Camel onCompletion has null header - java

I have a Camel route with an onCompletion() which then hits a Processor. Within this processor it gets a header from the Exchange but this header comes back null.
I know that onCompletion() runs at the end of that particular route but surely the Exchange headers should still be valid and usable. inputLocation below is defined higher up in the class and works for previous routes.
from("file://"+inputLocation+"?initialDelay=5000&delay=2000&recursive=true&delete=true")
.onCompletion()
.process(storedProcProcessor())
.end()
.choice()
.when(appContext.getBean(AppPredicate.class))
.log("Need to check against APP in the database for destination.")
.setHeader(AppConstants.INPUTLOCATION, simple(inputLocation))
.process(databaseProcessor())
.endChoice();

I checked with:
#Override
public void configure() {
from("direct:start")
.onCompletion()
.process(new Processor() {
#Override
public void process(final Exchange exchange) throws Exception {
LOG.info("Hello, {}", exchange.getIn().getHeader("myHeader"));
}
})
.end()
.setHeader("myHeader").constant("World!");
}
}
This prints
Hello, World!
Thus, the header myHeader is still available in onCompletion. So, I guess that your header is never properly set?

Related

Apache camel onException adding error details to the original message

I have added this exception handling to the camel route.
.onException(BeanCreationException.class, ValidationException.class)
.handled(true)
.process(new OnExceptionProcessor())
.to("errorQueue0").id("errorQueue")
.end()
public class OnExceptionProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
Exception cause = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
exchange.getIn().setHeader("FailedBecause", cause.getMessage());
}
}
When I read this message back from the error queue, I cannot find this header.
any idea on how to add error details along with the original message to the error queue
This could be a context problem because you are in a processor that is called by the error handler.
As an alternative, you could return the String value to set in the header from your processor method. By the way, this also improves the testability of your Processor.
Then you can use this return value to set the header in the error handler route directly.
.setHeader("FailedBecause", method(new OnExceptionProcessor()))

Sending Camel HTTP Post custom body

I am new to Apache camel. I am trying to create routes to call multiple rest APIs and aggregate the response into one.
But for some reason, the JSON request that I am creating does not reach the rest endpoint.
During debug, I see that Exchange objects do have the values that I have set and get converted into byte array and on the other side, rest API recieves empty objects.
I am working on a Spring boot project and I have tried different ways of marshalling the request to JSON including Gson and Jackson. None of which seem to work.
Please assist.
from("direct:oneResponse")
.multicast(new MyAggregationStrategy()).parallelProcessing()
.to("direct:rest1call", "direct:rest2call")
.end();
from("direct:rest1call")
.routeId("rest1call")
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader("Content-Type", constant("application/json"))
.setHeader("Accept", constant("application/json"))
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getIn().setBody(<<valid json>>); //json values as required for the rest call.
}
})
.to("http4://localhost:5555/mock/rest1call")
.setProperty("route", simple("routeId"))
.unmarshal(new JacksonDataFormat(Rest1Response.class));
from("direct:rest2call")
.routeId("rest2call")
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader("Content-Type", constant("application/json"))
.setHeader("Accept", constant("application/json"))
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getIn().setBody(<<valid json>>); //json values as required for the rest call.
}
})
.to("http4://localhost:5555/mock/rest2call")
.setProperty("route", simple("routeId"))
.unmarshal(new JacksonDataFormat(Rest2Response.class));
Can you try to create a processor and specify all the headers and body in it?
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getOut().setHeader(Exchange.HTTP_METHOD, HttpMethod.POST);
exchange.getOut().setHeader(Exchange.CONTENT_TYPE, "application/json");
exchange.getOut().setHeader("Accept", "application/json");
/* this is one way, string representation of json, but maybe you can try to build Model and fill that model with data */
exchange.getIn().setBody(<<valid json>>); //json values as required for the rest call.
}
})
if you decide to go with models, use marshaling after processor just to be sure your data is converted to JSON.
.marshal(yourDataFormat)
Try GsonDataFormat it works pretty good for me.

camel route http response xml parsing

