13

With Spring4 + ActiveMQ I want to receive a JMS Message from a Queue and convert to POJO automatically. I added the MappingJackson2MessageConverter to DefaultJmsListenerContainerFactory:

@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
    DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();

    // some other config

    MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
    converter.setTargetType(MessageType.TEXT);
    converter.setTypeIdPropertyName("???");
    factory.setMessageConverter(converter);

    return factory;
}

And this is my Listener Config

@JmsListener(destination = "queue.fas.flight.order", containerFactory = "jmsListenerContainerFactory")
public void processOrder(OrderRegisterDto registerParam) {
    System.out.println(registerParam.toString());
}

My question is, how do I set TypeIdPropertyName? The Queue is not under my control; others send JSON to it.

I want a common converter so I am using String receive message and am converting it to a POJO manually.

@JmsListener(destination = "xxxx", containerFactory = "xxxxx")
 public void order(String registerParam) {
    try{
        OrderRegisterDto dto = objectMapper.readValue(registerParam,OrderRegisterDto.class);
    }catch (IOException e){
        // TODO
    }
}

Are there any other better methods?

Gary Russell
  • 166,535
  • 14
  • 146
  • 179

4 Answers4

10

The converter expects the sender to provide type information for the conversion in a message property.

String typeId = message.getStringProperty(this.typeIdPropertyName);

The typeId can be a class name, or a key for an entry in the typeId mapping map.

If your message does not contain any type information, you need to subclass the converter and override getJavaTypeForMessage() to return a Jackson JavaType for the target class, e.g.:

return TypeFactory.defaultInstance().constructType(Foo.class);

If it's a constant and not dependent on some information in the message, you can create a static field in your subclass.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • First, yes, it works. ths subClass like this: `@Override protected JavaType getJavaTypeForMessage(Message message) throws JMSException { return TypeFactory.defaultInstance().constructType(OrderRegisterDto.class); }` But I want a **common converter**. With this method, only `OrderRegisterDto.class` can be convert, what about else, add another `ListenerFactory`? So I use `String` receive message, and convert it to POJO manually. `OrderRegisterDto dto = objectMapper.readValue(registerParam,OrderRegisterDto.class);` Does any more better method? –  Apr 06 '16 at 15:59
  • Don't try to put code in comments, edit the question instead. You can make the converter subclass as sophisticated as you want - but you need some hint in the message to tell you what class to create. – Gary Russell Apr 06 '16 at 16:37
  • 2
    is it possible to determine the type not from the message itself but from the target type of the @JmsListener annotated method? It is actually the expected behavior in comparison to standard deserialization that occur in controllers for example. – jeromerg Jul 05 '16 at 15:50
  • 2
    We recently added logic to [Spring AMQP](http://docs.spring.io/spring-amqp/docs/1.6.0.RELEASE/reference/html/_introduction.html#_json_messageconverter) to do that (infer the type from the `@RabbitListener`) method, but it's not available for `@JmsListener`; you need to configure the converter manually. – Gary Russell Jul 05 '16 at 15:58
0

This work:

@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {

    DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();

    factory.setConnectionFactory(connectionFactory);


    factory.setConcurrency("3-10");


    // Este es el convertidor por defeto de Spring.
    SimpleMessageConverter s = new SimpleMessageConverter();

    MappingJackson2MessageConverter s2 = new MappingJackson2MessageConverter();
    s2.setTargetType(MessageType.BYTES);
    s2.setTypeIdPropertyName("DocumentType");

    factory.setMessageConverter(s2);  // or "s"

    return factory;
}

This is the listener:

@JmsListener(containerFactory = "jmsListenerContainerFactory", destination = ORDER_QUEUE)
@Payload final ) {
public void receiveMessage(Session ses, @Payload final Message<Product> message, @Headers final Map<String, Object> headers)  {


}

And the Javascript STOMP client:

 var product = {
              productId : "111",
              name : "laptop",
              quantity: 2
            }
 var beforeSend = JSON.stringify(product);
 stompClient.send(destinationProductProd_02,{"DocumentType":"org.jms.model.Product"}, beforeSend);  // OK

Observe that I add the "DocumentType" STOMP header to the message to send. You need send the full package path of the Java class.

Sergio
  • 441
  • 9
  • 22
  • thx, that's could be fine if the `queue` under my control. But actually, someone else send the message to the `queue`, it's impossible to add an extra param like "DocumentType" or something else. –  Oct 13 '16 at 03:31
0

Looks like there is no simple solution to automatically convert the JSON to POJO in @JmsListener without the "type header" in a message. But it is possible to do it in your code explicitly using Jackson's ObjectMapper:

@Autowired
private ObjectMapper objectMapper;

@JmsListener(destination = "...", containerFactory = "...")
public void processOrder(String payload) {
    OrderRegisterDto dto = objectMapper.readValue(payload, OrderRegisterDto.class);
    System.out.println(dto.toString());
}

Don't forget to remove MappingJackson2MessageConverter in your config:

MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); factory.setMessageConverter(converter);

Taras Shpek
  • 101
  • 2
  • 4
0

Jackson need type info to convert the JSON to POJO; use @JsonTypeInfo can generate class type info in JSON automatically
so i use the this way solve this problem:

  1. define a abstract class with @JsonTypeInfo
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@class")
public abstract class AbstractMessage implements Serializable {

}

  1. define POJO extends AbstractMessage
public class Xxx extends AbstractMessage {
    // some properties
}

  1. config the MessageConverter
@Bean
public MessageConverter messageConverter() {
    MappingJackson2MessageConverter messageConverter = new MappingJackson2MessageConverter();
    messageConverter.setObjectMapper(objectMapper);
    // same with @JsonTypeInfo( property )
    messageConverter.setTypeIdPropertyName("@class");
    return messageConverter;
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
wyulong
  • 11
  • 2