using camel http handling CachedOutputStream - apache-camel

hi~ i am using camel http component. and i can't extract body message.
here is my code
.log(LoggingLevel.INFO, "ToUri ===> ${body}")
.toD("${body}")
.log(LoggingLevel.INFO, "Result ===> ${body}")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
long startTime = System.currentTimeMillis();
Message inboundMessage = exchange.getIn();
Object body = exchange.getIn().getBody();
String msg = inboundMessage.getBody(String.class);
System.out.println("body:"+body);
System.out.println("getInBody msg:"+msg);
System.out.println("getInBody body:"+body.toString());
=======================================================================
body : org.apache.camel.converter.stream.CachedOutputStream$WrappedInputStream#28936ba4
getInBody msg:
getInBody bodybodybody:org.apache.camel.converter.stream.CachedOutputStream$WrappedInputStream#28936ba4
the log is good works. like this
09:56:53.523 INFO route1 - ToUri ===> https://translation.googleapis.com/language/translate/v2?key=tesetKey&source=en&target=ja&q=hi
09:56:54.545 INFO route1 - Result ===> {
"data": {
"translations": [
{
"translatedText": "こんにちは"
}
]
}
}
i want to extract translatedText using camel.
how can i handle CachedOutputStream and what is this?
i search camel doc.and cant understand.please give me a hint to solve my problem.
thanks.

See stream-caching for information about CachedOutputStream: http://camel.apache.org/stream-caching.html
To get the message body as string from the processor, you just do
String body = exchange.getIn().getBody(String.class);
That will tell Camel that you want the message as a String and it will automatic covert the message body from CachedOutputStream to String. Then you can grab that text you want via regular Java code.
Also note there is jsonpath you can use to work with json data and get information, however its syntax can take a little bit to learn: http://camel.apache.org/jsonpath

You already took the stream data (with .log) before calling processor. Stream data can only fetched once apparently. Try remove the log step and you can get in the processor:
.log(LoggingLevel.INFO, "Result ===> ${body}")
.process(new Processor() {
after spend 2 days experimenting

you can use convertBodyTo(Class<?> type) method for that, like this
.log(LoggingLevel.INFO, "Result ===> ${body}")
.convertBodyTo(String.class)
.process(new Processor() { ... }

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.

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();
}
});

Camel message redelivery not behaving as expected

