Camel - Split() and doCatch(....) does not work - apache-camel

I'm trying to build a route that tries to validate an xml and if everything is correct then has to split this file otherwise an exception is thrown and it has to do something else. So I did the following:
from("file:"+fileOutboxTransformed+"?preMove=inprogress&move="+backupFolderTransformed+"/"+labelMessageType+"_${date:now:yyyyMMddHHmmssSSS}-${file:name.noext}.${file:ext}")
.log(LoggingLevel.INFO, "Got transformed file and sending it to jms queue: "+queue)
.doTry()
.to("validator:classpath:"+validator)
.split(xPathMessageTypeSplit)
.to("jms:"+queue+"?jmsMessageType=Text")
.doCatch(ValidationException.class)
.log(LoggingLevel.INFO, "Validation Exception for message ${body}")
.to("xslt:classpath:"+transformationsErrorAfter)
.split(xPathNotificationSplit)
.to("file:"+fileOutboxInvalid+"?fileName=${file:name.noext}-${date:now:yyyyMMddHHmmssSSS}.err2")
.end();
But it does not compile (if I do not use the split then it compiles and works) and the error is:
The method doCatch(Class<ValidationException>) is undefined for the type ExpressionNode
So I tried the following
from("file:"+fileOutboxTransformed+"?preMove=inprogress&move="+backupFolderTransformed+"/"+labelMessageType+"_${date:now:yyyyMMddHHmmssSSS}-${file:name.noext}.${file:ext}")
.log(LoggingLevel.INFO, "Got transformed file and sending it to jms queue: "+queue)
.doTry()
.to("direct:validate")
.doCatch(ValidationException.class)
.log(LoggingLevel.INFO, "Validation Exception for message ${body}")
.to("xslt:classpath:"+transformationsErrorAfter)
.split(xPathNotificationSplit)
.to("file:"+fileOutboxInvalid+"?fileName=${file:name.noext}-${date:now:yyyyMMddHHmmssSSS}.err2")
.end();
from("direct:validate")
.to("validator:classpath:"+validator)
.to("direct:split_message");
from("direct:split_message")
.split(xPathMessageTypeSplit)
.to("jms:"+queue+"?jmsMessageType=Text");
This time I get the error of duplicate endpoint
org.apache.camel.FailedToStartRouteException: Failed to start route route312 because of Multiple consumers for the same endpoint is not allowed: Endpoint[direct://validate]
do you have any idea on how to solve this problem?

In order to get back to the doTry() block from a split() block (or choice or other nested type), you need to use the endDoTry(). Contrary to its name, this method will end the nested split block and return back to the doTry() DSL.
I'd use the first route you posted with these changes:
from("file:"+fileOutboxTransformed+"?preMove=inprogress&move="+backupFolderTransformed+"/"+labelMessageType+"_${date:now:yyyyMMddHHmmssSSS}-${file:name.noext}.${file:ext}")
.log(LoggingLevel.INFO, "Got transformed file and sending it to jms queue: "+queue)
.doTry()
.to("validator:classpath:"+validator)
.split(xPathMessageTypeSplit)
.to("jms:"+queue+"?jmsMessageType=Text")
.endDoTry()
.doCatch(ValidationException.class)
.log(LoggingLevel.INFO, "Validation Exception for message ${body}")
.to("xslt:classpath:"+transformationsErrorAfter)
.split(xPathNotificationSplit)
.to("file:"+fileOutboxInvalid+"?fileName=${file:name.noext}-${date:now:yyyyMMddHHmmssSSS}.err2")
.endDoTry()
.end();

Your second try seems fine. The error you're getting is caused by two routes beginning with from("direct:validate")
Don't you have an other route in your application consuming from the same endpoint?
Edit : Try to name it differently, maybe validate exists already (in your app or inside camel)

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.)

Camel - Stop processing a route on exception

I have a camel route which processes hl7 messages. After the processing succeeds I would like to pass it to another endpoint b to do further processing.
But if any exception happens in processHL7, I have a catch block which does some processing. I want to stop processing when I go into the doCatch and encounter end, but this is not happening. Whatever happens the flow is going to endpoint b. How do i stop when I go into the doCatch block?
from("direct:a")
.doTry()
.to("bean:processHL7?method=process")
.doCatch(HL7Exception.class)
.to("direct:ErrorACK")
.transform(ack())
.end()
.transform(ack())
.to("direct:b");
This should work.
from("direct:a")
.doTry()
.to("bean:processHL7?method=process")
.doCatch(HL7Exception.class)
.to("direct:ErrorACK")
.transform(ack())
.stop()
.end()
.transform(ack())
.to("direct:b");
You can use stop() to stop the route from further processing.

apache camel idempotent consumer key removal

I have a setup like below.
The issue I have is that the key from the Idempotent Repository is not being removed when an exception is thrown (on line stated below) when using seda component within OnException. When I change to use direct within the OnException the key is removed from the cache. On both trials the email is also being sent correctly.
My queries are:
why is the key not being removed from the repository cache when using seda within the OnException?
is there an issue with using seda within the OnException?
Here is the routes:
MyRouteClass1
onException(Exception.class)
.setHeader("subjectText", simple("failure email!"))
.to("seda:notifySupportOnFailure")
.end();
from("direct:findWorkItems")
.bean(someService, "findWorkItems")
.split(body())
.throttle(1).timePeriodMillis(5000L)
.to("direct:handleWorkItem")
.choice().when(header("resultId").isGreaterThan(0))
.bean(someService, "updateWorkItemToHandled")
.end();
from("direct:handleWorkItem")
.idempotentConsumer(simple("${body.workItemId}"), duplicatesRepo)
.bean(someService, "handleWorkItem") // e.g. exception would be thrown within here
.setHeader("resultId", body())
.end();
MyRouteClass2
from("seda:notifySupportOnFailure")
.setHeader("from", simple("sender#mail.com"))
.setHeader("to", simple("recipient#mail.com"))
.setBody(simple("Failure:\n ${exception.message} \n\ndetail: \n${body}"))
.to("smtp://localhost")
.log("Failure email now sent.")
.end();
I have also attempted another workaround. I have modified the onException clause as per below. This does allow the key to be removed from the Repository when the exception is thrown. I am wondering if this is a correct approach ?
onException(Exception.class)
.setHeader("subjectText", simple("failure email!"))
.multicast().to("seda:notifySupportOnFailure").end()
.end();
A work around would be to put the idempotentConsumer call in your parent route 'findWorkItems' and setting the completionEager flag on it to true.
onException(Exception.class)
.setHeader("subjectText", simple("failure email!"))
.to("seda:notifySupportOnFailure")
.end();
from("direct:findWorkItems")
.idempotentConsumer(simple("${body.workItemId}"), duplicatesRepo).completionEager(true)
.bean(someService, "findWorkItems")
.split(body())
.throttle(1).timePeriodMillis(5000L)
.to("direct:handleWorkItem")
.choice().when(header("resultId").isGreaterThan(0))
.bean(someService, "updateWorkItemToHandled")
.end();
from("direct:handleWorkItem")
.bean(someService, "handleWorkItem") // e.g. exception would be thrown within here
.setHeader("resultId", body())
.end();

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