Camel - onException & redelivery - apache-camel

I am trying to implement an error handler & re-oauth mechanism for a Route which performs a GraphQL API call. However, I seem to be misunderstanding the usage of the onException handler and can't seem to get it to work.
Here's the scenario I am trying to cover:
Execute a Route which performs a GraphQL API call (including the setting of variables etc.). The API call itself will always respond with a HTTP 200.
Once the response is received, check the response payload for any error records
If errors exist with a code 401, throw an AuthorizationException
The onException handler catches the exception and is supposed to:
trigger the oauth route to refresh the token (updating the exchangeProperty)
re-run the Route (re-executing the GraphQL call)
After reaching the configured maximumRedeliveries in the onException handler, fail the execution.
Here's the code I have so far:
onException(AuthorizationException.class)
.handled(true)
.redeliveryDelay(1000)
.maximumRedeliveries(3)
.useOriginalMessage()
.to(ROUTE_OAUTH_URI) // updates the token (?)
.to(ROUTE_QUERY_URI) // re-executes the route (?)
;
from(ROUTE_QUERY_URI)
.process(exchange -> {
...set variables
})
.toD("graphql://...")
.setProperty("graphqlResponseErrors").jsonpath("$.errors.length()", true)
.choice()
.when(exchangeProperty("graphqlResponseErrors").isNotEqualTo(0))
.setProperty("graphqlResponseCode").jsonpath("$.errors[0].code", true)
.choice()
.when(exchangeProperty("graphqlResponseCode").isEqualTo(401))
.throwException(new AuthorizationException(
"Authorization exception"))
.end()
.end();
I have tried a few different combinations, including onRedeliver instead of the oauth route, remove the useOriginalMessage and re-trigger the route again but I couldn't get it to work.
Any help or hints would be much appreciated. Thanks!
Update:
Adding the below code to the onException, does retrieve and set a new token, however now the route seems go into an infinite loop:
.onRedelivery(exchange -> {
ProducerTemplate producerTemplate = exchange.getContext().createProducerTemplate();
Exchange oauthExchange = producerTemplate.send(ROUTE_OAUTH_URI, exchange);
exchange.setProperty("accessToken", oauthExchange.getProperty("accessToken"));
});

Related

Camel, end rest-based route, returning from choice in loop

I'm trying to add error handling to my parallel processing:
...
.multicast(new GroupedMessageAggregationStrategy())
.parallelProcessing()
.to("direct:getAndSaveRoute1")
.to("direct:getAndSaveRoute2")
.end()
.split(body())
.choice()
.when(simple("${body.errorOcurred} == true"))
//TODO:: end route returning current body
.endChoice()
.otherwise()
.log(...)
.endChoice()
.end()
//after split, if no error occurred
.to("direct:nextRoute")
.end()
I can't seem to figure out though how to return/ end the route (and pass back the current body as the rest response body) within the choice in the split. end() and endRest() seem to cause issues...
It is also not clear as how many end()s I need; Adding an end() for the split causes an exception and makes Spring fail to boot.
For those in the future, I ended up making a bean to turn the list into a single message, and then do a choice based on that.
Not very 'camel', but needed to be wrapped up

Having access to entire soap:body when service call returns soap:fault

I'm building a route that sends a SOAP request to a webservice. For achieving that, I wrote this code.
.doTry()
.inOut(getEndpointDocumentWS())
.log("Response WHEN OKAY: ${body}")
.process(Document_WS_REPLY_PROCESSOR)
.endDoTry()
.doCatch(Exception.class)
.log(LoggingLevel.INFO, "SOAP REPLY WITH FAULTMESSAGE")
.log("Response ON ERROR FAULT: ${body}")
.process(Document_WS_REPLY_ERROR_PROCESSOR)
.end();
Everything goes as planned when the service response is "okay". Otherwise, when the service response is a soap:Fault, I'm not having access to all of the response (I am using soapUI to mock the soap:Fault response).
I can access a tiny fraction of the soap:fault by getting the EXCEPTION_CAUGHT property.
The instruction
.log("Response ON ERROR FAULT: ${body}")
Has no data at all.
What can I do differently to have access to all the instead of only the faultstring?
Exception exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT,
Exception.class);
According to this answer, Camel's CXF component not catching onException(Exception.class):
Camel's onException only triggeres if there is an exception. A SOAP
Fault is represented as a Message with the fault flag = true.
What you can do is to set handleFault=true on CamelContext, then it
will turn SOAP fault messages into an exception that the onException
can react upon.
Assuming you have not configured handleFault=true, then it's odd that your exception handler is running at all. It could be that some other exception, not the Fault you're looking for, is occurring which causes the exception handler to run.
If you have already configured handleFault=true, I don't have any advice except inspect the data objects in a debugger to see if you can find out what's going on. (If you don't know, to do this you can add a Processor to your exception handler and insert a break point inside the Processor. Break points don't work in route definition because route definitions are only run on initialization.)

Apache Camel: Unable to get the Exception Body

