Best way to write a Camel Test on a Content Based Routing - apache-camel

I'm a bit stuck on how to proceed in writing a Simple Camel Test case for a CBR. Say I have this simple route:
<route id="_route1">
<from id="_to1" uri="file:/home/user/data/eip-demo/in?noop=true"/>
<choice id="_choice1">
<when id="_when1">
<simple>${header.CamelFileName} regex '^.*xml$'</simple>
<to id="_to2" uri="file:/home/user/data/eip-demo/xml"/>
</when>
<when id="_when2">
<simple>${header.CamelFileName} regex '^.*txt$'</simple>
<to id="_to3" uri="file:/home/user/data/eip-demo/txt"/>
</when>
<otherwise id="_otherwise1">
<log id="_log1" message="File unknown!"/>
<to id="_to2" uri="file:/tmp"/>
</otherwise>
</choice>
</route>
What is the most correct way to test this route? I think I could replace the file: component with a direct: component and Produce the files in the Test Case. However, then I will not be able to run the route directly from the IDE (Jboss Developer Studio). What is the most correct approach to coding a route like that which needs to be tested?
UPDATE: I've refined a bit the Camel Test created by JBoss Developer Studio:
public class CamelContextXmlTest extends CamelSpringTestSupport {
// TODO Create test message bodies that work for the route(s) being tested
// Expected message bodies
protected Object[] expectedBodies = { "<something id='1'>expectedBody1</something>",
"textfile" };
protected Object[] name = { "test.xml",
"test.txt" };
// Templates to send to input endpoints
#Produce(uri = "file:/home/data/eip-demo/in?noop=true")
protected ProducerTemplate inputEndpoint;
// Mock endpoints used to consume messages from the output endpoints and then perform assertions
#EndpointInject(uri = "mock:output")
protected MockEndpoint outputEndpoint;
#EndpointInject(uri = "mock:output2")
protected MockEndpoint output2Endpoint;
#EndpointInject(uri = "mock:output3")
protected MockEndpoint output3Endpoint;
#Test
public void testCamelRoute() throws Exception {
// Create routes from the output endpoints to our mock endpoints so we can assert expectations
context.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
from("file:/home/user/data/eip-demo/xml").to(outputEndpoint);
from("file:/home/user/data/eip-demo/txt").to(output2Endpoint);
}
});
// Define some expectations
// TODO Ensure expectations make sense for the route(s) we're testing
//outputEndpoint.expectedBodiesReceivedInAnyOrder(expectedBodies);
// Send some messages to input endpoints
int index=0;
for (Object expectedBody : expectedBodies) {
inputEndpoint.sendBodyAndHeader(expectedBody,"CamelFileName",name[index]);
index++;
}
outputEndpoint.expectedMessageCount(1);
output2Endpoint.expectedMessageCount(1);
// Validate our expectations
assertMockEndpointsSatisfied();
}
#Override
protected ClassPathXmlApplicationContext createApplicationContext() {
return new ClassPathXmlApplicationContext("META-INF/spring/camel-context.xml");
}
}
Is it a good solution or can it be improved?

What you need here is AdviseWith:
http://camel.apache.org/advicewith.html
You simply need to replace/modify your endpoints on the fly OR simply add a mock on the fly after each condition of your CBR. If you couldn't do it, let me know and I will help with with some actual code that does this job for you.
R.

Related

Camel - ControlBus - Activate timer route