I have a route in Camel that I want to retry when an exception occurs, but I want to set a property so that the route can do something slightly differently the second time to try to stop the error happening again on the retry. Here's a route that illustrates the idea I'm trying at the moment.
from("direct:onExceptionTest")
.onException(Exception.class)
.maximumRedeliveries(1)
.log("Retrying")
.setProperty("retrying", constant(true))
.end()
.log("Start")
.choice()
.when(property("retrying").isNull())
.log("Throwing")
.throwException(new Exception("Hello world"))
.end()
.end()
.log("Done")
Obviously this isn't the real route; the whole choice body just simulates my component erroring in certain cases. I'm expecting to see the following messages logged:
Start
Throwing
Retrying
Start
Done
But what I actually see is:
Start
Throwing
Retrying
Failed delivery for (MessageId: ... on ExchangeId: ...). Exhausted after delivery attempt: 2 caught: java.lang.Exception: Hello world. Processed by failure processor: FatalFallbackErrorHandler[Pipeline[[Channel[Log(onExceptionTest)[Retrying]], Channel[setProperty(retrying, true)]]]]
I've tried adding handled(true) to the exception handler, but all this does is suppress the error message. I don't see the second Start or Done log message.
Why doesn't my route behave as I expect, and what do I need to do to get it to behave the way I want?
Update
#ProgrammerDan points out that the problem is that redelivery isn't intended for what I'm trying to achieve, which would explain why my route doesn't work! So I need to do the work in my handler, but my route calls a web service and has a few other steps and I don't want to duplicate all this in the handler. I've come up with this, which works as expected but it involves the route calling itself again from the start. Is this a bad idea? Will I get myself into knots with this approach?
from("direct:onExceptionTest")
.onException(Exception.class)
.onWhen(property("retrying").isNull()) // don't retry forever
.log("Retrying")
.setProperty("retrying", constant(true))
.handled(true)
.to("direct:onExceptionTest") // is recursion bad?
.end()
.log("Start")
.choice()
.when(property("retrying").isNull())
.log("Throwing")
.throwException(new Exception("Hello world"))
.end()
.end()
.log("Done")
Use onRedelivery with a Processor to set the property:
String KEY = "retrying";
from("direct:onExceptionTest")
.onException(RuntimeException.class)
.onRedelivery(new Processor() { // Sets a processor that should be processed before a redelivery attempt.
#Override
public void process(final Exchange exchange) throws Exception {
LOG.info("Retrying");
exchange.setProperty(KEY, true);
}
})
.maximumRedeliveries(1)
.handled(true)
.end()
.log("Start")
.process(new Processor() {
#Override
public void process(final Exchange exchange) throws Exception {
LOG.info("No problem");
}
})
.process(new Processor() {
#Override
public void process(final Exchange exchange) throws Exception {
if (exchange.getProperty(KEY) == null) {
LOG.info("Throwing");
throw new RuntimeException("Hello World");
}
else {
LOG.info("No throwing");
}
}
})
.log("Done");
This prints
[ main] route1 INFO Start
[ main] OnExceptionHandler INFO No problem
[ main] OnExceptionHandler INFO Throwing
[ main] OnExceptionHandler INFO Retrying
[ main] OnExceptionHandler INFO No throwing
[ main] route1 INFO Done
As #ProgrammerDan noted, only the processor that failed is re-executed but not the first processor that passed without any problems.
Edit:
If all the processing has to be re-done then you may use a sub-route with doTry and doCatch as follows:
from("direct:onExceptionTest")
.doTry()
.to("direct:subroute")
.doCatch(RuntimeException.class)
.setProperty(KEY, constant(true))
.to("direct:subroute")
.end()
.log("Done");
from("direct:subroute")
.log("Start")
.process(new Processor() {
#Override
public void process(final Exchange exchange) throws Exception {
LOG.info("No problem");
}
})
.process(new Processor() {
#Override
public void process(final Exchange exchange) throws Exception {
if (exchange.getProperty(KEY) == null) {
LOG.info("Throwing");
throw new RuntimeException("Hello World");
}
else {
LOG.info("No throwing");
}
}
});
From the Camel Docs:
When using doTry .. doCatch .. doFinally then the regular Camel Error Handler does not apply. That means any onException or the likes does not trigger. The reason is that doTry .. doCatch .. doFinally is in fact its own error handler and that it aims to mimic and work like how try/catch/finally works in Java.
Couple of points to consider about Camel's redelivery mechanism. First, check out the docs on the topic which might challenge your assumptions about how Camel handles redelivery. The point I've linked to is that Camel attempts redelivery at point of failure, it does not start over from the beginning of the route (as you appear to assume). If I'm understanding the docs correctly (I haven't tried this pattern in a while) you are basically telling it to retry throwing an exception several times, which I doubt is what you want to test.
Second, I'd recommend just doing the alternate handling directly in the onException() processor chain, as demonstrated a little further down in the same docs. Basically, you could specify how you want the message handled via a custom processor, and use both handled(true) and stop() to indicate that no further processing is necessary.
To sum it up, redelivery is generally meant to handle typical endpoint delivery failures, like intermittent connectivity drops, receiving server momentary unavailability, etc. where it makes the most sense to just "try again" and have a reasonable expectation of success. If you need more complex logic to handle retries, use a custom processor or series of processors within your onException() processor chain.

Camel http4 component 411 Bad Content-Length

The route :
from("direct:start")
.setProperty(Exchange.CHARSET_NAME, constant("iso-8859-1"))
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Message m = exchange.getOut();
m.setBody(exchange.getIn().getBody());
m.setHeader(Exchange.HTTP_METHOD, HttpMethods.POST);
m.setHeader(Exchange.CONTENT_ENCODING, "gzip" );
m.setHeader(Exchange.CONTENT_LENGTH, m.getBody(byte[].class).length );
m.setHeader(HttpHeaders.CONTENT_TYPE, "application/xml");
m.setHeader(Exchange.HTTP_CHARACTER_ENCODING, "iso-8859-1");
m.setHeader(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate");
}
})
.marshal().gzip()
.to("http4://remote.com/path")
.unmarshal().gzip();
What I am sending :
String body = "<?xmlversion=\"1.0\"encoding=\"ISO-8859-1\"?><theXml></theXml>";
producer.sendBody(body);
I am getting
HTTP operation failed invoking http://remote.com/path with statusCode: 411
What is missing/wrong with this route ?
EDIT
The correct route would be
from("direct:start")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Message m = exchange.getOut();
m.setBody(exchange.getIn().getBody());
m.setHeader(Exchange.HTTP_METHOD, HttpMethods.POST);
m.setHeader(Exchange.CONTENT_ENCODING, "gzip" );
m.setHeader(Exchange.CONTENT_TYPE, "application/xml");
}
})
// http4 takes care of compressing/decompressing gzip
.to("http4://remote.com/path")
But now I have another problem : the remote server does not handle "Transfer-Encoding: Chuncked" Which seems to be the default way camel-http4 does it.
And i can't figure out how to turn Chunked off.
See next question How to turn off “Transfer-Encoding Chuncked” in Camel-http4?
You are setting the content length from the length of the unencoded data. It should probably be the length of the transmitted data. Refer to this SO question:
content-length when using http compression
By the way, do you really need to gzip with the data format?
There is a Unit test in camel sending GZIPed data.
https://svn.apache.org/repos/asf/camel/trunk/components/camel-http4/src/test/java/org/apache/camel/component/http4/HttpCompressionTest.java

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

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)

Resources