Whenever there is normal flow in my Camel Routes I am able to get the body in the next component. But whenever there is an exception(Http 401 or 500) I am unable to get the exception body. I just get a java exception in my server logs.
I have also tried onException().. Using that the flow goes into it on error, but still I do not get the error response body that was sent by the web service(which I get when using POSTMAN directly), I only get the request in the body that I had sent to the web service.
Also adding the route:
from("direct:contractUpdateAds")
.to("log:inside_direct:contractUpdateAds_route_CompleteLog?level=INFO&showAll=true&multiline=true")
.streamCaching()
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.log("before calling ADS for ContractUpdate:\nBody:${body}")
.to("{{AdsContractUpdateEndpoint}}")
.log("after calling ADS for ContractUpdate:\nBody:${body}")
.convertBodyTo(String.class)
.end();
Option 1: handle failure status codes yourself
The throwExceptionOnFailure=false endpoint option (available at least for camel-http and camel-http4 endpoints) is probably what you want. With this option, camel-http will no longer consider an HTTP Status >= 300 as an error, and will let you decide what to do - including processing the response body however you see fit.
Something along those lines should work :
from("...")
.to("http://{{hostName}}?throwExceptionOnFailure=false")
.choice()
.when(header(Exchange.HTTP_RESPONSE_CODE).isLessThan(300))
// HTTP status < 300
.to("...")
.otherwise()
// HTTP status >= 300 : would throw an exception if we had "throwExceptionOnFailure=true"
.log("Error response: ${body}")
.to("...");
This is an interesting approach if you want to have special handling for certains status codes for example. Note that the logic can be reused in several routes by using direct endpoints, just like any other piece of Camel route logic.
Option 2 : Access the HttpOperationFailedException in the onException
If you want to keep the default error handling, but you want to access the response body in the exception handling code for some reason, you just need to access the responseBody property on the HttpOperationFailedException.
Here's an example:
onException(HttpOperationFailedException.class)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
// e won't be null because we only catch HttpOperationFailedException;
// otherwise, we'd need to check for null.
final HttpOperationFailedException e =
exchange.getProperty(Exchange.EXCEPTION_CAUGHT, HttpOperationFailedException.class);
// Do something with the responseBody
final String responseBody = e.getResponseBody();
}
});

Handle exception from load balancer only once (when failover is exhausted)

My code consumes jms queue and through lb redirects to external http client.
I need to log original message for every failed delivery to local directory.
Problem is that onException is caught by each failover.
Is there any way how to achieve this?
Pseudo code:
onException(Exception.class).useOriginalMessage()
.setHeader(...)
.to("file...")
.setHeader(...)
.to("file...")
from("activemq...")
.process(...)
.loadBalance().failover(...)
.to("lb-route1")
.to("lb-route2")
.end()
.process()
.to("file...")
from("lb-route1")
.recipientList("dynamic url")
.end()
from("lb-route2")
.recipientList("dynamic url")
.end()
I have not tested this logic with multiple to statements, but when I have my list of endpoints in an array this logic functions just fine. I will attempt to deliver to the first endpoint, if I cannot I will attempt to deliver this to the second endpoint. If an exception occurs at the second endpoint it will propagate back to the route's error handler. If you need it to round robin instead, change the last false on the failover statement to a true.
String[] endpointList = {"direct:end1", "direct:end2"};
from("direct:start")
.loadbalance().failover(endpointList.length-1, false, false)
.to(endpointList);

Camel Reslet Component with async processing

I have a requirement which is as follows:
Accept HTTP POST requests containing XML to a certain URL.
Perform pre-requisite actions such as saving the request XML to a file.
Validate the incoming XML matches the corresponding schema.
If the schema validation fails, synchronously respond with a HTTP 400 response code.
If the schema validation passes, synchronously respond with a HTTP 200 response code.
Pass the XML message on for further processing.
When this further processing completes, asynchronously respond to the caller with a HTTP 200 response code.
This is currently how I have the route configured:
onException(IOException.class)
.log(LoggingLevel.INFO, "Schema validation error on incoming message: ${id}")
.handled(true)
.maximumRedeliveries(0)
.process(schemaValidationErrorProcessor);
from("restlet:http://localhost:" + portNum + "/api/XX/XXX?restletMethod=POST")
.log(LoggingLevel.INFO, "Received message")
.convertBodyTo(String.class)
.multicast()
.parallelProcessing()
.to(SAVE_REQUEST_TO_FILE_QUEUE, PROCESS_PROVISIONING_REQUEST_QUEUE);
from(SAVE_REQUEST_TO_FILE_QUEUE)
.log(LoggingLevel.INFO, "Storing message: ${id}")
.to("file://" + requestLogFolder);
from(PROCESS_PROVISIONING_REQUEST_QUEUE)
.log(LoggingLevel.INFO, "Processing provisioning request: ${id}")
.process(requestGate)
.choice()
.when(header(SYSTEM_STATUS_HEADER).isEqualTo(true))
.unmarshal(xmlParser)
.inOnly("bean:requestHandler?method=handle")
.when(header(SYSTEM_STATUS_HEADER).isEqualTo(false))
.log(LoggingLevel.INFO, "Intentially dropping message")
.endChoice();
The schema validation part is achieved via the .unmarshal(xmlParser) line (I have a JaxbDataFormat object configured elsewhere with the schema set in that). When schema validation fails, an IOException is thrown and this is handled by my schemaValidationErrorProcessor which adds the HTTP 400 to the response.
That is all working fine.
The problem I am having is passing the XML message on for further processing. Basically, I need this to be done asynchronously because when the schema validation passes I need to synchronously respond with a 200 response. The processing that I need to do is in the .inOnly("bean:requestHandler?method=handle") line.
I naively thought that setting the routing to my bean to inOnly would set this to be asynchronous and that main route would not wait for a response. However, this is not the case as when the requestHandler.handle method throws an exception, this is thrown back to the caller of the REST endpoint. I don't want this to happen as I want all of this processing to be done in 'the background' as the consumer will have already received a 200 response.
So, my question is, how would I go about achieving such behaviour? I have thought about using queues etc but ideally would like to avoid such components if possible.
Use Camel Websocket component for asynchronously respond to the caller.
From the Camel documentation:
from("activemq:topic:newsTopic")
.routeId("fromJMStoWebSocket")
.to("websocket://localhost:8443/newsTopic?sendToAll=true&staticResources=classpath:webapp");

Resources