Is there any way to mocking part of camel route?
I build such a route:
from("a").b().signReq().send().validateAns().c().to("d");
but when i run tests, i don't want add signReq().send().validateAns() into route. Any suggestions?
Also, maybe there is a way to encapsulate such part of route into method? It will be great, because i have many routes and many same interaction parts. Best if it can be done without runtime choice/when switches, because i know all conditions in configure phase.
For testing existing routes you can use AdviceWith and advice a route before its being tested.
I propose using weaveById which is the most precise way to replace parts of a route.
For example in the following route
from("direct:start")
.to("mock:foo")
.to("mock:bar").id("bar")
.to("mock:result");
After setting the id of "mock:bar" to "bar" then you can use in your test
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
// weave the node in the route which has id = bar
// and replace it with the following route path
weaveById("bar").replace().multicast().to("mock:a").to("mock:b");
}
});
In you example you can do something like:
from("a").b().to("direct:replace").id("replace").c().to("d");
from("direct:replace").signReq().send().validateAns();
And afterwards advice the route by using:
weaveById("replace").remove();
Of course more ways exist to implement this functionality. For all the options and a full example go to http://camel.apache.org/advicewith.html
Tip: Give extra attention to the part of the code in the example that starts the context!
// we must manually start when we are done with all the advice with
context.start();
Related
We have a Play 2.6 Java application that works great so far.
Now we need to add some route to it that will just be taking the original request, decorating it with some additional headers or whatever and sending it forward to be handled by some other app (probably using Play's WS client)
The question is if it is possible to create some route like this one in the Play routes file:
* /forward-to-smth/*whatever my.Action.forward
where * will match all the HTTP Methods, and we'll be just getting the Http.Request instance in the action body and handling it as required.
What is the best way of handling this kind of scenarios in Play?
We can probably create multiple routes' entries for each HTTP method and multiple actions in in the controller for each of the methods, delegating all the handling to a single handle-it-all method, but is there any more elegant solution?
Creating some custom HttpRequestHandler for this case will probably be an overkill?
Play conveniently generates a Router from the routes file, but you can also write a router yourself. See https://www.playframework.com/documentation/2.6.x/ScalaSirdRouter
class MyRouter #Inject()(controller: MyController) extends SimpleRouter {
override def routes: Routes = {
case _ => controller.forward
}
}
Then, in your routes file, add
-> /forward-to-smth my.MyRouter
The problem
I have a spring mvc application that uses apache camel. I am confused on the role that the RouteBuilder class plays and how it actually gets initialized. I know that the docs say that the configure() method is:
Called on initialization to build the routes using the fluent builder syntax.
but when does this initialization occur? Does it occur at application startup or some time later when the route is about to be used?
The purpose of this question is ultimately to ask how I can modify the route at runtime. I want to be able to build different routes as needed.
Examples
xml definitions:
<service name="myService" tier="3">
<requestType>my.package.RequestType</requestType>
<responseType>my.package.ResponseType</responseType>
<endpoint>
<httpEndpoint>
<url default="true" value="someUrl"/>
<timeout value="5000"/>
</httpEndpoint>
</endpoint>
</service>
Route Builder template:
public class myRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
// When does this method get executed?
}
}
Questions
When does configure() execute?
How can I dynamically set the endpoint url?
You are able to use toD to dynamically change the endpoint at runtime based on an expression. See the documentation
If you want to change more of the route or add a completely new route then look at the API on the CamelContext. This Stackoverflow question has an example of adding a completely new route.
The lifecycle of the Camel service is documented here : https://camel.apache.org/lifecycle.html
Camel uses a simple lifecycle interface called Service which has a single start() and stop() method.
Various classes implement Service such as CamelContext along with a number of Component and Endpoint classes.
When you use Camel you typically have to start the CamelContext which will start all the various components and endpoints and activate the routing rules until the context is stopped again.
It is when the context starts that the various components start. Not sure i understand the dynamic url part. If it is to indicate a dynamic endpoint (if the data is this , then queue1 else queue2) you should be able to use something like the DynamicRouter EIP which is as explained here (https://camel.apache.org/dynamic-router.html)
You have several options.
Inject them as spring properties.
Inject them from external properties source.
Inject them from some bean method.
Then you can put the property value in a header and later put the value in the .toD("$header.routeEndpoint"). This can take care of dynamic endpoints.
Off course to rebuild the entire route you need to play with the API.
I'm using Camel to integrate 2 systems. I have defined different routes and one of the routes consumes from a specific rabbitmq queue and send it to a REST service. Nothing fancy here, the route looks like this:
public class WebSurfingRabbitToRestRoute extends RouteBuilder{
#Override
public void configure() throws Exception {
from("rabbitmq://rabbit_host:port/Rabbit_Exchange").
setHeader("CamelHttpMethod", constant("POST")).
setHeader("Content-Type", constant("application/json")).
bean(TransformResponse.class, "transform").
to("http4://rest_service_host:port/MyRestService).
}
}
As you can see, i process every message before sending it to the rest service since i need to adjust some things. The problem comes when i find out that sometimes (i dont know how or when), the system that publish into rabbit, send 2 messages concatenated at once.
What i expect to get is a simple json like this:
[{field1:value1, field2:value2}]
What i sometimes get is:
[{field1:value1, field2:value2},{field1:value3, field2:value4}]
So when i face this scenario, the rest service im routing the message to, fails (obviously).
In order to solve this, i would like to know if there is a way to invoke a route from inside a processor. From the previous snippet of code you can see that Im calling the transform method, so the idea will be to do something like the following pseudo-code, because after the route is already fired, i cant split the events and send them both within the same route "instance", so i thought about invoking a different route that i can call from here which will send the message2 to the very same rest service.
public class TransformRabbitmqResponse {
public String transform(String body) throws Exception {
// In here i do stuff with the message
// Check if i got 2 messages concatenated
// if body.contains("},{") {
// split_messages
// InvokeDifferentRoute(message2)
//}
}
}
Do you guys think this is possible?
One option (though I am not sure this is the best option) would be to split this up into two different routes using a direct endpoint.
public class WebSurfingRabbitToRestRoute extends RouteBuilder{
#Override
public void configure() throws Exception {
from("rabbitmq://rabbit_host:port/Rabbit_Exchange")
.setHeader("CamelHttpMethod", constant("POST"))
.setHeader("Content-Type", constant("application/json"))
.bean(TransformResponse.class, "transform");
from("direct:transformedResponses")
.to("http4://rest_service_host:port/MyRestService");
}
}
And then in your transform bean, you can use camel Producer Template to publish the transformed payload(s) to your new direct endpoint (assuming you are using json?).
producerTemplate.sendBody("direct:transformedResponses", jsonString);
I am working on a Camel project where we are consuming multiple external partners / services. And I am struggling with error and exception management.
Currently I have a working version, but I'm really not happy with it... loads of duplicated code just for error handling. Routes look like this:
from("direct:myEntryPoint")
.errorHandler(defaultErrorHandler())
.onException(IOException.class)
.asyncDelayedRedelivery()
.useOriginalMessage()
.delayPattern("0:100;5:500;10:2000;20:60000")
.maximumRedeliveries(-1)
.end()
.onException(Throwable.class)
.handled(true)
.to("mock:logAndStoreFailedMessage")
.end()
.to("mock:businessLogic");
Here, the behavior is simple: if there is an IOException, we consider we didn't reach the partner and will retry again later. Other exceptions are handled like critical error and we will just log that the message failed.
Some other partners are considered more critical, we don't want to retry even for IOException:
from("direct:myEntryPoint")
.errorHandler(defaultErrorHandler())
.onException(Throwable.class)
.handled(true)
.to("mock:logAndStoreFailedMessage")
.end()
.to("mock:businessLogic");
And we have other behaviors too, but I don't want to spoil the post and list them all...
My question is:
How can we share onException logic across multiple routes?
My thoughts about this:
I would have been more than happy with a RouteDefinition#onException(OnExceptionDefinition) method, I guess, but it does not exist... (or even a RouteDefinition#onException(Class... throwables, OnExceptionDefinition) to specify the classes)
I tried working with ExceptionPolicyStrategy on the errorHandler, but apparently it's not build for this purpose (and didn't find a hint of this even in "Camel In Action")
I tried building a RouteBuilder extension (like suggested in Global onException to handle multiple RouteBuilder classes), but I don't have just one onException behavior to implement but I would like to be able to choose or even build a new one and I feel it's gonna be ugly to allow that. (Edit: see below, Edit 1 & 2)
Any help would be appreciated!
Thanks!
Edit 1:
I tried a bit more subclassing RouteBuilder and arrive to something I could live with, but I'm quite confident there are some better ways out there! Here is a sample of code for the subclass:
public abstract class MyRouteBuilder extends RouteBuilder {
protected ErrorHandlerBuilder firstErrorHandler() {
final DefaultErrorHandlerBuilder errorHandlerBuilder = super.defaultErrorHandler();
onException(IOException.class)
.asyncDelayedRedelivery()
.useOriginalMessage()
.delayPattern("0:100;5:500;10:2000;20:60000")
.maximumRedeliveries(-1);
onException(Throwable.class)
.handled(true)
.to("mock:logAndStoreFailedMessage")
.end();
return errorHandlerBuilder;
}
}
Edit 2:
And of course, it is not working when you declare many from routes in the same RouteBuilder (because, you cannot write a general onException after declaring a route), so it's for single use purpose.
Anyhow, I don't mark the question as answered yet, waiting for comments and better / cleaner propositions.
I've got a series of "pipelined" components that all communicate through ActiveMQ message queues. Each component uses Camel to treat each of these queues as an Endpoint. Each component uses the same basic pattern:
Where each component consumes messages off of an input queue, processes the message(s), and then places 1+ messages on an outbound/output queue. The "output" queue then becomes the "input" queue for the next component in the chain. Pretty basic.
I am now trying to roll up my sleeves and provide unit testing for each component using the MockEndpoints provided by Camel's test API. I have been pouring over the javadocs and the few examples on Camel's website, but am having difficulty connecting all the dots.
It seems to me that, for each component, a portion of my unit testing is going to want to accomplish the following three things:
Test to see if there are messages waiting on a particular "input" queue
Pull those messages down and process them
Push new messages to an "output" queue and verify that they made it there
I believe I need to create MockEndpoints for each queue like so:
#EndpointInject(uri = "mock:inputQueue")
protected MockEndpoint intputQueue;
#EndpointInject(uri = "mock:outputQueue")
protected MockEndpoint outputQueue;
So now, in my JUnit test methods, I can set up expectations and interact with these endpoints:
#Test
public final void processMethodShouldSendToOutputQueue()
{
Component comp = new Component();
comp.process();
outputQueue.assertIsSatisfied();
}
I'm just not understanding how to wire everything up correctly:
How do I connect comp to the inputQueue and outputQueue MockEndpoints?
For each MockEndpoint, how do I set up expectations so that assertIsSatisfied() checks that a message is present inside a particular queue, or that a particular queue contains messages?
Adam, there are several ways to do this.
For POJO components, blackbox test them separately from any Camel context/routing to focus on business logic.
If you want to do end-to-end testing of the routes, consider using one of these approaches to validate that each step in the route is satisfied.
use NotifyBuilder to build Exchange validation expressions (somewhat complex to get your head around)
use AdviceWith to dynamically change the route before its run (add Log/Mock endpoints, etc)
I prefer AdviceWith because its very flexible and leverages the familiar MockEndpoints. For a complete example of this, see this unit test
In short, you will create a unit test to inject MockEndpoints into your route and then validate against them as usual...
context.getRouteDefinition("myRouteId").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
// mock all endpoints
mockEndpoints();
}
});
getMockEndpoint("mock:direct:start").expectedBodiesReceived("Hello World");
template.sendBody("direct:start", "Hello World");