Apache Camel: Weave does not work in Split. Why? - spring

I have the following XML DSL context definition in spring:
<beans>
<camelContext>
<route>
<from uri="direct:foo"/>
<split parallelProcessing="true">
<simple>${body}</simple>
<to uri="direct:bar"/>
</split>
</route>
</camelContext>
</beans>
In my test I'm trying to weave the direct:bar endpoint like so:
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
weaveByToString(".*direct:bar.*").replace().to("mock:bar");
}
});
This successfully works. But when route is starts an exception is thrown saying org.apache.camel.NoSuchBeanException: No bean could be found in the registry for: direct:bar.
Why?
May be camel does not support weaving inside split?
Note: Everything works just fine with following XML:
<beans>
<camelContext>
<route>
<from uri="direct:dummy"/>
<to uri="direct:bar"/>
</route>
<route>
<from uri="direct:foo"/>
<split parallelProcessing="true">
<simple>${body}</simple>
<to uri="direct:dummy"/>
</split>
</route>
</camelContext>
</beans>

I could not reproduce your error with the use case described using Camel 2.7. Here is my test that pass:
#Test
public void test() throws Exception {
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
weaveByToString(".*direct:bar.*").replace().to("mock:bar");
}
});
MockEndpoint mock = getMockEndpoint("mock:bar");
mock.expectedMessageCount(1);
template.sendBody("direct:foo", "this is a test");
assertMockEndpointsSatisfied();
}
Starting the route using camel:run also start without throwing a NoSuchBeanException.

Related

Multiple camel context not accepted in Spring Boot Came single configl xml model ?

Base Facts : Apache Camel 2.20.1 (Spring Boot)
Multiple context reference in same spring boot config xml throws below highlighted error, despite providing explicit different ids
When my own example failed - I tried with simple sample case.. but met with the same error
Error creating bean with name 'typeConverter' defined in class path resource >[org/apache/camel/spring/boot/TypeConversionConfiguration.class]: Unsatisfied >dependency expressed through method 'typeConverter' parameter 0; nested >exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: >No qualifying bean of type 'org.apache.camel.CamelContext' available: expected >single matching bean but found 2: camel1,camel2
<!-- here we have the 1st CamelContext -->
<camelContext id="camel1" xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:one" />
<to uri="mock:result" />
</route>
</camelContext>
<!-- and there we have the 2nd CamelContext -->
<camelContext id="camel2" xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:two" />
<to uri="log:two" />
<to uri="mock:result" />
</route>
</camelContext>
My context are being called from Spring boot starter class as follows.
#SpringBootApplication
#ComponentScan(basePackages="com.wm")
#ImportResource("classpath:META-INF/spring/spring-context.xml")
public class ExtAuthServiceAppStarter {
public static void main(String[] args) {
SpringApplication.run(ExtAuthServiceAppStarter.class, args);
}
}
Any suggestions ?
Yes Spring Boot is not an application server to host N+ applications in the same JVM.
Camel on Spring Boot is optimized and adhered to run one CamelContext only.
Right, you can't have multiple Camel Context.
One option to consider, having routes you want to include inside a Route Context instead of a Camel Context. A Route Context is like a subset of a Camel Context. Then you reference the Route Contexts you want to import and Camel will add them to the context.
Reference : https://tomd.xyz/multiple-camel-contexts/
I think you can do that : you have to specify the context in ProducerTemplate tag named #Produce like this :
#Produce(uri = "direct:JMS:export:JMS", context = "camel1")
ProducerTemplate exportProducer

Testing apache camel routes