I am new to using camel and I need to build a route in JAVA to process some xml returned by http request. I tried to parse the body of the response by setting up a route with a processor and log it to a file setting the consumer as http url but it didn't work. Then I tried to set up a jms queue to grab and process it from the queue with similar result.
I think I am getting the 200 response but the producer text file I set it to write to is not working and the log4j in DEBUG is not too informative on isolating the issue. Does anyone have any insight on this issue to point me in the right camel direction? thanks in advance!
public static void main(String[] args) throws Exception {
CamelContext camelContext = new DefaultCamelContext();
// connect to embedded ActiveMQ JMS broker
ConnectionFactory connectionFactory =
new ActiveMQConnectionFactory("vm://localhost");
camelContext.addComponent("jms",
JmsComponent.jmsComponentAutoAcknowledge(connectionFactory));
try {
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start")
.to("http://tomcatappurl:8080/format=xml?bridgeEndpoint=true")
.process(new OrderProcessor())
.to("log:DEBUG?showBody=true&showHeaders=true")
.log("file:C:/Desktop/camellog1.txt")
.to("log:DEBUG?showBody=true&showHeaders=true")
.log("${headers}")
.convertBodyTo(String.class)
.to("file:C:/Desktop/camellog1.txt")
.log("${in.headers}")
.to("stream:out")
.to("jms");
from("jms:incomingOrders")
.process(new Processor() {
public void process (Exchange exchange) throws Exception {
//HttpServletRequest request = exchange.getIn().getBody(HttpServletRequest.class);
System.out.println("Response received from Google, is streamCaching = " + exchange.getContext().isStreamCaching());
System.out.println("----------------------------------------------IN MESSAGE--------------------------------------------------------------");
System.out.println(exchange.getIn().getBody(String.class));
System.out.println("----------------------------------------------OUT MESSAGE--------------------------------------------------------------");
//System.out.println(exchange.getOut().getBody(String.class)); //Activating this line causes empty response on browser
}
})
.to("file:C:/Users/Desktop/camellog1.txt?fileExist=Append");
}
});
camelContext.start();
} finally {
camelContext.stop();
}
}
I think your routes are not running.
You need something (like a timer) to trigger your routes, for example:
from("timer:myTimer?period=30s")
.to("direct:start");
Camel documentation for Direct component says:
The direct: component provides direct, synchronous invocation of any consumers when a producer sends a message exchange.
So you need something else to start the invocation of the route.
Mind that your first route must finish to the correct JMS queue:
// ... cut
.to("file:C:/Desktop/camellog1.txt")
.log("${in.headers}")
.to("stream:out")
.to("jms:incomingOrders");
without the queue name it will not work.

Camel: getting EndpointUri of a nested route

I have route A that calls route B:
from("direct:a")
.to("direct:b");
from("direct:b")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception
{
System.out.println("Route B URI: " + exchange.getUnitOfWork().getRouteContext().getFrom().getEndpointUri()); //oops, prints direct:a
}
});
I would like the nested route B to print out its own URI, not the URI of the encapsulating route A. How could I do it?
Afaik, I don't think it's possible as is.
The UnitOfWorks keep a Stack of RouteContext, but this stack is not publicly accessible. However, you can access a history of the processor in which the exchange has been routed. This collection of MessageHistory is located in an header Exchange.MESSAGE_HISTORY. It contains the id of the processor and (sanitized) uri of the endpoints.
A more simple/robust approach can be to add explicitly a header on each route invoking the route B.
Personally, I don't think it's a good idea to depend on such information // internal details of the route!
Add a routeId() instruction to your route and use that to access your route definition:
from("direct:b")
.routeId("routeB")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println("Route B URI: " + exchange.getContext().getRoute("routeb").getFrom().getEndpointUri());
}
});

Getting exact response from combined routes

