Camel onException doesn't catch NoMessageIdException of idempotentConsumer? - apache-camel

Example route:
onException(Exception.class)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println("it works");
}
})
.handled(true);
from("jetty://http://0.0.0.0:8888/test")
.idempotentConsumer(header("myid"), MemoryIdempotentRepository.memoryIdempotentRepository(1000000))
.skipDuplicate(false)
.filter(property(Exchange.DUPLICATE_MESSAGE).isEqualTo(true))
.throwException(new DuplicateRequestException())
.end();
Sending a request to the listener URL without myid parameter throws org.apache.camel.processor.idempotent.NoMessageIdException: No message ID could be found using expression: header(myid) on message exchange: Exchange[Message: [Body is instance of org.apache.camel.StreamCache]]
without ever passing from onException.

Yes this is in fact a bug in Apache Camel. I have logged a ticket to get this fixed in the next releases.
https://issues.apache.org/jira/browse/CAMEL-7990

Related

Exception from recipientList EIP of camel not caught at route level

Camel Version 2.22.0
Runtime: SpringBoot : 2.0.2.RELEASE
JDK version: 1.8.0_121
EIP: recipientList.
Problem: Exception raised from parallel process of recipientList is not caught at route level onException clause.
Below is the DSL
#Override
public void configure() throws Exception {
restConfiguration().clientRequestValidation(true)
//.contextPath("/pss/v1.0/")
.port("8080").host("0.0.0.0")
.enableCORS(true)
.apiContextPath("/api-doc")
.apiProperty("api.title", "Test REST API")
.apiProperty("api.version", "v1")
.apiContextRouteId("doc-api")
.component("servlet")
.bindingMode(RestBindingMode.json);
rest("/api/").clientRequestValidation(true)
.id("api-route")
.consumes("application/json")
.get("/bean/{name}")
.bindingMode(RestBindingMode.json)
.to("direct:remoteService");
from("direct:remoteService")
.onException(Exception.class).handled(true)
.log("Exception Caught : ${exception.message}")
.end()
.recipientList(constant("direct:route1, direct:route2"), ",").parallelProcessing().aggregationStrategy(new GroupedBodyAggregationStrategy())
.stopOnException()
.end()
.log("The final Exchange data : ${exception.message}");
from("direct:route1")
.setHeader( Exchange.CONTENT_ENCODING, simple("gzip"))
.setBody(simple("RESPONSE - [ { \"id\" : \"bf383eotal length is 16250]]"))
.log("${body}");
from("direct:route2")
.log("${body}")
.process(e-> {
List<String> myList = new ArrayList();
myList.add("A");
myList.add("b");
myList.add("C");
e.getIn().setBody(myList);
})
.split(body())
.parallelProcessing(true)
.aggregationStrategy(new GroupedBodyAggregationStrategy())
.stopOnException()
.log("${body}")
.choice()
.when(simple("${body} == 'b'"))
.throwException(new Exception("jsdhfjkASDf"));
}
Try make onException as global like this:
onException(Exception.class).handled(true)
.log("Exception Caught : ${exception.message}")
.end();
from("direct:remoteService")
.recipientList(constant("direct:route1, direct:route2"), ",").parallelProcessing().aggregationStrategy(new GroupedBodyAggregationStrategy())
.stopOnException()
.end()
.log("The final Exchange data : ${exception.message}")
;
UPD: So you need to disable error handlers in recipient routes. Try like this (can't insert normally code sample)
That's a classical mistake: (exactly like the split EIP) each recipient will process a copy of the original Exchange. Any failure on these copies will not affect (raise an exception on) the route processing the master Exchange, as every single exchange runs in a completely separate unit of work.
If you enable the "shareUnitOfWork" option (on the recipientList), exceptions should be propagated.

camel splitter - stop looping on specific exception

How can we stop looping on camel splitter on specific exception?
The "stopOnException()" is stopping the looping for every exception, but instead I want to stop looping only on some specific exceptions. And if the exception is "HttpOperationFailedException", I want to stop looping based on response code.
For example if response code is "500" stop execution and if response code is 404 continue execution.
Is it possible?
Original Question
from("timer:categoryRouter?delay=0")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getIn().setBody("A,F,B,D,C");
}
})
// tell Splitter to use the aggregation strategy which handles and ignores exceptions
.split(body(), new MyIgnoreFailureAggregationStrategy())
.stopOnException()
// log each splitted message
.log("Split line ${body}")
// and have them translated into a quote
.bean(WordTranslateBean.class)
// and send it to a mock
.to("mock:split")
.end()
// log the outgoing aggregated message
.log("Aggregated ${body}")
// and send it to a mock as well
.to("mock:result");
Bean which throws exception:
public class WordTranslateBean {
private Map<String, String> words = new HashMap<String, String>();
public WordTranslateBean() {
words.put("A", "Camel rocks");
words.put("B", "Hi mom");
words.put("C", "Yes it works");
}
public String translate(String key) throws HttpOperationFailedException {
if (!words.containsKey(key)) {
HttpOperationFailedException httpOperationFailedException = null;
if(key.equals("F")) {
httpOperationFailedException = new HttpOperationFailedException("uri",500,"Internal Server Error","location",null,"Key not a known word " + key);
}
else {
httpOperationFailedException = new HttpOperationFailedException("uri",404,"Resource Not Found","location",null,"Operation not supported on word " + key);
}
throw httpOperationFailedException;
}
return words.get(key);
}
}
Working Solution:
from("timer:categoryRouter?delay=0")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getIn().setBody("A,F,B,D,C");
}
})
// tell Splitter to use the aggregation strategy which handles and ignores exceptions
.split(body(), new MyIgnoreFailureAggregationStrategy())
.stopOnException()
// log each splitted message
.log("Split line ${body}")
// and have them translated into a quote
.doTry()
.bean(WordTranslateBean.class)
// and send it to a mock
.to("mock:split")
.doCatch(HttpOperationFailedException.class)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
HttpOperationFailedException e = (HttpOperationFailedException) exchange.getProperty(Exchange.EXCEPTION_CAUGHT);
if(e.getStatusCode()!=404){
throw e;
}
}
})
.end()
.end()
// log the outgoing aggregated message
.log("Aggregated ${body}")
// and send it to a mock as well
.to("mock:result");
Why don't you throw a custom exception based on a response code ? That's one option . Basically you can catch the original http exception , check the response code , throw your custom exception. Can you post your route ? It's easy to implement this way, just want to see how you have organised your routes .
Basically we still need to use "stopOnException" to stop the splitter when exception occurred. But to control on which exception the splitter should break, you can use "doTry..doCatch" block and in the respective catch block throw back the exception again.
from("timer:categoryRouter?delay=0")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getIn().setBody("A,F,B,D,C");
}
})
// tell Splitter to use the aggregation strategy which handles and ignores exceptions
.split(body(), new MyIgnoreFailureAggregationStrategy())
// log each splitted message
.log("Split line ${body}")
// and have them translated into a quote
.doTry()
.bean(WordTranslateBean.class)
// and send it to a mock
.to("mock:split")
.doCatch(HttpOperationFailedException.class)
.log("Ignore Exception")
.doCatch(IOException.class)
.throwException(new IOException())
.doCatch(UnsupportedOperationException.class)
.log("Ignore Exception")
.end()
.end()
// log the outgoing aggregated message
.log("Aggregated ${body}")
// and send it to a mock as well
.to("mock:result");
If the exception is related to http and want to inspect the response code to act accordingly then you can my question which has the working solution.
You can catch the exceptions and decide what to do with them. Inside of your splitter:
<doTry>
<!-- Your Splitter logic here -->
<doCatch>
<exception>java.lang.IllegalStateException</exception>
<log message="This exception happened here, but not a problem.."/>
</doCatch>
<doCatch>
<exception>java.io.IOException</exception>
<log message="Big problem here. STOPPING.."/>
<stop/>
</doCatch>
<doFinally>
<to uri="mock:finally"/>
</doFinally>
</doTry>