I have a timer route that is initially not started. I would like to activate it from another route. I am attempting to use the camel's controlbus EIP pattern.
// From my other route
.to("controlbus:route?routeId=fileConsumerRoute&action=start&async=true")
from("timer://camel-fileConsumerRoute?fixedRate=true&period=5s")
.routeId("fileConsumerRoute").noAutoStartup()
.log("Route is running");
I see in the logs aft the controlbus line is run that the route is being resumed
Context action: [resume]
but the timer still does not fire. I dont see the log line "Route is running"
How do I activate the timer endpoint with the controlbus?
Is there another EIP pattern or diffrent way to achieve activating the timer endpoint?
I realize I'm late to the party, but, you could use controlbus or something like this start stop routes. if there is some predicate some other boolean test.
example:
we test if some condition exists every period and perform operation on result of condition...
<!-- Token Database -->
<route autoStartup="true" id="core.predix.tkndependencyCheckerRoute">
<from id="_from2" uri="timer://tkndbAvailable?fixedRate=true&period=30000"/>
<process id="_process2" ref="tkndbAvailableProcessor"/>
</route>
public class TknDbAvailableProcessor implements Processor {
private static final Logger LOG = LoggerFactory.getLogger(TknDbAvailableProcessor.class);
private TKNDataService tknDataService = null;
#Override
public void process(Exchange exchange) throws Exception {
ServiceStatus status = exchange.getContext().getRouteStatus("core.fleet.EnrichmentRoute");
if (null == tknDataService) {
if (status.isStarted()) {
exchange.getContext().stopRoute("core.fleet.EnrichmentRoute");
}
} else {
if (tknDataService.isTKNDataServiceAvailable()) {
if (status.isStopped()) {
exchange.getContext().startRoute("core.fleet.EnrichmentRoute");
}
} else {
if (status.isStarted()) {
exchange.getContext().stopRoute("core.fleet.EnrichmentRoute");
}
}
}
}
public void setDataService(TKNDataService dataService) {
this.tknDataService = dataService;
}
}
If needing a master singleton route if needing redundancy - on diff nodes use a JGroups Cluster with ControlBus
<bean class="org.apache.camel.component.jgroups.JGroupsFilters"
factory-method="dropNonCoordinatorViews"
id="dropNonCoordinatorViews" scope="prototype"/>
<bean class="org.apache.camel.component.jgroups.JGroupsExpressions"
factory-method="delayIfContextNotStarted"
id="delayIfContextNotStarted" scope="prototype">
<argument value="5000"/>
</bean>
<route autoStartup="true" id="FleetMainClusterControlRoute">
<from id="_from2" uri="jgroups:mainCluster?enableViewMessages=true&channelProperties=etc/jgroups.xml"/>
<filter id="filterNonCoordinatorViews">
<method ref="dropNonCoordinatorViews"/>
<threads id="threads1" threadName="ControlThreads">
<delay id="_delay1">
<ref>delayIfContextNotStarted</ref>
<log id="logCR" loggingLevel="INFO" message="Starting Main Cluster consumer!!!!!!!!!!!!!!!!!!!!!"/>
<to id="_toCR" uri="controlbus:route?routeId=core.predix.tkndependencyCheckerRoute&action=start&async=true"/>
</delay>
</threads>
</filter>
</route>

JBoss fuse camel: Error when I evaluate a body

I am invoking a web service from the camel, and when I try to evaluate its response, I get an error. This is the camel code:
<!-- Transformatio to the ws backend -->
<process id="_transformToValidaAccesoUsuario" ref="transformToValidaAccesoUsuario"/>
<!-- Invoke the ws -->
<to id="invokeAutenticaSesion" uri="cxf:bean:autenticaSesionProxy?defaultOperationName=validarAccesoUsuario"/>
<!-- Validate the response -->
<choice id="validacionAutenticaUsuario">
<when id="validacionUsuarioOK">
<simple>${body.getResponseStatus.getDescripcionRespuesta} == 'OK'</simple>
<log id="logValidacionUsuario" message="validacionUsuario correcto"/>
</when>
<otherwise id="validacionUsuarioError">
<log id="logValidacionUsuario2" message="validacionUsuario incorrecto"/>
</otherwise>
</choice>
I have this error when I run the service:
<faultstring>Failed to invoke method: getResponseStatus on null due to: org.apache.camel.component.bean.MethodNotFoundException: Method with name: getResponseStatus not found on bean: [pe.gob.sis.esb.negocio.consultaafiliados.proxy.autenticasesion.ValidarAccesoUsuarioResponseType#f482049] of type: org.apache.cxf.message.MessageContentsList. Exchange[]</faultstring>
Edit:
The class already has the method getResponseStatus()
package pe.gob.sis.esb.negocio.consultaafiliados.proxy.autenticasesion;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "ValidarAccesoUsuarioResponseType", namespace = "http://sis.gob.pe/esb/tecnico/autenticaSesion/messages/validarAccesoUsuario/v1/", propOrder = {
"responseStatus",
"login"
})
public class ValidarAccesoUsuarioResponseType {
protected ResponseStatus responseStatus;
protected Login login;
public ResponseStatus getResponseStatus() {
return responseStatus;
}
public void setResponseStatus(ResponseStatus value) {
this.responseStatus = value;
}
public Login getLogin() {
return login;
}
public void setLogin(Login value) {
this.login = value;
}
}
Ah its maybe the crappy MessageContentsList from camel-cxf / CXF. Its a design fault in camel-cxf I think. So what you can do is to convert the message body to not include that, by
<setBody><simple>${body[0]}</simple></setBody>
Which will take the first element from that MessageContentsList and store that as the message body, which will be that POJO class.
It depends a bit how you configure camel-cxf / CXF and what is stored as the message body. But the MessageContentsList is a smell.

How to map the response for the REST request by Apache camel HTTP component