I have an application build using Apache Camel 2.15.3. And I'm wiring the routes using spring-xml for dependency injection. I'm having a hard time understanding how to write automatic test for my routes. For example I might have the routes:
<onException id="Exception">
<exception>java.lang.Exception</exception>
<handled>
<constant>true</constant>
</handled>
<to uri="direct:fear"/>
</onException>
<route id="happyStory">
<from uri="direct:inTheBeginning"/>
<to uri="bean:enchantedKingdom?method=warn" />
<to uri="bean:fluffykins" />
</route>
<route id="scaryStory">
<from uri="direct:fear"/>
<onException>
<exception>java.lang.Exception</exception>
<handled>
<constant>true</constant>
</handled>
</onException>
<to uri="bean:monster"/>
<choice>
<when>
<simple>${header.succesfullywarned}</simple>
<to uri="bean:enchantedKingdom?method=hide"/>
</when>
<otherwise>
<to uri="bean:enchantedKingdom?method=panic" />
</otherwise>
</choice>
</route>
And I wan't to be able to say that when the bean method warn is called then the header "succesfullywarned" should be set in the message and then when the bean fluffykins is called there should be a exception that causes the message to get sent to "scaryStory" and in this case I wan't to assert that the bean method hide is called.
This is roughly my test class setup:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration({"/META-INF/spring/route-
stories.xml","/META-INF/spring/beans.xml"})
#MockEndpointsAndSkip("(bean:fluffykins|bean:monster|bean:enchantedKingdom?method=warn|bean:enchantedKingdom?method=hide|bean:enchantedKingdom?method=panic)")
public class StoryHappyRouteTest extends CamelSpringTestSupport {
private String url = "direct:inTheBeginning";
#Autowired
private ApplicationContext applicationContext;
#Override
protected AbstractApplicationContext createApplicationContext() {
return (AbstractApplicationContext)applicationContext;
}
#Test
public void test(){
MockEndpoint warn = getMockEndpoint("mock:bean:enchantedKingdom?method=warn");
MockEndpoint fluffy = getMockEndpoint("mock:bean:fluffykins");
MockEndpoint monster = getMockEndpoint("mock:bean:monster");
MockEndpoint hide = getMockEndpoint("mock:bean:enchantedKingdom?method=hide");
MockEndpoint panic = getMockEndpoint("mock:bean:enchantedKingdom?method=panic");
fluffy.whenAnyExchangeReceived(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println("Bunny!");
throw new NullPointerException();
}
});
template.sendBody(url,"");
warn.assertExchangeReceived(0);
fluffy.assertExchangeReceived(0);
monster.assertExchangeReceived(0);
panic.assertExchangeReceived(0);
}
}
I've read the chapter on testing in the first edition of Camel in action and look around in the manual (http://camel.apache.org/testing.html) but I don't understand how to apply it in my situation. In the above test everything works except where I have bean with multiple methods so I have an uri that contains "?method=methodname", and for some reason this makes it not work. I don't get an error or but the mock is not used and instead the actual beans are called.
Is it not possible to do what I wan't to do? I can change the test setup in any way, but it is a given that the routes and the beans are defined in the spring-xml files.
I've taught about mocking the beans themselves and not the endpoints but the only way I can think of doing that is creating a "imposter-beans.xml" file where all the beans are defined, that points to stubbclasses that extend every class used in the routes. But that feels like an elaborate and wrong approach.
You can write code what to do when the mock receives a message. This is covered in the book in section 6.2.6, where you can use methods like whenAnyExchangeReceived or whenExchangeReceived, and in those inlined processors you can set the header, or throw an exception etc. See for example listing 6.9.

Camel - Using property in Spring DSL

I'm trying to set some properties in my process method but I'm not able to figure out that how to use those properties in xml, like I can use header values in xml easily by using syntax : ${in.header.myKey}
Here's my code :
<route>
<from uri="activemq:queue:start.queue" />
<to uri="stream:out" />
<process ref="jsonProcessor"></process>
<to uri="bean:validateInputIdentifiers?method=validation(${in.property.SourceMap}, ${in.property.DestinationMap})" />
</route>
Here in.property.SourceMap is Unknown function. What is the correct way?
Would be great if it is something similar to header. Also I want to use property only and not header since values of header may not persists later in my routes.
Here's process method code:
#Override
public void process(Exchange exchange) throws Exception {
List<Map<String, String>> body = exchange.getIn().getBody(List.class);
Map<String, String> sourceMap = body.get(0);
Map<String, String> destinationMap = body.get(1);
exchange.setProperty("SourceMap", sourceMap);
exchange.setProperty("DestinationMap", destinationMap);
}
Kindly provide the solution.
There could be multiple solution combinations for your problem.
Sample Property Key and Value.
<cm:property name="app.user" value="PROD008"/>
In Route if u want to set header with property value. Use below code snippet.
<setHeader headerName="password">
<simple>${properties:app.user}</simple>
</setHeader>
If you want to use property, you can use below snippet.
<to uri="{{some.endpoint}}"/>
For your example: if Properties are SourceMap and DestinationMap you can use any of below.
1. <to uri="bean:validateInputIdentifiers?method=validation(${property.SourceMap}, ${property.DestinationMap})" />
2. <to uri="bean:validateInputIdentifiers?method=validation({{SourceMap}},{{DestinationMap}})" />
If you want to use header instead of property then use below code snippet.
<to uri="bean:validateInputIdentifiers?method=validation(${header.SourceMap}, ${header.DestinationMap})" />
After hit and trial I got the working solution:
<route>
<from uri="activemq:queue:start.queue" />
<to uri="stream:out" />
<process ref="jsonProcessor"></process>
<to uri="bean:validateInputIdentifiers?method=validation(${property.SourceMap}, ${property.DestinationMap})" />
</route>

