How can we access multiple JMS Queues using single consumre in JAVA - spring-boot

I have a requirement of accesssing multiple JMS queues and perform the desired operations based upon the event we are getting. This is being done on Spring Boot project. Could anyone please help

You can configure different #JmsListener in Spring boot and it will receive message from respective Queue you have configured.
#JmsListener(destination = "${abcQueueName}", containerFactory = "abcQueueListenerFactory")
public void receiveQuery(#Payload Test test,
#Headers MessageHeaders headers,
Message message,
Session sessionQuery) {
}
#Bean(name = "abcQueueListenerFactory")
public JmsListenerContainerFactory<?> testQueueListenerFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setPubSubDomain(false);
factory.setSessionTransacted(true);
factory.setConcurrency(concurrency + "-" + maxConcurrency);
factory.setReceiveTimeout(Long.valueOf(receiveTimeout));
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(jsonMessageConverter);
factory.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
configurer.configure(factory, connectionFactory);
return factory;
}

Related

Configure dead letter queue. DLQ for a jms consumer

It is a rather simple question... I have a spring project where I consume queues (CONSUMER).
Now I want to configure individual dead letter queues for each queue I am consuming.
However, in my mind, the individual dead letter queues configuration must be done in the broker service (SERVER), not in the CONSUMER. Is it really so?
My code below WILL NOT work, correct?
#Bean
public DeadLetterStrategy deadLetterStrategy(){
IndividualDeadLetterStrategy dlq = new IndividualDeadLetterStrategy();
dlq.setQueueSuffix(".DLQ");
dlq.setUseQueueForQueueMessages(true);
return dlq;
}
#Bean
public ActiveMQConnectionFactory consumerActiveMQConnectionFactory() {
var activeMQConnectionFactory = new ActiveMQConnectionFactory();
activeMQConnectionFactory.setBrokerURL(brokerUrl);
RedeliveryPolicy policy = activeMQConnectionFactory.getRedeliveryPolicy();
policy.setMaximumRedeliveries(maximumRedeliveries);
policy.setInitialRedeliveryDelay(0);
policy.setBackOffMultiplier(3);
policy.setUseExponentialBackOff(true);
return activeMQConnectionFactory;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
var factory = new DefaultJmsListenerContainerFactory();
factory.setSessionAcknowledgeMode(JmsProperties.AcknowledgeMode.CLIENT.getMode());
factory.setConcurrency(factoryConcurrency);
factory.setConnectionFactory(consumerActiveMQConnectionFactory());
return factory;
}
#Bean
public BrokerService broker() throws Exception {
final BrokerService broker = new BrokerService();
broker.addConnector(brokerUrl);
broker.setPersistent(false);
broker.setDestinationPolicy(policyMap());
return broker;
}
#Bean
public PolicyMap policyMap() {
PolicyMap destinationPolicy = new PolicyMap();
List<PolicyEntry> entries = new ArrayList<PolicyEntry>();
PolicyEntry queueEntry = new PolicyEntry();
queueEntry.setQueue(">"); // In activemq '>' does the same thing as '*' does in other languages
queueEntry.setDeadLetterStrategy(deadLetterStrategy());
entries.add(queueEntry);
destinationPolicy.setPolicyEntries(entries);
return destinationPolicy;
} }
#JmsListener(destination = "myqueue")
public void onMessage(Message message, Session session) throws JMSException {
try {
stuff()
message.acknowledge();
} catch (Exception ex) {
session.recover();
}
}
A JMS consumer in ActiveMQ 5.x cannot configure the broker side dead letter strategy, this must be done at the broker in the configuration XML or via programmatic broker configuration. You could configure it in spring as you've done if your broker is simply an in memory broker however that is of little use for most applications.
Refer to the broker documentation for more help on configuration of the broker.

Not able to configure durable subscriber in JMS with Spring Boot

I'm using Apache ActiveMQ 5.15.13 and Spring Boot 2.3.1.RELEASE. I'm trying to configure durable subscriber, but I'm not able do do. My application on runtime gives me an error as
Cause: setClientID call not supported on proxy for shared Connection. Set the 'clientId' property on the SingleConnectionFactory instead.
Below is the complete ActiveMQ setup with Spring Boot.
JMSConfiguration
public class JMSConfiguration
{
#Bean
public JmsListenerContainerFactory<?> connectionFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer)
{
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
// You could still override some of Boot's default if necessary.
factory.setPubSubDomain(true);
/* below config to set durable subscriber */
factory.setClientId("brokerClientId");
factory.setSubscriptionDurable(true);
// factory.setSubscriptionShared(true);
return factory;
}
#Bean
public MessageConverter jacksonJmsMessageConverter()
{
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
}
Receiver Class
public class Receiver {
private static final String MESSAGE_TOPIC = "message_topic";
private Logger logger = LoggerFactory.getLogger(Receiver.class);
private static AtomicInteger id = new AtomicInteger();
#Autowired
ConfirmationReceiver confirmationReceiver;
#JmsListener(destination = MESSAGE_TOPIC,
id = "comercial",
subscription = MESSAGE_TOPIC,
containerFactory = "connectionFactory")
public void receiveMessage(Product product, Message message)
{
logger.info(" >> Original received message: " + message);
logger.info(" >> Received product: " + product);
System.out.println("Received " + product);
confirmationReceiver.sendConfirmation(new Confirmation(id.incrementAndGet(), "User " +
product.getName() + " received."));
}
}
application.properties
spring.jms.pub-sub-domain=true
spring.jms.listener.concurrency=1
spring.jms.listener.max-concurrency=2
spring.jms.listener.acknowledge-mode=auto
spring.jms.listener.auto-startup=true
spring.jms.template.delivery-mode:persistent
spring.jms.template.priority: 100
spring.jms.template.qos-enabled: true
spring.jms.template.receive-timeout: 1000
spring.jms.template.time-to-live: 36000
When i try to run application it gives me error as below
Could not refresh JMS Connection for destination 'message_topic' - retrying using FixedBackOff{interval=5000, currentAttempts=1, maxAttempts=unlimited}. Cause: setClientID call not supported on proxy for shared Connection. Set the 'clientId' property on the SingleConnectionFactory instead.
My application has standalone producer and consumer. I did try to Google the error but nothing helped.
Late answer. Here is what worked for me.
Use SingleConnectionFactory in place of ConnectionFactory
Set client-id to SingleConnectionFactory
Do not set client-id to factory
public JmsListenerContainerFactory<?> connectionFactory(SingleConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
// Add this
connectionFactory.setClientId("your-client-id")
// Do not do this
//factory.setClientId("brokerClientId");

Spring Boot JMS MarshallingMessageConverter in replies

I've a JMS listener similar to the one done here:
Synchronous Message send and receive using JMS template and Spring Boot
It receives a XML payload. But different from that one, I want to also return a reply. If I return a String, it works ok, but as I'm doing with the message payload, I want to return a JAXB object.
However, if I return a Message, Spring tries to convert the object using the SimpleMessageConverter.
How can I configure the MarshallingMessageConverter to be used when converting the reply payload?
I had to configure the DefaultJmsListenerContainerFactory's message converter.
But what is weird, I already have a MarshallingMessageConverter there for DefaultMessageHandlerMethodFactory and JmsMessagingTemplate, and it's from another Java package (org.springframework.messaging.converter.MarshallingMessageConverter).
#Bean
public JmsListenerContainerFactory<?> jmsListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer)
{
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setErrorHandler(errorHandler());
factory.setMessageConverter(jmsMarshallingMessageConverter()); // !!!!
configurer.configure(factory, connectionFactory);
return factory;
}
#Bean
public org.springframework.jms.support.converter.MarshallingMessageConverter jmsMarshallingMessageConverter()
{
Jaxb2Marshaller marshaller = marshaller();
org.springframework.jms.support.converter.MarshallingMessageConverter converter =
new org.springframework.jms.support.converter.MarshallingMessageConverter();
converter.setMarshaller(marshaller);
converter.setUnmarshaller(marshaller);
converter.setTargetType(MessageType.TEXT);
return converter;
}

How to read pending messages from an ActiveMQ queue in Spring Boot