I have implemented following route with Apache camel XML mode.
<restConfiguration component="servlet" bindingMode="json" contextPath="/abc-esb/rest" port="8181">
<dataFormatProperty key="prettyPrint" value="true" />
<dataFormatProperty key="json.in.disableFeatures" value="FAIL_ON_EMPTY_BEANS,FAIL_ON_UNKNOWN_PROPERTIES" />
</restConfiguration>
<rest path="/service-http" consumes="application/json" produces="application/json">
<put type="com.abc.abcd.esb.models.UserServiceMapping" uri="/create">
<route>
<setHeader headerName="Authorization">
<simple>Basic YWRtaW46YWRtaW4=</simple>
</setHeader>
<setHeader headerName="CamelHttpMethod">
<constant>POST</constant>
</setHeader>
<setBody>
<simple>
{"SystemUserDetails":{"userName":"${body.objectDetails.userName}",
"password": "${body.objectDetails.password}"}}
</simple>
</setBody>
<to uri="http://192.168.1.20:8081/abcd/services/UserServiceRest/V1.0/users/create?bridgeEndpoint=true" />
</route>
</put>
</rest>
Functionality happens as expected. But camel returns HTTP 500 mentioning following error.
com.fasterxml.jackson.databind.JsonMappingException: No serializer
found for class
org.apache.camel.converter.stream.CachedOutputStream$WrappedInputStream
and no properties discovered to create BeanSerializer (to avoid
exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) at
com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:69)
at
com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:32)
at
com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:130)
When I analyze a tcp dump related to this scenario, I found this HTTP 500 occurred when trying to mapping the {"status" : "Success"} response from the external webservice.
Content of the UserServiceMapping class
private String serviceName;
private String serviceMethod;
private String mappedServiceObject;
private SystemUserDetails objectDetails;
// private StatusDetails statusDetails;
public String getMappedServiceObject() {
return mappedServiceObject;
}
public void setMappedServiceObject(String mappedServiceObject) {
this.mappedServiceObject = mappedServiceObject;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getServiceMethod() {
return serviceMethod;
}
public void setServiceMethod(String serviceMethod) {
this.serviceMethod = serviceMethod;
}
public SystemUserDetails getObjectDetails() {
return objectDetails;
}
public void setObjectDetails(SystemUserDetails objectDetails) {
this.objectDetails = objectDetails;
}
/*
* public StatusDetails getStatusDetails() { return statusDetails; }
*
* public void setStatusDetails(StatusDetails statusDetails) {
* this.statusDetails = statusDetails; }
*/
Request response flow of the scenario
How ever up to now I couldn't find an exact reason for this issue but I could by-pass the problem by setting Accept: application/xml other than Accept: application/json in my external REST client which makes the initial REST request. (REST service endpoint in other end configured to produce both json and xml)

get exchange related properties like ExchangePattern ID and BodyType in message

I'm new to Camel. I'm trying to read a file from one location, making some change in the file content and sending the file to another location. I trying to get exchange related information added in the file like the following:
Exchange[ExchangePattern: InOnly, BodyType: String, Body:
Test Message from custom processor:
Text from input file.
First line in the above sample is not coming. So far I wrote the following code:
applicationContext.xml:
<bean id="customProcessor" class="com.javacodegeeks.camel.CustomProcessor"/>
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="file:C:\\inputdir\\" />
<process ref="customProcessor"/>
<to uri="file:C:\\outputdir\\" />
</route>
CustomProcessor.java:
public class CustomProcessor implements Processor {
public void process(Exchange exchange) throws Exception {
String msgBody = exchange.getIn().getBody(String.class);
exchange.getIn().setBody("Test Message from custom processor: " + msgBody);
}
}
Main class:
public static final void main(String[] args) throws Exception {
ApplicationContext appContext = new ClassPathXmlApplicationContext(
"applicationContext.xml");
CamelContext camelContext = SpringCamelContext.springCamelContext(
appContext, false);
try {
camelContext.start();
Thread.sleep(200000);
} finally {
camelContext.stop();
}
}
You can access all the properties of Camel Exchange using Exchanges' method getProperties() i.e. exchange.getProperties() which return Map, so using this you can get the information and save it into the file using your CustomProcessor Camel processor.
For more information have a look on:
http://camel.apache.org/maven/current/camel-core/apidocs/org/apache/camel/Exchange.html

How do I properly use Mocks when testing Camel routes?

I am trying to write a Camel test that checks to ensure a content based router is routing XML files correctly. Here are the enpoints and the route in my blueprint.xml:
<endpoint uri="activemq:queue:INPUTQUEUE" id="jms.queue.input" />
<endpoint uri="activemq:queue:QUEUE1" id="jms.queue.1" />
<endpoint uri="activemq:queue:QUEUE2" id="jms.queue.2" />
<route id="general-jms.to.specific-jms">
<from ref="jms.queue.input" />
<choice>
<when>
<xpath>//object-type = '1'</xpath>
<log message="Sending message to queue: QUEUE1" />
<to ref="jms.queue.1" />
</when>
<when>
<xpath>//object-type = '2'</xpath>
<log message="Sending message to queue: QUEUE2" />
<to ref="jms.queue.2" />
</when>
<otherwise>
<log message="No output was able to be determined based on the input." />
</otherwise>
</choice>
</route>
Right now, all I am trying to do is send in a sample source file that has an <object-type> of 1 and verify that is it routed to the correct queue (QUEUE1) and is the correct data (should just send the entire XML file to QUEUE1). Here is my test code:
public class RouteTest extends CamelBlueprintTestSupport {
#Override
protected String getBlueprintDescriptor() {
return "/OSGI-INF/blueprint/blueprint.xml";
}
#Override
public String isMockEndpointsAndSkip() {
return "activemq:queue:QUEUE1";
}
#Test
public void testQueue1Route() throws Exception {
getMockEndpoint("mock:activemq:queue:QUEUE1").expectedBodiesReceived(context.getTypeConverter().convertTo(String.class, new File("src/test/resources/queue1-test.xml")));
template.sendBody("activemq:queue:INPUTQUEUE", context.getTypeConverter().convertTo(String.class, new File("src/test/resources/queue1-test.xml")));
assertMockEndpointsSatisfied();
}
}
When I run this test, I see the log message that I put in the route definition that says it is sending it to QUEUE1, but the JUnit test fails with this error message: java.lang.AssertionError: mock://activemq:queue:QUEUE1 Received message count. Expected: <1> but was: <0>.
Can someone help me understand what I am doing wrong?
My understanding is that Camel will automatically mock the QUEUE1 endpoint since I overrode the isMockEndpointsAndSkip() and provided the QUEUE1 endpoint uri. I thought this meant I should be able to use that endpoint in the getMockEnpoint() method just by appending "mock:" to the beginning of the uri. Then I should have a mocked endpoint of which I can set expections on (i.e. that is has to have the input file).
If I am unclear on something please let me know and any help is greatly appreciated!
The solution is to use CamelTestSupport.replaceRouteFromWith.
This method is completely lacking any documentation, but it works for me when invoking it like this:
public class FooTest extends CamelTestSupport {
#Override
public void setUp() throws Exception {
replaceRouteFromWith("route-id", "direct:route-input-replaced");
super.setUp();
}
// other stuff ...
}
This will also prevent the starting of the original consumer of the from destination of the route. For example that means it isn't necessary anymore to have a activemq instance running when a route with an activemq consumer is to be tested.
After working on this for quite some time, the only solution I came up with that actaully worked for me was to use the createRouteBuilder() method in my test class to add a route to a mocked endpoint at the end of the route defined in my blueprint.xml file. Then I can check that mocked endpoint for my expectations. Below is my final code for the test class. The blueprint XML remained the same.
public class RouteTest extends CamelBlueprintTestSupport {
#Override
protected String getBlueprintDescriptor() {
return "/OSGI-INF/blueprint/blueprint.xml";
}
#Test
public void testQueue1Route() throws Exception {
getMockEndpoint("mock:QUEUE1").expectedBodiesReceived(context.getTypeConverter().convertTo(String.class, new File("src/test/resources/queue1-test.xml")));
template.sendBody("activemq:queue:INPUTQUEUE", context.getTypeConverter().convertTo(String.class, new File("src/test/resources/queue1-test.xml")));
assertMockEndpointsSatisfied();
}
#Test
public void testQueue2Route() throws Exception {
getMockEndpoint("mock:QUEUE2").expectedBodiesReceived(context.getTypeConverter().convertTo(String.class, new File("src/test/resources/queue2-test.xml")));
template.sendBody("activemq:queue:INPUTQUEUE", context.getTypeConverter().convertTo(String.class, new File("src/test/resources/queue2-test.xml")));
assertMockEndpointsSatisfied();
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
public void configure() throws Exception {
from("activemq:queue:QUEUE1").to("mock:QUEUE1");
from("activemq:queue:QUEUE2").to("mock:QUEUE2");
}
};
}
}
While this solution works, I don't fully understand why I can't just use isMockEndpointsAndSkip() instead of having to manually define a new route at the end of my existing blueprint.xml route. It is my understanding that defining isMockEndpointsAndSkip() with return "*"; will inject mocked endpoints for all of your endpoints defined in your blueprint.xml file. Then you can check for your expections on those mocked endpoints. But for some reason, this does not work for me.

Resources