I am playing with Spring-boot and jms message driven beans.
I installed Apache ActiveMQ.
One queue is being used on which different message types are being send and read.
One simple MessageConverter was written to convert a POJO instance into XML.
A property Class was set in the message to determine how to convert a message to a POJO:
@Component
@Slf4j
public class XMLMessageConverter implements MessageConverter {
private static final String CLASS_NAME = "Class";
private final Map<Class<?>, Marshaller> marshallers = new HashMap<>();
@SneakyThrows
private Marshaller getMarshallerForClass(Class<?> clazz) {
marshallers.putIfAbsent(clazz, JAXBContext.newInstance(clazz).createMarshaller());
Marshaller marshaller = marshallers.get(clazz);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
return marshaller;
}
@Override
public Message toMessage(@NonNull Object object, Session session) throws JMSException, MessageConversionException {
try {
Marshaller marshaller = getMarshallerForClass(object.getClass());
StringWriter stringWriter = new StringWriter();
marshaller.marshal(object, stringWriter);
TextMessage message = session.createTextMessage();
log.info("Created message\n{}", stringWriter);
message.setText(stringWriter.toString());
message.setStringProperty(CLASS_NAME, object.getClass().getCanonicalName());
return message;
} catch (JAXBException e) {
throw new MessageConversionException(e.getMessage());
}
}
@Override
public Object fromMessage(@NonNull Message message) throws JMSException, MessageConversionException {
TextMessage textMessage = (TextMessage) message;
String payload = textMessage.getText();
String className = textMessage.getStringProperty(CLASS_NAME);
log.info("Converting message with id {} and {}={}into java object.", message.getJMSMessageID(), CLASS_NAME, className);
try {
Class<?> clazz = Class.forName(className);
JAXBContext context = JAXBContext.newInstance(clazz);
return context.createUnmarshaller().unmarshal(new StringReader(payload));
} catch (JAXBException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
Messages of different type (OrderTransaction or Person) where send every 5 seconds to the queue:
@Scheduled(fixedDelay = 5000)
public void sendMessage() {
if ((int)(Math.random()*2) == 0) {
jmsTemplate.convertAndSend("DummyQueue", new OrderTransaction(new Person("Mark", "Smith"), new Person("Tom", "Smith"), BigDecimal.TEN));
}
else {
jmsTemplate.convertAndSend("DummyQueue", new Person("Mark", "Rutte"));
}
}
Two listeners were defined:
@JmsListener(destination = "DummyQueue", containerFactory = "myFactory")
public void receiveOrderTransactionMessage(OrderTransaction transaction) {
log.info("Received {}", transaction);
}
@JmsListener(destination = "DummyQueue", containerFactory = "myFactory")
public void receivePersonMessage(Person person) {
log.info("Received {}", person);
}
When I place breakpoints in the converter I see everything works fine but sometimes (not always) I get the following exception:
org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method could not be invoked with incoming message
Endpoint handler details:
Method [public void nl.smith.springmdb.configuration.MyListener.**receiveOrderTransactionMessage**(nl.smith.springmdb.domain.**OrderTransaction**)]
Bean [nl.smith.springmdb.configuration.MyListener@790fe82a]
; nested exception is org.springframework.messaging.converter.MessageConversionException: Cannot convert from [nl.smith.springmdb.domain.**Person**] to [nl.smith.springmdb.domain.**OrderTransaction**] for org.springframework.jms
It seems that after the conversion Spring invokes the wrong method. I am complete in the dark why this happens. Can somebody clarify what is happening?