Camel Splitter Aggregator requires dummy statement

When using split/aggregator in camel (2.14) the following throws an exception:
#Override
public void configure() throws Exception {
from("timer://foo?repeatCount=1")
.bean(someBean)
.split(body(), new MyAgg())
.end()
.bean(printBean);
}
Exception in thread "main" org.apache.camel.FailedToCreateRouteException: Failed to create route route1 at: >>> Split[{body} -> []] <<< in route: Route(route1)[[From[timer://foo?repeatCount=1]] -> [Bean[org... because of Definition has no children on Split[{body} -> []]
Whereas the following works fine:
#Override
public void configure() throws Exception {
from("timer://foo?repeatCount=1")
.bean(someBean)
.split(body(), new MyAgg())
.log("blah blah")
.end()
.bean(printBean);
}
someBean sets a List of String in the exchange. printBean prints out the exchange at the end. MyAgg appends the string from exchanges.
The log statement in the second option seems redundant. Is there an option to make the first option work?

Apache Camel - Dead Letter Channel - enrich message

I'm using deadLetterChannel to take care of exceptions and send them to the error queue.
errorHandler(deadLetterChannel(QUEUE_ERROR).maximumRedeliveries(3).redeliveryDelay(2000));
Is it possible to enrich the message with additional message headers? Or do i have to use onException for it?
You can use onRedelivery and with a processor to add headers before redelivering
errorHandler(deadLetterChannel(QUEUE_ERROR).maximumRedeliveries(3).redeliveryDelay(2000).onRedelivery(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
//add headers here
}
}));

Mocking Camel http endpoint with resource stream in camel

I have a route like following
from(direct:start)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.setProperty("doc_url", "http://myhost:5984/test/record/doc.csv");
}
}).setHeader(Exchange.HTTP_METHOD, constant("GET"))
.convertBodyTo(String.class)
.recipientList(header("doc_url")
.split().streaming.process(new MyProcessor());
I don't want to run apache couchdb every time for testing. I want to make this http endpoint refer to resource file in the codebase. How to write this?
you can use the Camel AdviceWith feature to intercept/replace endpoints for testing...
camelContext.getRouteDefinition("myRouteId")
.adviceWith(camelContext, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception
{
interceptSendToEndpoint("couchdb:http://localhost/database)
.skipSendToOriginalEndpoint()
.to("http://localhost:5984/test/record/doc.csv");
}
});

Resources