Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JsonType of request overrides response type when using JMS inbound gateway in combination with json converters (jackson2) [INT-3535] #7498

Closed
spring-operator opened this issue Oct 19, 2014 · 8 comments
Assignees
Milestone

Comments

@spring-operator
Copy link
Contributor

spring-operator commented Oct 19, 2014

Joost van Weenen opened INT-3535 and commented

I believe that the issue described in #7262 still exists for JMS based gateways.
I have a unit test where i post a request message of type java.lang.String and expect another type in the reply.
The headers with the type from the request are copied over the header value that is provided by the json messageconverter.

The problem is located in the function ```
org.springframework.integration.jms.DefaultJmsHeaderMapper.fromHeaders(MessageHeaders headers, javax.jms.Message jmsMessage)


The workaround i used is to add "jms_" to the value of typeIdPropertyName. I saw in the code that these headers are not copied. It worked for now. 

<bean id="jsonMessageConverter"
     class="org.springframework.jms.support.converter.MappingJackson2MessageConverter">
     <property name="targetType" value="TEXT" />
     <property name="typeIdPropertyName" value="jms_javatype" />
</bean>


Some example code:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "jms_javatype")
@Component
public class Command {
@JsonProperty("id")
public String id;
@JsonProperty("amount")
public Integer amount;

public Command(String id, int amount) {
	this.id = id;
	this.amount = amount;
}

public Command() {
}

}


Affects: 4.0.4

Attachments:

Referenced from: pull request #1302

@spring-operator
Copy link
Contributor Author

Artem Bilan commented

Share, please, your test-case.

Or even better: provide a contribution on the matter!
https://github.com/spring-projects/spring-integration/blob/master/CONTRIBUTING.md

@spring-operator
Copy link
Contributor Author

Joost van Weenen commented

I'll try to come up with an minimal test-case. I don't think that i have the knowledge to patch the problem.

@spring-operator
Copy link
Contributor Author

Joost van Weenen commented

Before calling headerMapper.fromHeaders(replyMessage.getHeaders(), jmsReply) @ line ChannelPublishingJmsMessageListener:342

ActiveMQTextMessage {
properties = {javatype=java.lang.String}
readOnlyProperties = false
readOnlyBody = false
droppable = false
text = "This is a String"}
GenericMessage [payload=This is a String
headers={jms_redelivered=false
id=014d5d53-3df8-1974-12c9-733b8df71d19
priority=4
jms_timestamp=1413802256019
javatype=net.vanweenen.spring.bug.MyCustomTypeDTO
timestamp=1413802267538}]

After calling the function which is implemented in my case by concrete class DefaultJmsHeaderMapper:

ActiveMQTextMessage {commandId = 0
properties = {priority=4
javatype=net.vanweenen.spring.bug.MyCustomTypeDTO
timestamp=1413802267538}
text = "This is a String"}

This triggers the following exception in the receiving outbound gateway:

2014-10-20 13:00:58,570 WARN  [main] org.springframework.integration.gateway.GatewayProxyFactoryBean$MethodInvocationGateway - failure occurred in gateway sendAndReceive
org.springframework.messaging.MessageHandlingException: error occurred in message handler [org.springframework.integration.jms.JmsOutboundGateway#0]; nested exception is org.springframework.jms.support.converter.MessageConversionException: Failed to convert JSON message content; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_STRING), expected FIELD_NAME: missing property 'javatype' that is to contain type id  (for class net.vanweenen.spring.bug.MyCustomTypeDTO)
 at [Source: "This is a String"; line: 1, column: 1]

The exception is caused by the fact that the gateway is receiving a simpel string "This is a String" but the javatype of the message indicates it should be JSON representing an object of type net.vanweenen.spring.bug.MyCustomTypeDTO.
As you can see the initial type was correctly set in the properties of jmsReply but after the fromHeaders() function this property is overridden by the type of the payload that was received.

Hope this helps. I will upload the example in a couple of hours.


@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "javatype")
@Component
public class MyCustomTypeDTO {
	public String id;
}

		<bean id="jsonMessageConverter"
			class="org.springframework.jms.support.converter.MappingJackson2MessageConverter">
			<property name="targetType" value="TEXT" />
			<property name="typeIdPropertyName" value="javatype" />
		</bean>

@spring-operator
Copy link
Contributor Author

Joost van Weenen commented

Minimal test case

@spring-operator
Copy link
Contributor Author

Artem Bilan commented

Well, I see now what's going on.
Thank you for sample, although it's a bit complicated ;)

The real issue is around that javatype header, which does not make sence for the downstream Spring Integration.

This issue isn't related to that with AMQP.

Since here MappingJackson2MessageConverter properly converts JSON to the target object and we send it to the channel.
Yes, we copy here all those JMS headers to the Spring MessageHeaders.
In the and we receive a reply as s String and MappingJackson2MessageConverter properly populates its javatype, but the next step is DefaultJmsHeaderMapper, which remaps MessageHeaders to the JMS Message headers.

Having that all I don't see reason to change anything in the Framework, because we don't have access to the MappingJackson2MessageConverter.typeIdPropertyName to filter in the DefaultJmsHeaderMapper.

However you can filter it just after <int-jms:inbound-gateway> using <header-filter header-names="javatype">.

Let us know if it is appropriate for you and we close it as won't fix.

@spring-operator
Copy link
Contributor Author

Joost van Weenen commented

Ok, I agree about the fact that spring shouldn't/can't access the MappingJackson2MessageConverter.typeIdPropertyName so it is impossible to prevent copying that header in the DefaultJmsHeaderMapper.

But then again this also isn't behavior that made sense to me. I expected the MappingJackson2MessageConverter to just "work" and the error doesn't clearly indicate what the problem is.
I propose to make a clear warning or note in the documentation about the behaviour around the typeIdPropertyName and point the users to two possible workarounds. Maybe on the page about message transformations?

  1. Use your proposed header-filter in the inbound flow and filter the property directly after receiving the jms message.
  2. Use jms_ as prefix in the property name because properties with those prefix are not copied by default by the DefaultJmsHeaderMapper although this is more a convenient workaround that is bound to be broken at one point in time. The most solid is using the first option.

I can confirm that your proposed solution worked for me. Sorry about the complicated example, still learning this stuff ;)

@spring-operator
Copy link
Contributor Author

Artem Bilan commented

Well, I sleeped with this one more time :)

And now I see that the result of MessageConverter should have a precedence over those values from the MessageHeaders.

I think I'll PR something soon to show what's going on.

Of course it will be only the change to the current version without backport, because there is always a case when end-users may rely on the bug.

@spring-operator
Copy link
Contributor Author

Gary Russell commented

Merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants