11

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
  • 131,626
  • 14
  • 99
  • 125
Mr.Sheng
  • 275
  • 3
  • 12

3 Answers3

9

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
  • 131,626
  • 14
  • 99
  • 125
  • 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? – Mr.Sheng 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
  • 351
  • 6
  • 21
  • 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. – Mr.Sheng 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
  • 61
  • 2
  • 4