Automatically converting JSON bytes from RabbitMQ queue to object - java

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.

Related

Spring Integration - Exceptions and retries

My setup:
I have a message daemon using ActiveMQ which will consume JSON messages.
The publisher of JSON messages adds type header with value, for ex, com.example.Foo which is the type of the json message. I use this to transform json to pojo.
Spring config:
Once the message is received, these are the steps it goes through:
1. Transformer: Transforms json to pojo
2. Payload type router: Based on the type of the pojo, routes the pojo to appropriate service activator.
3. Service activator: Process the message.
<int:chain input-channel="transformerChannel">
<int:transformer id="jsonToPojoTransformer" ref="JsonToPojoTransformer" method="transform" />
<int:payload-type-router default-output-channel="defaultChannel">
<int:mapping type="com.example.Foo" channel="fooHandlerChannel"/>
<int:mapping type="com.example.Bar" channel="barHandlerChannel"/>
</int:payload-type-router>
</int:chain>
<int:service-activator input-channel="fooHandlerChannel" ref="fooHandler" method="onMessage"/>
<int:service-activator input-channel="barHandlerChannel" ref="barHandler" method="onMessage"/>
Service activator definition:
public class FooHandler {
public void onMessage(Foo foo) {...}
}
Problem:
I want to know how to access the message headers in the service activator. It seems like the service activator does not have access to the message headers since the transformer is returning a pojo.
Lets say the service activator is unable to call a down stream rest service for whatever reason. I want to skip processing this message now and I want to retry this message later. Or lets say there was an exception in processing this message. I want to retry processing this message after some delay. How do I accomplish this?
--edit--
Removed details to reduce question size as per Artem's comment.
Please, try do not make so long topics here in SO. It's hard to answer particular question if there are to many of them.
Absolutely not clear why you can't get access to header from your service activator method. You can accept the whole Message<>, and call its getHeaders(). You can use#Headersannotation on theMaparg to get headers from the message. You can use#Header` annotation to extract exactly particular header from the message.
Even if your transformer method returns just a POJO that doesn't mean that it isn't wrapped to the Message with the headers from requestMessage. If you need to return specific header alongside with your POJO, you should create Message yourself, using MessageBuilder and don't forget to copy requestMessage headers, just because transformer doesn't copy request headers if the whole message is returned.
You have to support TX on your JMS consumer, so that RuntimeException will lead to the rollback and, therefore, redelivery eventually. And you should ensure that all the flow is performed in the same thread. Otherwise TX is committed and message is acked on the broker. The same happens when you don't have transactions.

HttpMessageConverter error handling in inbound gateway

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.

How to invoke Java Mule component with enum value as method parameter?

I am working currently on an API that is exposed by Mule ESB 3.5.0 (non-EE). This API accepts a XML file with accounts to be imported via HTTP and puts this task definition into a RabbitMQ queue. Another Mule flow is responsible for taking items from the queue one-at-a-time (thanks to processingStrategy="synchronous") and feeding them to the platform core. The queue is required as the core is able to process one-file-at-a-time.
The setup above is up & running smoothly. What I would like to achieve now, is to enable our customers to troubleshoot the integration by exposing an HTTPS endpoint, where import statuses will be available (identified by some GUID and SHA1 of the request).
I created a simple POJO component that handles the logic of adding the status updates, the method signature being:
void addStatus(final String guid, final String status)
I managed to invoke the method above by defining the bean as
<bean id="importStatusComponent" class="com.example.ImportStatusComponent" />
and invoking the java-component in the Mule flow with:
<invoke object-ref="importStatusComponent" method="addStatus"
methodArguments="#[flowVars.guid], Import started"
methodArgumentTypes="java.lang.String, java.lang.String" />
As we would like to expose this to customers and allow them to implement some programmatic checking of the status, I decided to change the status type to an enum-based dictionary ImportStatusEnum.
Unfortunately, I am unable to fed enum into MEL that goes into <invoke methodArgument=""> tag attribute.
Examples of what I have tried:
1) Arguments as two separate MEL expressions.
<configuration>
<expression-language>
<import class="com.example.ImportStatusEnum" />
</expression-language>
</configuration>
<invoke object-ref="importStatusComponent" method="addStatus"
methodArguments="#[flowVars.guid], #[ImportStatusEnum.STARTED]"
methodArgumentTypes="java.lang.String, com.example.ImportStatusEnum" />
2) Arguments as a single MEL expression.
<configuration>
<expression-language>
<import class="com.example.ImportStatusEnum" />
</expression-language>
</configuration>
<invoke object-ref="importStatusComponent" method="addStatus"
methodArguments="#[flowVars.guid, ImportStatusEnum.STARTED]"
methodArgumentTypes="java.lang.String, com.example.ImportStatusEnum" />
3) Fully qualified class names instead of imports (not shown here).
How to pass an enum value as method argument to invoke component in Mule? Any help will be highly appreciated :)
This one will work
<invoke object-ref="importStatusComponent" method="addStatus" methodArguments="#[flowVars.guid], #[com.example.ImportStatusEnum.STARTED]" methodArgumentTypes="java.lang.String, com.example.ImportStatusEnum" />

Spring Integration AMQP - Wrong JSON type on Response

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!

Spring-WS: how to use WebserviceTemplate with pre-generated SOAP-envelope

Can you use a Spring-WS WebserviceTemplate for calling a webservice and avoid that it generates a SOAP-envelope? That is, the message already contains an SOAP-Envelope and I don't want that the WebserviceTemplate wraps another one around it. :-)
The reason I want this is that I'd like to call a webservice that uses ws-security and do not want to put the ws-security stuff into the WebserviceTemplate, but just want to feed it a message with pre-generated ws-security information in the SOAP-envelope. I tried calling the method sendSourceAndReceiveToResult with a Source already contains a Soap-Envelope with the WS-Security stuff and the webservice template wraps around another Soap-Envelope and thus destroys the message.
You're using ws-security in a strange way... I guess that you're trying to avoid ws-security dependancy by using pre-generated messages - for simple client might make sense, although it's definitely not by-the-book.
You can configure WebServiceTemplate to use plain XML without SOAP by setting messageFactory on WebServiceTemplate to this bean:
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<property name="messageFactory" ref="poxMessageFactory" />
</bean>
<bean id="poxMessageFactory" class="org.springframework.ws.pox.dom.DomPoxMessageFactory" />
Interceptors can come in handy for the sort of thing you are trying to do. Take a look at the Interceptor hierarchy here: http://static.springframework.org/spring-ws/docs/1.0-m1/api/org/springframework/ws/EndpointInterceptor.html
You can register an EndpointInterceptor with spring-ws and manipulate the response to your liking.

Categories

Resources