TopicSubscriber receive() not working Spring JMS - consumer closed - spring

I am trying to receive messages using a TopicSubscriber created using execute method of JMSTemplate in Spring JMS. The objective is to test receiving of messages from a topic both synchronously and asynchronously. However, when trying to receive message from a durable topic subscriber, I get an error saying Consumer is closed.
I am new to Spring JMS. Can someone specify how to configure a JMSListener inside the test class? I have created two JMSListeners but they do not seem to work.
Here is my Test Class Code:
#SpringBootTest
class AqJmsExampleApplicationTests {
private ConnectionFactory connectionFactory;
private JmsTemplate jmsTemplate;
#BeforeEach
void setUp() {
// initialize jmstemplate with connectionFactory
// ......
}
#Test
void test() {
this.jmsTemplate.setExplicitQosEnabled(true);
this.jmsTemplate.setPubSubDomain(true);
TopicSubscriber tSub = this.jmsTemplate.execute(s -> {
return s.createDurableSubscriber(
(Topic)this.jmsTemplate
.getDestinationResolver()
.resolveDestinationName(s,
"testTopic4", true), "tSub");
}, true);
this.jmsTemplate.setPriority(7);
this.jmsTemplate.send("testTopic4", s -> {
return s.createTextMessage("hi this is first topic message");
});
try {
Message msg = tSub.receive(20000);
System.out.println("The message received is: " +
((TextMessage)msg).getText());
} catch (JMSException e) {
System.out.println("Got exception: " + e);
e.printStackTrace();
}
}
static class MyTestBean {
#JmsListener(destination = "testTopic4",
id = "top41",
subscription = "testTopic4"
)
public void receiveMessageTopic41(Message msg) throws JMSException{
System.out.println("Received for async subscriber 1<" +
((TextMessage)msg).getText() + ">");
}
#JmsListener(destination = "testTopic4",
id = "top42",
subscription = "testTopic4"
)
public void receiveMessageTopic42(Message msg) throws JMSException{
System.out.println("Received for async subscriber 2<" +
((TextMessage)msg).getText() + ">");
}
}
The error received is the following:
Got exception: javax.jms.IllegalStateException: JMS-115: Consumer is closed
Any help would be appreciated. Thanks!

Related

Spring jms invokes the wrong listener method when receiving a message

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?

Acknowledge message after JPA transaction succeed while consuming from Kafka