I have a dynamic route creator web application. According to flow design, I produce a camel route. Route may contain multicast, filter, aggregate, processor etc. After designing flow through the UI, my route has been created like this:
from("seda:start").routeId("idx")
.multicast()
.to("direct:a", "direct:b", "direct:c")
.parallelProcessing()
.end();
from("direct:a").transform(constant("A")).delay(1000).to("direct:merge");
from("direct:b").transform(constant("B")).delay(2000).to("direct:merge");
from("direct:c").transform(constant("C")).delay(3000).to("direct:merge");
from("direct:merge")
.aggregate(new MyAggregationStrategy()).constant(true).completionSize(3)
.to("mock:end");
I have an API to give result of this route to the users. When I execute this route with InOut MEP, response is 'C' but mock:end is satisfied with 'ABC':
MockEndpoint mock = getMockEndpoint("mock:end");
mock.expectedBodiesReceived("ABC"); //works as expected
String reply = template.requestBody("seda:start", "", String.class);
assertEquals("ABC", reply); //it returns 'C', but I expect 'ABC'
assertMockEndpointsSatisfied();
How can I change the code to get aggregated result with a synchronous call? Here is the code:
public class ResponseTest extends CamelTestSupport {
#Test
public void testAsyncInOut() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:end");
mock.expectedBodiesReceived("ABC"); //works as expected
String reply = template.requestBody("seda:start", "", String.class);
assertEquals("ABC", reply); //it returns 'C', but I expect 'ABC'
assertMockEndpointsSatisfied();
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("seda:start").routeId("idx")
.multicast()
.to("direct:a", "direct:b", "direct:c")
.parallelProcessing()
.end();
from("direct:a").transform(constant("A")).delay(1000).to("direct:merge");
from("direct:b").transform(constant("B")).delay(2000).to("direct:merge");
from("direct:c").transform(constant("C")).delay(3000).to("direct:merge");
from("direct:merge")
.aggregate(new MyAggregationStrategy()).constant(true).completionSize(3)
.to("mock:end");
}
};
}
class MyAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
if (oldExchange == null) {
// this is the first time so no existing aggregated exchange
return newExchange;
}
// append the new word to the existing
String body = newExchange.getIn().getBody(String.class);
String existing = oldExchange.getIn().getBody(String.class);
oldExchange.getIn().setBody(existing + body);
return oldExchange;
}
}
}
EDIT
Multicasting message to 5 different endpoints doesn't mean that all the messages will be aggregated later. So I cannot use aggregate strategy in multicast definition. Some of them might be used for another kind of job. Flow definition might be like this:
After multicast message to 'a','b','c','d','e' endpoints, 'a' and 'b' can be aggregated together('direct:merge1'), 'c' and 'd' aggregated together('direct:merge2') and 'e' can be used for another thing.
And final aggregator will be aggregate 'direct:merge1' and 'direct:merge2'
to 'direct:merge3'. All these endpoints are created dynamically('direct:a','direct:b','direct:c','direct:d','direct:e','direct:merge1','direct:merge2','direct:merge3'). This scenario will be created like this:
from("seda:start").routeId("idx")
.multicast()
.to("direct:a", "direct:b", "direct:c", "direct:d", "direct:e")
.parallelProcessing()
.end();
from("direct:a").transform(constant("A")).delay(1000).to("direct:merge1");
from("direct:b").transform(constant("B")).delay(2000).to("direct:merge1");
from("direct:c").transform(constant("C")).delay(3000).to("direct:merge2");
from("direct:d").transform(constant("D")).delay(1000).to("direct:merge2");
from("direct:e").transform(constant("E")).delay(1000).to("mock:anywhere");
from("direct:merge1").aggregate(new MyAggregationStrategy()).constant(true).completionSize(2).to("direct:merge3");
from("direct:merge2").aggregate(new MyAggregationStrategy()).constant(true).completionSize(2).to("direct:merge3");
from("direct:merge3").aggregate(new MyAggregationStrategy()).constant(true).completionSize(2).to("mock:end");
When I send message to seda:start, I expect ABDC but I got 'E'. Is there a way to get final aggregated message('ABDC')? Here is the test method:
#Test
public void testAsyncInOut() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:end");
mock.expectedBodiesReceived("ABDC"); //works as expected
String reply = template.requestBody("seda:start", "", String.class);
assertEquals("ABDC", reply); //it returns 'E' because of default multicast behavior, but I expect 'ABDC'
assertMockEndpointsSatisfied();
}
From the multicast documentation:
By default Camel will use the last reply as the outgoing message.
If you want to aggregate the results of the multicast into a single message, you state that in the multicast definition.
#Override
public void configure() throws Exception {
from("seda:start").routeId("idx")
.multicast(new MyAggregationStrategy()) //Put the Aggregation Strategy here!
.to("direct:a", "direct:b", "direct:c")
.parallelProcessing()
.end();
from("direct:a").transform(constant("A")).delay(1000).to("direct:merge");
from("direct:b").transform(constant("B")).delay(2000).to("direct:merge");
from("direct:c").transform(constant("C")).delay(3000).to("direct:merge");
from("direct:merge")
.to("mock:end");
}
Note that your mock endpoint will now be called 3 times, as the aggregation doesn't occur until later. You would need to modify your test accordingly.

Categories

Resources