I like to read pending (not acknowledged) messages in a ActiveMQ queue using Spring boot. How to do that?
So far I can read a message the moment it is send to the queue:
#JmsListener(destination = "LOCAL.TEST",
containerFactory = "myJmsListenerContainerFactory")
public void receiveMessage(final Message jsonMessage) throws JMSException {
String messageData = null;
// jsonMessage.acknowledge(); // dont consume message (for testing)
LOGGER.info("=== Received message {}", jsonMessage);
}
using a standard configuration for the mq-connection:
#Bean
public ActiveMQConnectionFactory getActiveMQConnectionFactory() {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
activeMQConnectionFactory.setBrokerURL(BROKER_URL + ":" + BROKER_PORT);
return activeMQConnectionFactory;
}
and a standard ListenerContainerFactory:
#Bean
public DefaultJmsListenerContainerFactory myJmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(getActiveMQConnectionFactory());
factory.setConcurrency("1-1");
return factory;
}
But this just loggs a message if I manually send one using
#Autowired
private JmsTemplate jmsTemplate;
public void send(String destination, String message) {
LOGGER.info("sending message='{}' to destination='{}'", message, destination);
jmsTemplate.convertAndSend(destination, message);
}
with the standard template
#Bean
public JmsTemplate jmsTemplate() {
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(getActiveMQConnectionFactory());
return template;
}
I cannot read messages sent earlier that are still in the Queue (since I didn't .acknowledge() them)...
JMS supports "browsing" messages which appears to be the functionality you want. You should therefore change your Spring application to use a QueueBrowser instead of actually consuming the messages.
Messages won't be resent if not acknowledged. They are not returned to the queue until the session is closed or the connection lost, for example by stopping (and restarting) the listener container created by the factory.
You can access the container using the JmsListenerEndpointRegistry bean (or stop/start the entire registry which will stop/start all of its containers).
To read all pending messages, you can do like this
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616?jms.redeliveryPolicy.maximumRedeliveries=1");
Connection connection = connectionFactory.createConnection("admin", "admin");
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("listenerQueue");
MessageConsumer consumer = session.createConsumer(destination);
QueueBrowser browser = session.createBrowser((Queue) destination);
Enumeration elems = browser.getEnumeration();
while (elems.hasMoreElements()) {
Message message = (Message) consumer.receive();
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
System.out.println("Incoming Message: '" + textMessage.getText() + "'");
message.acknowledge();
}
}
connection.close();
Step by step implementation of Spring boot ActiveMQ. Lets write some code to make it more clear. This will help to read all pending messages in current session only.
Add these dependencies in pom.xml file.
<!-- Dependencies to setup JMS and active mq environment -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
</dependency>
Add #EnableJms into your main controller where your main() method exists.
Create connection factory by adding these 2 methods in application controller only.
#Bean
public JmsListenerContainerFactory<?> myFactory(
ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
logger.info("configuring jms connection factory....");
// anonymous class
factory.setErrorHandler(
new ErrorHandler() {
#Override
public void handleError(Throwable t) {
logger.error("An error has occurred in the transaction", t);
}
});
// lambda function
factory.setErrorHandler(t -> logger.info("An error has occurred in the transaction"));
configurer.configure(factory, connectionFactory);
return factory;
}
// Serialize message content to json using TextMessage
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
Mention credentials in in application.yml file as
spring.activemq.user=admin
spring.activemq.password=admin
spring.activemq.broker-url=tcp://localhost:61616?jms.redeliveryPolicy.maximumRedeliveries=1
Autowire jmsTemplate in any spring bean class.
#Autowired
private JmsTemplate jmsTemplate;
Now it is time to send message to a queue.
jmsTemplate.convertAndSend("anyQueueName", "value1");
jmsTemplate.convertAndSend("anyQueueName", "value2");
...
Add a jmslistener. This method will be called automatically by JMS when any message will be pushed to queue.
#JmsListener(destination ="anyQueueName", containerFactory = "myFactory")
public void receiveMessage(String user) {
System.out.println("Received <" + user + ">");
}
Manually you can read the messages available in queue:-
import javax.jms.TextMessage;
import javax.jms.QueueBrowser;
import javax.jms.Session;
import javax.jms.TextMessage;
public void readMessageFromQueue(){
jmsTemplate.browse("anyQueueName", new BrowserCallback<TextMessage>() {
#Override
public TextMessage doInJms(Session session, QueueBrowser browser) throws JMSException {
Enumeration<TextMessage> messages = browser.getEnumeration();
while (messages.hasMoreElements()) {
System.out.println("message found : -"+ messages.nextElement().getText());
}
}
});
}
Output :-
message found :- value1
message found :- value2
-Happy Coding

Configuring multiple DefaultJmslistenercontainerfactory

In my app, I have 2 diff mq conn factory beans. For this I have 2 diff DefaultJmslistenercontainerfactory beans ex cf1 n cf2. Each of DefaultJmslistenercontainerfactory bean is being referred in seperate #JmsListener. ..Now i want to start stop each listrner programatically , for that I am overriding configureMessageListeners(JmsListenerRegistrar) method where I can set the DefaultJmslistenercontainerfactory instance. Note I only one instance can be set..
then in my code I get spring instance of JmsListenerRegistry from which I can get list dmlc..which I can start n stop
However. .since I have set only one DefaultJmslistenercontainerfactory instance, my code returns only one dmlc..
Question here is how can I pass multiple DefaultJmslistenercontainerfactory instances in configureJmsListener() method??
Note- I do not create dmlc manually..I just configure factory..
Why are you using configureMessageListeners() ? That is for programmatic endpoint registration, not influencing the configuration of #JmsListener.
Show your configuration (edit the question, don't try to post code/config in comments).
This works fine for me...
#Bean
public JmsListenerContainerFactory<DefaultMessageListenerContainer> one(
#Qualifier("jmsConnectionFactory1") ConnectionFactory cf) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(cf);
return factory;
}
#Bean
public JmsListenerContainerFactory<DefaultMessageListenerContainer> two(
#Qualifier("jmsConnectionFactory2") ConnectionFactory cf) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(cf);
return factory;
}
#JmsListener(id="fooListener", destination="foo", containerFactory="one")
public void listen1(String payload) {
System.out.println(payload + "foo");
}
#JmsListener(id="barListener", destination="bar", containerFactory="two")
public void listen2(String payload) {
System.out.println(payload + "bar");
}
...
#Autowired
JmsListenerEndpointRegistry registry;
...
MessageListenerContainer fooContainer = registry.getListenerContainer("fooListener");
MessageListenerContainer barContainer = registry.getListenerContainer("barListener");
You can also use registry.getListenerContainers() to get a collection.
I thought I explained all this in my answer to your other question.

Resources