Search code examples
javaspring-amqpspring-rabbit

How to bind a message directly to my object class


I am getting the message like below which contains 2 HashMap (inputMap and outputMap). Is it the right Approach to use ObjectMapper ?

Sample Message :

In RabbitMQ :

 {"ORCH_KEY":{"inputMap":{},"outputMap":{"activityId":"10001002","activityStatus":"SUCCESS"}}}

In Configuration Code

SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory());

    Jackson2JsonMessageConverter messageConverter=new Jackson2JsonMessageConverter();
    DefaultClassMapper classMapper = new DefaultClassMapper();
    classMapper.setDefaultType(ExchangeDTO.class);
    messageConverter.setClassMapper(classMapper);
    factory.setMessageConverter(messageConverter);

Consumer Code

 @RabbitListener(containerFactory = "adapterOPListenerContainerFactory", queues = Constants.ADAPTOR_OP_QUEUE)
    public void handleAdapterQueueMessage(HashMap<String, ExchangeDTO> adapterOutputMap) {

        logger.info("Adapter Handler::::::::::"+this.getClass().getCanonicalName());
        try {

            logger.info("Adapter Output Map:::::::::::" + adapterOutputMap);

            if(adapterOutputMap.size()==1){
                Iterator<Entry<String, ExchangeDTO>> iterator = adapterOutputMap.entrySet().iterator();
                Entry<String, ExchangeDTO> next = iterator.next();

                String ORCH_KEY =next.getKey();
                logger.info("Ochestration Key:::::::::::" + ORCH_KEY);

                ExchangeDTO exchangeDTO = next.getValue();
                logger.info("Size of OutputMap:::::"+exchangeDTO.getOutputMap().size());

ExchangeDTO class

public class ExchangeDTO implements Serializable {

/**
 * 
 */
private static final long serialVersionUID = 1L;
private HashMap<String, Object> inputMap = new HashMap<String, Object>();
private HashMap<String, Object> outputMap = new HashMap<String, Object>();

public HashMap<String, Object> getInputMap() {
    return inputMap;
}

public void setInputMap(HashMap<String, Object> inputMap) {
    this.inputMap = inputMap;
}

public HashMap<String, Object> getOutputMap() {
    return outputMap;
}

public void setOutputMap(HashMap<String, Object> outputMap) {
    this.outputMap = outputMap;
}

}

I am facing an error like below :

java.util.LinkedHashMap cannot be cast to com.bosch.bip.dto.ExchangeDTO


Solution

  • What do you mean by "in java code" ? The object mapper is expecting a JSON string.

    {"ORCH_KEY":{"inputMap":{},"outputMap":{"activityId":"10001002","activityStatus":"SUCCESS"}}}
    

    If you are using the Spring AMQP Json converter, the JSON will already have been converted to a Map of Maps.

    {inputMap={}, outputMap={activityId=10001002, activityStatus=SUCCESS}}
    

    looks like a toString() call on the result.

    You need to customize the Jackson2JsonMessageConverter with a custom ClassMapper that tells the converter you want an ExchangeDTO created.

    You can use a DefaultClassMapper; the default class mapper falls back to its default type if there is no information in the message to determine the type: myDefaultClassMapper.setDefaultType(MyType.class);. Then inject the mapper into the message converter.

    EDIT

    I just ran a test and it worked just fine for me (notice that no customization of the classmapper is required)...

    public static class DTO {
    
        private Map<String, Object> inputMap;
    
        private Map<String, Object> outputMap;
    
        public Map<String, Object> getInputMap() {
            return this.inputMap;
        }
    
        public void setInputMap(Map<String, Object> inputMap) {
            this.inputMap = inputMap;
        }
    
        public Map<String, Object> getOutputMap() {
            return this.outputMap;
        }
    
        public void setOutputMap(Map<String, Object> outputMap) {
            this.outputMap = outputMap;
        }
    
        @Override
        public String toString() {
            return "DTO [inputMap=" + this.inputMap + ", outputMap=" + this.outputMap + "]";
        }
    
    }
    
    @RabbitListener(queues = QUEUE, containerFactory = "adapterOPListenerContainerFactory")
    public void listen(HashMap<String, DTO> message) {
        System.out.println("Result:" + message.getClass() + ":" + message);
        latch.countDown();
    }
    
    @Bean
    public SimpleRabbitListenerContainerFactory adapterOPListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        return factory;
    }
    

    and

    ... Received message: (Body:'{"MAP":{"inputMap":{"foo":"bar"},"outputMap":{"baz":"qux"}}}'
    

    and

    Result:class java.util.HashMap:{MAP=DTO [inputMap={foo=bar}, outputMap={baz=qux}]}
    

    This mechanism (conveying the argument type to the JSON converter) requires Spring AMQP 1.6 or later - 1.6.2 is the current version.

    EDIT2

    It works fine for me...

    @RabbitListener(queues = QUEUE, containerFactory = "adapterOPListenerContainerFactory")
    public void listen(HashMap<String, DTO> dtos) {
        System.out.println("Result:" + dtos.getClass() + ":" + dtos);
        DTO dto = dtos.entrySet().iterator().next().getValue();
        System.out.println(dto);
        latch.countDown();
    }
    

    and

    Result:class java.util.HashMap:{MAP=DTO [inputMap={foo=bar}, outputMap={baz=qux}]}
    DTO [inputMap={foo=bar}, outputMap={baz=qux}]
    

    It seems like your map values are not being converted to the DTO, but to the default LinkedHashMap. I don't see how that's possible if you are using the same configuration as me.

    If you can post a small test project somewhere that exhibits the problem, I can take a look to see what's wrong.