I'm facing a weird behavior when a conversion error occurs using a message converter in my inbound gateway. The idea in the example below is to receive XML payloads (or serialized java), convert them to java objects and respond with the same media type.
Given this configuration:
<bean id="converterXml" class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter" />
<bean id="converterSerialized" class="org.springframework.integration.http.converter.SerializingHttpMessageConverter" />
<util:list id="converters">
<ref bean="converterXml" />
<ref bean="converterSerialized" />
</util:list>
<int-http:inbound-gateway id="inboundIntegrationGateway"
view-name="/integration"
message-converters="converters" error-channel="errorChannel"
request-payload-type="MyXmlPojo"
supported-methods="POST" request-channel="inboundIntegration"
path="/services/integration"
reply-timeout="50000">
</int-http:inbound-gateway>
If an invalid XML payload (no end tag for example) is submitted, the exception HttpMessageNotReadableException raised in the JAXB XML converter is not forwarded in the errorChannel (where I defined a service activator to handle exceptions). Note that this handler works well after the payload conversion.
<int:service-activator input-channel="errorChannel" ref="exceptionHandlerService"
method="handleException" requires-reply="true" />
What am I missing here? Why is the HttpMessageNotReadableException not handled by my error handler? Any help is welcome!
Why is the HttpMessageNotReadableException not handled by my error handler?
The error-channel comes into force only when we already send Message to the downstream flow, but if your incoming HTTP request can't be converted to Message, there is still nothing to send to the error-channel.
Right, with convertExceptions = true we can't just return an exception as is and let some HttpMessageConverter to convert it into a reasonable HTTP response. Typically SerializingHttpMessageConverter is on the scene.
Why is this exception not wrapped in a MessageHandlingException and sent to my error handler?
Just because we haven't reached messaging yet. With your problem we are still in the standard Spring MVC environment and Spring Integration is still powerless here during request conversion.
You should consider some solution from Spring MVC for your case: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-exceptionhandlers
Adding convert-exceptions="true" to your int-http:inbound-gatewaycould solve the exception flow problem. This post has been useful for me.
Related
I have an HTTP Inbound Gateway in my Integration Application, which I will call during some save operation. It's like this. If I have one product, I will call the API once, and if I have more than once, then I will call multiple times. The problem is, for single invoke, SI works just fine. But for multiple calls, request and response get messed up. I thought Spring Integration Channels are just like MQ's, but it is not?
Let me explain this. Let's say I have 2 products. First, I invoke SI for Product A and then for B. Response of A got mapped to request B! It happens all the time. I don't want to use some dirty hacks like wait for the first response to come and invoke again. This means the system has to wait for a long time. I guess we can do it in Spring Integration using task executor, but with all the basic samples out there, I can't find the right one. So please help me find out how can I fix this issue!
My Configuration is :
<int:channel id="n2iMotorCNInvokeRequest" />
<int:channel id="n2iMotorCNInvokeResponse" />
<int:channel id="n2iInvoketransformerOut" />
<int:channel id="n2iInvokeobjTransformerOut" />
<int:channel id="n2iInvokegatewayOut" />
<int-http:inbound-gateway id="i2nInvokeFromPOS"
supported-methods="GET"
request-channel="i2nInvokeRequest"
reply-channel="i2nInvokeResponse"
path="/postProduct/{Id}"
mapped-response-headers="Return-Status, Return-Status-Msg, HTTP_RESPONSE_HEADERS"
reply-timeout="50000">
<int-http:header name="Id" expression="#pathVariables.Id"/>
</int-http:inbound-gateway>
<int:service-activator id="InvokeActivator"
input-channel="i2nInvokeRequest"
output-channel="i2nInvokeResponse"
ref="apiService"
method="getProductId"
requires-reply="true"
send-timeout="60000"/>
<int:transformer input-channel="i2nInvokeResponse"
ref="apiTransformer"
method="retrieveProductJson"
output-channel="n2iInvokeRequest"/>
<int-http:outbound-gateway request-channel="n2iInvokeRequest" reply-channel="n2iInvoketransformerOut"
url="http://10.xx.xx.xx/api/index.php" http-method="POST"
expected-response-type="java.lang.String">
</int-http:outbound-gateway>
<int:service-activator
input-channel="n2iInvoketransformerOut"
output-channel="n2iInvokeobjTransformerOut"
ref="apiService"
method="productResponse"
requires-reply="true"
send-timeout="60000"/>
The i2nInvokeFromPOS gateway is what we call from Web Application which is where all the products will be created. This Integration API will fetch that data, and post it to the backend system so that it will get updated to the other POS locations too!
Steps :
I will send the productId to i2nInvokeFromPOS.
apiTransformer -> retrieveProductJson() method will fetch the product details from DB based on the ID
Send the Request JSON to Backend system using http:outbound-gateway
Get the response from Backend and update the product status as uploaded in DB. Happens in apiService -> productResponse()
Once the response for A is received, all I'm getting is HTTP 500 Error for the Request B! But the Backend API is just fine.
The framework is completely thread-safe - if you are seeing cross-talk between different requests/responses then one (or more) of your components that the framework is invoking is not thread-safe.
You can't keep state in fields in, for example, code invoked from a service activator.
Is it possible to configure Spring AMQP so it will automatically convert messages from queue (which are essentially JSON strings) into the objects of desired type?
What I have tried so far:
1) My cfg:
<rabbit:listener-container connection-factory="rabbitConnectionFactory" message-converter="jsonMessageConverter">
<rabbit:listener ref="foo" method="listen" queue-names="test_queue"/>
</rabbit:listener-container>
<bean id="foo" class="foo.FooListener"/>
<bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.JsonMessageConverter"/>
2) My listener FooListener has method listen(FooMessage foo) { ... }
and
3) My FooMessage is just simple POJO and messages in my test_queue are just serialized instances of FooMessage in JSON format.
So it doesn't work, Spring claims for listen(byte[] msg) method:
java.lang.NoSuchMethodException: foo.FooListener.listen([B).
Is it possible to do?
Well, actually this thing works like a charm with Spring AMQP even prior version 1.6. The problem was with incorrect content-type of my message, it was just text/plain.
If message with json is sent in appropriate way, say
template.convertAndSend("test_queue", "test_queue", new FooMessage("blablabla","blabla"));
with correctly setup template:
<rabbit:template id="amqpTemplate"
connection-factory="rabbitConnectionFactory"
message-converter="jsonMessageConverter"
/>
<bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/>
so method listen(FooMessage foo) { ... } is invoked with automatically created object of type FooMessage from message.
Please, read the documentation for the Jackson2JsonMessageConverter.
And pay attention to the note:
In versions prior to 1.6, if type information is not present, conversion would fail. Starting with version 1.6, if type information is missing, the converter will convert the JSON using Jackson defaults (usually a map).
So, independently of the target argument type of the POJO handler, the MessageProperties must contain __TypeId__ information.
To meet your requirement consider to use #RabbitListener instead.
See the same section for news in the Spring AMQP 1.6.
Although feel free to raise a JIRA issue and we may consider some hooks for general MessageListenerAdapter where we know the method via listener ref="foo" method="listen" and, therefore, can infer the target argument type before conversion.
I've read that if exception is thrown in the flow the first thing the framework will do is check message header for error-channel property. Is it always the case?
In my particular case I'm assigning a custom error-channel to a message header and yet the message seems to get propagated up the stream to the nearest error-handler/error-channel.
<int:chain id="buildAggregatedResponseChain" input-channel="aggregatedResultChannel"
output-channel="sendAggregatedChannel">
<int:header-enricher>
<int:error-channel ref="myErrorChannel"/>
</int:header-enricher>
<int:service-activator ref="service" method="doSomething"/>
</int:chain>
I explicitly throw an exception inside doSomething but the exception never ends up in myErrorChannel. Instead, it is "propagated" to the nearest ErrorHandler up the stream or to the error-channel specified up the stream for int-mail:imap-idle-channel-adapter(tried several different flows).
What do I miss? Maybe someone can outline the main principal of error handling/error propagation(for example when talking about several transactions, etc)? There is some information out there, but it is quite scattered and not systematic.
It depends on the upstream flow; if there's an async handoff the header is consulted; otherwise, the exception is thrown back to the inbound endpoint.
In general, I would advise against modifying framework headers such as errorChannel. Instead put an error-channel on the inbound endpoint (such as your imap idle adapter) and handle the errors on that flow.
Modifying the headers directly is rarely needed. If you wish to insert different error-handling mid-flow then you can insert a messaging gateway...
<int:service activator ... ref="gw" />
<int:gateway id="gw" default-request-channel="..."
error-channel="midFlowErrorChannel" />
If the downstream flow (from the gateway) returns no result on success, then be sure to add a default reply timeout of 0 (or use a custom service interface with a method that returns void).
I'm using Spring Integration & SI AMQP 3.0.0-RELEASE.
I have a fairly simple Request-Response over AMQP between two SI instances.
I'm finding that when the response arrives back on the requesting server, that SI is attempting to deserialize the response using the the Request object's type, not the Response object.
ie., Given the gateway interface of:
public AnalyticsReponse getAnalyticsReport(EntityMessage objectUri);
I find that even though the correct JSON of an AnalyticsResponse arrives on the server, SI is attempting to deserialize it as an EntityMessage, which is failing.
I've debugged it through, and I suspect that the cause is that the Responding side is copying the inbound json__TypeId__ header, rather than supplying it's own. However, I can't see where I've misconfigured this.
Here's my config -- what have I done wrong?
Requesting side:
<int:channel id="analytics.reports.requests.channel" />
<int:channel id="analytics.reports.responses.channel" />
<int:gateway service-interface="com.project.analytics.gateway.AnalyticsReportingGateway">
<int:method name="getAnalyticsReport" request-channel="analytics.reports.requests.channel" reply-channel="analytics.reports.responses.channel"/>
</int:gateway>
<int-amqp:outbound-gateway
request-channel="analytics.reports.requests.channel"
reply-channel="analytics.reports.responses.channel"
exchange-name="analytics.reports.exchange" amqp-template="amqpTemplate" />
Responding side:
<int:channel id="analytics.reports.requests.channel" />
<int:channel id="analytics.reports.responses.channel" />
<int-amqp:inbound-gateway request-channel="analytics.reports.requests.channel" reply-channel="analytics.reports.responses.channel"
queue-names="analytics.reports.queue" connection-factory="rabbitConnectionFactory" message-converter="jsonMessageConverter"/>
<int:service-activator input-channel="analytics.reports.requests.channel" output-channel="analytics.reports.responses.channel"
ref="analyticsReporter" method="getAnalytics"/>
<bean class="com.project.analytics.reporters.SimpleAnalyticsReporter" id="analyticsReporter"/>
public class SimpleAnalyticsReporter {
#SneakyThrows
public AnalyticsReponse getAnalytics(EntityMessage message) {
return new AnalyticsReponse("Hello");
}
As far as you aren't interested in org.springframework.integration.mapping.support.JsonHeaders, because you use jsonMessageConverter, you should filter them (<header-filter>) or fully ignore all AMQP headers (mapped-request-headers="-" or mapped-reply-headers="-").
However I see that I wasn't right yesterday (https://jira.springsource.org/browse/INT-3285) and reopen the issue to revise how can we get deal with standard headers by default to allow to work similar scenarios.
Thank you!
I have the following route defined using Spring DSL:
<camelContext id="myapp-camel-ctx" errorHandlerRef="deadLetterErrorHandler"
xmlns="http://camel.apache.org/schema/spring">
<route id="myapp-camel-route">
<from uri="timer://runOnce?repeatCount=1&delay=10" />
<to uri="bean:fizzBean?method=doFizz" />
<!-- What I call the "Smooks processor" -->
<to uri="smooks://my-smooks-config.xml" />
<to uri="bean:buzzBean?method=doBuzz" />
</route>
</camelContext>
<bean id="deadLetterErrorHandler" class="org.apache.camel.builder.DeadLetterChannelBuilder">
<property name="deadLetterUri" value="bean:errorCatcher" />
</bean>
<bean id="errorCatcher" class="com.me.myorg.myapp.ErrorCatcher">
<property name="foo" value="BAR" />
</bean>
Sometimes, depending on the output (outbound message) of the fizzBean, the Smooks processor throws an exception and hangs the entire application. When it does this, I can see the exception being thrown in the app logs (it's actually a MySQL exception), but not sure how to wrap/catch it and continue processing. I thought that, given the ErrorCatcher setup above, the thrown MySQL exception would be handled and that the route would continue processing. Instead, I never see evidence in my app logs that the ErrorCatcher#handle method gets executed when these Smooks/MySQL exceptions are thrown.
Have I configured anything incorrectly here? Is there anything more I can do (either via the Smooks processor's URI configs or something else) to prevent exceptions being thrown from inside that processor from hanging the entire app? Thanks in advance!
It depends how the Smooks team have implemented their Camel component. The Camel error handler only kicks in, if an exception was thrown, which Camel can catch; or there was an exception explicit set on the Exchange using setException. If Smooks do not do that (maybe they catch the exception, and dont propagate that back to Camel), then Camel cannot detect that exception and react upon it.
If you want to see what goes on at runtime, you can enable the tracer
http://camel.apache.org/tracer
Also mind that when you use a bean to handle the exception with the error handler. Then read this FAQ how to get access to the caused exception: http://camel.apache.org/why-is-the-exception-null-when-i-use-onexception.html
your configuration seems to be correct, if you want to see that it works, you can change your handle methods signature as follows
public void handle(Exception exception, Exchange exchange) {
System.out.println("Got Exception..."+exception.getMessage());
System.out.println("Exchange is :"+exchange);
}
now you can see the result on the console...