In a Quarkus application, I want to consume a Kafka message and persist its information in the database using an Entity Manager.
This is what I got so far:
#ApplicationScoped
public class ClientEventConsumer {
#Inject ClientRepository repository;
private final BlockingQueue<Message<ClientEvent>> messages = new LinkedBlockingQueue<>();
void startup(#Observes StartupEvent startupEvent) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
executor.scheduleAtFixedRate(() -> {
if (messages.size() > 0) {
try {
Message<ClientEvent> message = messages.take();
ClientEvent clientEvent = message.getPayload();
ClientEntity clientEntity = new ClientEntity();
clientEntity.setId(clientEvent.getId());
clientEntity.setName(clientEvent.getName());
repository.merge(clientEntity);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, 1000, 500, TimeUnit.MILLISECONDS);
}
#Incoming("qualificationCheck")
public CompletionStage<Void> consume(Message<ClientEvent> msg) {
messages.add(msg);
return msg.ack();
}
}
But with this approach the message gets acknowledged before the record is actually persisted in the database. Is there a way to only acknowledge the message if the JPA transaction succeeded?

Spring JMS listener acknowledge

I am using JMS to send receive message from IBM MQ message broker. I am currently working on listener service throwing unhandled excepion and message sent
back to queue without acknowledgement.
I want the service to retry a configurable number of time and throw meaning full exception message that listener service is unavailable.
My listener and container factory looks like below.
#JmsListener(destination = "testqueue", containerFactory = "queuejmsfactory")
public void consumer(String message) throws JMSException
{ handle(message); }
#Bean(name = "queuejmsfactory") public JmsListenerContainerFactory getQueueTopicFactory(ConnectionFactory con ,
DefaultJmsListenerContainerFactoryConfigurer config)
{ DefaultJmsListenerContainerFactory d = new DefaultJmsListenerContainerFactory();
d.setSessionTransacted(true);
d.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
config.configure(d,con);
return d; }
I short, I have an existing code using the SessionawareMessageListener onMessage which i am trying to
replicate to #JmsListener. How do i handle the session commit and rollback automatically and
how do i get the session in JmsListener if have to handle manually similar to onMessage.
#Override
public void onMessage(Mesage mes, Session ses) throws JMSException
{ try
{ TestMessage txtMessage = (TextMessage)message;
handle(txtMessage); ses.commit();
} catch (Exception exp)
{ if (shouldRollback(message))
{ ses.rollback();}
else{logger,warn("moved to dlq");
ses.commit();
}
} }
private boolean shouldRollback(Message mes) throws JMSException
{ int rollbackcount = mes.getIntProperty("JMSXDeliveryCount");
return (rollbackcount <= maxRollBackCountFromApplication.properties)
}
Updated code:
#JmsListener(destination = "testqueue", containerFactory = "queuejmsfactory")
public void consumer(Message message) throws JMSException
{
try {TestMessage txtMessage = (TextMessage)message;
handle(txtMessage);}
catch(Excepton ex) {
if shouldRollback(message)
{throw ex;}
else {logger.warn("moved to dlq")}
}}
private boolean shouldRollback(Message mes) throws JMSException
{ int rollbackcount = mes.getIntProperty("JMSXDeliveryCount");
return (rollbackcount <= maxRollBackCountFromApplication.properties)
}
#Bean(name = "queuejmsfactory") public JmsListenerContainerFactory getQueueTopicFactory(ConnectionFactory con ,
DefaultJmsListenerContainerFactoryConfigurer config)
{ DefaultJmsListenerContainerFactory d = new DefaultJmsListenerContainerFactory();
d.setSessionTransacted(true);
d.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
config.configure(d,con);
return d; }
I have also tried to access the JMSXDeliveryCount from Headers, but couldnt get the exact object to access delivery count. Can you clarify plz.
#JmsListener(destination = "testqueue", containerFactory = "queuejmsfactory")
public void consumer(Message message,
#Header(JmsHeaders.CORRELATION_ID) String correlationId,
#Header(name = "jms-header-not-exists") String nonExistingHeader,
#Headers Map<String, Object> headers,
MessageHeaders messageHeaders,
JmsMessageHeaderAccessor jmsMessageHeaderAccessor) {}
You can add the Session as another parameter to the JmsListener method.

How to handle JmsException /How do I set the redeliveryPolicy in ActiveMQ?

Let see one scenario is there when jms send message to consumer and we have to save that to db,but when db is down we cant able to save that to db due to db server is down.So how to acknowledge jms to send message again?
This issue raised by many people.I resolved this using below code snippet.
#Configuration
public class AppConfiguration {
#Bean
public JmsListenerContainerFactory<?> jmsContainerFactory(DefaultJmsListenerContainerFactoryConfigurer configurer) {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setClientID(clientId);
connectionFactory.setBrokerURL(brokerUrl);
CachingConnectionFactory cf = new CachingConnectionFactory(connectionFactory);
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(cf);
factory.setSubscriptionDurable(true);
configurer.configure(factory, cf);
return factory;
}
}
#Component("ApprovalSubsCriber")
public class ApprovalSubscriber implements SessionAwareMessageListener<TextMessage> {
#Override
#JmsListener(destination = "topic/jmsReplyTest1", containerFactory = "jmsContainerFactory", subscription = "ApprovalSubsCriber")
public void onMessage(TextMessage message, Session session) throws JMSException {
// This is the received message
System.out.println("Receive: " + message.getText());
// Let's prepare a reply message - a "ACK" String
ActiveMQTextMessage textMessage = new ActiveMQTextMessage();
textMessage.setText("ACK");
System.out.println(session.getAcknowledgeMode());
if ("notexception".equals(message.getText())) {
session.commit();
} else {
session.rollback();//If exception comes
}
}
}

delay delivery of message in activeMQ in spring boot

I want to send a message at any time 't' that will be received by the receiver after 'x' sec.
for doing so, I have written sender code
#Autowired
private JmsTemplate jmsTemplate;
private Queue queue = new ActiveMQQueue("topicName");
public void show(String message) {
try {
System.out.println("Sending message " + message);
jmsTemplate.convertAndSend(queue, message, new MessagePostProcessor() {
#Override
public Message postProcessMessage(Message message) throws JMSException {
System.out.println("postProcessMessage executed ");
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, 3 * 60 * 1000);
System.out.println("long time " + message
.getLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY));
return message;
}
});
System.out.println("Sending done " + message + " at " + System.currentTimeMillis());
} catch (Exception er) {
er.printStackTrace();
}
}
and Reciever code
#JmsListener(destination = "topicName")
public void reciever(String message) {
System.out.println("receiving message " + message + " at " + System.currentTimeMillis());
}
But message received by receiver instant .without any delay.
output is
Sending message this is a message postProcessMessage executed long
time 180000 receiving message this is a message at 1514391984964
Sending done this is a message at 1514391984970
configuration file is
#Bean
JmsTemplate createJMSTemplate(ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(connectionFactory);
return jmsTemplate;
}
#Bean
ConnectionFactory myActiveMQConnectionFactory() {
RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setBackOffMultiplier(1);
redeliveryPolicy.setUseExponentialBackOff(false);
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
connectionFactory.setRedeliveryPolicy(redeliveryPolicy);
NetworkConnector networkConnector = new DiscoveryNetworkConnector();
networkConnector.setConsumerTTL(2);
return connectionFactory;
}
Delayed message is not supported by activemq with default config, you should turn it on at first.
adding schedulerSupport in activemq.conf
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}" schedulerSupport="true">

Resources