Testing apache camel routes - spring

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.

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

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>

Apache Camel -- Reference type constants in Spring DSL

I'm trying to define a publish operation to Hazelcast topic using Spring DSL
<from uri="direct:inbound" />
<onCompletion>
<log message="onCompletion:- ${body}" />
<setHeader headerName="${type:org.apache.camel.component.hazelcast.HazelcastConstants.OPERATION}">
<simple>${type:org.apache.camel.component.hazelcast.HazelcastConstants.PUBLISH_OPERATION}</simple>
</setHeader>
<to uri="hazelcast:topic:foo" />
</onCompletion>
<log message="${body}" />
The above route works, but I have to use long SIMPLE scripts like "${type:org.apache.camel.component.hazelcast.HazelcastConstants.OPERATION}" to ref a constant value. Is there any simpler or short form for this?
I tried to define a spring bean for HazelcastConstants class and ref it through SIMPLE scripts as below but it's not working with MethodNotFoundException "Method with name: OPERATION not found on bean"
<bean id="hazelcastConstants" class="org.apache.camel.component.hazelcast.HazelcastConstants" />
... ...
<simple>${bean:hazelcastConstants.OPERATION}</simple>
Your bean workaround would work, if you defined a bean contained a method returning the constant in question, e.g.:
public class ContantRetriever() {
public String getHazelCastOperation() {
return org.apache.camel.component.hazelcast.HazelcastConstants.PUBLISH_OPERATION;
}
}
Your Spring context:
<bean id="hazelcastConstants" class="yourpackage.ContantRetriever"/>
<simple>${bean:hazelcastConstants.getHazelCastOperation}</simple>
If that is no good for you, I am afraid you are stuck with the long form of accessing constants.

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

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.

Spring integration : replace xml configured bean property dynamically?

I'm trying to do a ftp poller with the help of Spring integration and the poller works great with the xml configuration. Now I would like to be able to dynamically set some properties of the poller like the cron-expression or the polling rate to make it configurable by java code and link it to a web interface.
I have seen a lot of topics around the subject but nothing really clear to do that.
Is there a classic way of doing that ?
Can it be done with SpeL ?
My bean poller declaration in XML is as follows :
<int-ftp:inbound-channel-adapter id="ftpInbound"
channel="ftpChannel" session-factory="ftpClientFactory"
filename-regex=".*\.tmp$" auto-create-local-directory="true"
delete-remote-files="false" remote-directory="/cft-polling" local-directory="file:target/ftp-output" >
<int:poller fixed-rate="1000" />
</int-ftp:inbound-channel-adapter>
<int:channel id="ftpChannel">
<int:queue />
</int:channel>
I'm not sure there is enough here for a solid answer, but assuming that the ftp poller is defined and managed in the spring container, and assuming there are proper accessores to modify it's properties...that you will be able to change it's setting just like you would any other object.
First you would have to get a reference of the spring managed object, you can do this by having one of your classes implement ApplicationContextAware thereby exposing the Spring context.
Then it's just a matter of getting the bean from the context and updating it's property.
public class MyManagedClass implements ApplicationContextAware {
private ApplicationContext springContext;
public void changeBeansProperty(){
MyFtpPoller poller = (MyFtpPoller) springContext.getBean("ftpInbound");
poller.setCronExpress("12 12 * * * *");
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.springContext = applicationContext;
}
}

Resources