Camel setBody using Spring configuration

I have built a Java Camel Timer-JMS route using:
context.addRoutes(new RouteBuilder() {
public void configure() {
from("timer:foo?period=1s").setBody(body().
append("Message at ${date:now:yyyy-MM-dd HH:mm:ss}")).to("jms:queue:activemq/queue/TestQueue");
}
});
Now I need to turn it to Spring.
<camel:route>
<camel:from uri="timer:foo?period=1s" />
<camel:to uri="jms:queue:activemq/queue/TestQueue" />
</camel:route>
I'm missing the equivalent in Spring XML configuration of the expression:
setBody(body().append("Message at ${date:now:yyyy-MM-dd HH:mm:ss}")
In Spring you can use the simple language, to build such messages. In fact you can also do this in Java, it the same.
<setBody>
<simple>${body}Message at ${date:now:yyyy-MM-dd HH:mm:ss}</simple>
</setBody>
Mind that the timer sends an empty/null body. So you may see "null" in the message.
About simple see: http://camel.apache.org/simple

Camel Transactional client with Spring, how to rollback route changes?

Consider the following two test. The findOne() has no side effect, the delete() has a side effect on the underlying h2 database. My problem is the #Transactional does not rollback the changes for the delete() method.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:app-context.xml")
public class AccountProcessorTest extends BaseRouteTest {
private static final String ACCOUNTS_ENDPOINT = "seda:bar";
#Test
#Transactional
public void findOne() {
final Map<String, Object> headers = new HashMap<String, Object>();
headers.put("id", 1);
headers.put(Exchange.HTTP_METHOD, "GET");
final String response = template.requestBodyAndHeaders(ACCOUNTS_ENDPOINT, null, headers, String.class);
assertEquals("Checking account",JsonPath.read(response, "name"));
}
#Test
#Transactional
public void delete() {
final Map<String, Object> headers = new HashMap<String, Object>();
headers.put("id", 1);
headers.put(Exchange.HTTP_METHOD, "DELETE");
final String response = template.requestBodyAndHeaders(ACCOUNTS_ENDPOINT, null, headers, String.class);
assertEquals(200, JsonPath.read(response, "code"));
}
}
The BaseRouteTest is just a utility where I get a reference to the Camel ProducerTemplate
public class BaseRouteTest implements InitializingBean {
#Autowired
private ApplicationContext applicationContext;
protected ProducerTemplate template;
#Override
public void afterPropertiesSet() throws Exception {
template = getCamelContext().createProducerTemplate();
}
private CamelContext getCamelContext() {
return applicationContext.getBean("foo", CamelContext.class);
}
}
I have marked the route as transacted using the transacted tag.
<!-- camel-context -->
<camel:camelContext id="foo">
<camel:route>
<camel:from uri="seda:bar"/>
<camel:transacted />
<camel:process ref="accountProcessor"/>
</camel:route>
</camel:camelContext>
My spring configuration file:
<context:component-scan base-package="com.backbase.progfun"/>
<!-- embedded datasource -->
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:data/schema.ddl"/>
<jdbc:script location="classpath:data/insert.sql"/>
</jdbc:embedded-database>
<!-- spring jdbc template -->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
<!-- transaction management start -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- transaction management end -->
<!-- camel-context -->
<camel:camelContext id="foo">
<camel:route>
<camel:from uri="seda:bar"/>
<camel:transacted />
<camel:process ref="accountProcessor"/>
</camel:route>
</camel:camelContext>
You can try it out quickly if you clone my github repo found here:
https://github.com/altfatterz/camel-transaction
If you run the AccountProcessorTest it the findOne() test case fails because the side effect of delete() test case is not rolled back.
Any suggestion would be greatly appreciated.
Thank you.
Transactions aren't carried across SEDA queues.
Therefore the transaction started by your test is a different transaction from the transaction in your route. So the changes made by your route won't be rolled back when the transaction started by your test is rolled back.

Resources