ActiveMQ with more than one Listener - spring

I am using it with spring. Now I have the following setting:
<!--异步调用消息 -->
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="cacheConnectionFactory"></property>
<!-- <property name="destination" ref="ptpQueue"></property> -->
<property name="destination" ref="topicQueue"></property>
<property name="messageListener" ref="objHelper"></property>
<property name="sessionAcknowledgeModeName" value="AUTO_ACKNOWLEDGE"/>
</bean>
My messageListener only listens ref="objHelper" but now I want it to listen on both ref="objHelper" and ref="bexHelper" !
Both my objHelper and bexHelper have implemented MessageListener and have a method onMessage(){………… } but I have no idea how to do this.

The Spring MessageListenerContainer can only be configured with exactly one message listener.
So this is not possible.
Why do you need to use 2 message listeners on the same message listener container?

Related

IBM MQ call failed with compcode '2' ('MQCC_FAILED') reason '2027' ('MQRC_MISSING_REPLY_TO_Q') Exception With Spring Boot

I have a spring boot application which uses JMS template to communicate with queue services.
Here is my producer definition:
<bean id="jmsConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="targetConnectionFactory">
<bean class="org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter">
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
<property name="targetConnectionFactory">
<bean class="com.ibm.mq.jms.MQQueueConnectionFactory">
<property name="transportType" value="1"/>
<property name="queueManager" value="${queueManager}"/>
<property name="hostName" value="${connName}"/>
<property name="port" value="${connPort}" />
<property name="channel" value="${channel}"/>
</bean>
</property>
</bean>
</property>
<property name="sessionCacheSize" value="10"/>
<property name="cacheConsumers" value="false"/>
</bean>
<bean id="jmsTemplate" class="org.springframework.integration.jms.DynamicJmsTemplate">
<property name="connectionFactory" ref="jmsConnectionFactory"/>
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.MappingJackson2MessageConverter"></bean>
</property>
</bean>
<!-- JMS outbound gateway -->
<int-jms:outbound-gateway
id="myJmsOutboundGateway"
connection-factory="jmsConnectionFactory"
request-destination-expression="headers.request_destination_queue"
request-channel="jmsOutboundRequestChannel"
reply-destination-expression="headers.reply_destination_queue"
reply-channel="jmsOutboundReplyChannel"
explicit-qos-enabled="true"/>
Here is consumer definition which stays in other project to listen queue:
<!-- Transation flow start -->
<bean id="inboundQueue" class="com.ibm.mq.jms.MQQueue">
<constructor-arg index="0" value="${queueManager}"/>
<constructor-arg index="1" value="#{'inQueue'}"/>
<property name="targetClient" value="1" />
</bean>
<int:channel id="jmsOutboundChannel" />
<int-jms:outbound-channel-adapter
id="acquirerOutboundAdapter"
destination-name="destinationQueue"
jms-template="jmsTemplate"
channel="jmsOutboundChannel"/>
When I run the project and try to send message over IBM MQ, I get this exception:
JMS attempted to perform an MQPUT or MQPUT1; however IBM MQ reported an error.
Use the linked exception to determine the cause of this error.; nested exception is com.ibm.mq.MQException: JMSCMQ0001: IBM MQ call failed with compcode '2' ('MQCC_FAILED') reason '2027' ('MQRC_MISSING_REPLY_TO_Q').
at org.springframework.jms.support.JmsUtils.convertJmsAccessException(JmsUtils.java:274)
at org.springframework.jms.support.JmsAccessor.convertJmsAccessException(JmsAccessor.java:185)
at org.springframework.jms.core.JmsTemplate.execute(JmsTemplate.java:507)
at org.springframework.jms.core.JmsTemplate.send(JmsTemplate.java:584)
at org.springframework.jms.core.JmsTemplate.convertAndSend(JmsTemplate.java:691)
at
When we checked headers in message in debug mode, we saw that header which told as missing stays in message headers:
jms_destination=queue:///destinationQueue
jms_timestamp=1650004155460
JMS_IBM_PutApplType=28
JMS_IBM_Format=
id=3d22ed9e-d665-8c9a-7995-bb1a2b0c36eb
acquirerId=404045
jms_messageId=ID:414d5120514d3120202020202020202079ca536203fb1940
JMS_IBM_MsgType=1
JMSXUserID=mqm
priority=4
acq_rsp_ts=1650004162979
ReplyToQ=destinationQueue
JMS_IBM_Character_Set=UTF-8
JMS_IBM_Encoding=273
reply_destination_queue=destionationQueue
jms_replyTo=queue://QM1/destionationQueue
startTime=1650004162965
JMS_IBM_PutTime=06291556
request_destination_queue=inQueue
JMSXAppID=core.SoftPOSApplication
jms_redelivered=false
JMS_IBM_PutDate=20220415
jms_correlationId=210595709001
ReplyToQMgr=destinationQueue
Why are we facing this problem although header stays in headers? How can we solve this problem?
If the message type has been set to one of "Request", MQ will check for the presence of a reply to queue in the MQ header; obviously you would want the reply to queue to be present on the queue manager but I don't believe the reply to queue actually has to exist at the time the application puts the message for the put to succeed. Here is a snippet of JMS code that would demonstrate that:
Destination sendTo = session.createQueue("IN.Q");
Destination replyTo = session.createQueue("IN.Q.REPLY");
MessageProducer producer = session.createProducer(sendTo);
Message msg = session.createTextMessage("test");
msg.setIntProperty("JMS_IBM_MsgType",com.ibm.mq.constants.MQConstants.MQMT_REQUEST);
// Uncomment the line below to prevent a '2027' ('MQRC_MISSING_REPLY_TO_Q') error
// msg.setJMSReplyTo(replyTo);
producer.send(msg);
I appreciate this isn't a complete answer but hopefully it will help you understand what is happening and focus on debugging the Spring configuration.

How to configure Delegating Session Factory in spring sftp inbound channel adaptor

We want to delegate the session-factory at run time to spring sftp inbound channel adapter. For that we have done the below configuration.
We have gone through the spring-sftp integration docs but we are not sure how to set the session-factory attribute value via java. Could you please suggest us how to delegate the session-factory run time in spring sftp inbound channel adapter using Delegating Session Factory.
XML Configuration
<beans>
<bean id="defaultSftpSessionFactoryOne" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
<property name="host" value="**.***.**.***" />
<property name="port" value="**" />
<property name="user" value="######" />
<property name="password" value="######" />
<property name="allowUnknownKeys" value="true" />
</bean>
<bean id="defaultSftpSessionFactoryTwo" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
<property name="host" value="**.***.**.***" />
<property name="port" value="**" />
<property name="user" value="######" />
<property name="password" value="######" />
<property name="allowUnknownKeys" value="true" />
</bean>
<bean id="delegatingSessionFactory" class="org.springframework.integration.file.remote.session.DelegatingSessionFactory">
<constructor-arg>
<bean id="factoryLocator"
class="org.springframework.integration.file.remote.session.DefaultSessionFactoryLocator">
<constructor-arg name="factories">
<map>
<entry key="one" value-ref="defaultSftpSessionFactoryOne"></entry>
<entry key="two" value-ref="defaultSftpSessionFactoryTwo"></entry>
</map>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
<int:channel id="receiveChannel" />
<int-sftp:inbound-channel-adapter id="sftpInbondAdapter" auto-startup="false"
channel="receiveChannel" session-factory="delegatingSessionFactory"
local-directory="C:\\Users\\sftp" remote-directory="/tmp/archive"
auto-create-local-directory="true" delete-remote-files="false"
filename-regex=".*\.txt$">
<int:poller cron="0/10 * * * * ?">
</int:poller>
</int-sftp:inbound-channel-adapter>
java code
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
DelegatingSessionFactory<String> dsf = (DelegatingSessionFactory<String>) ac.getBean("delegatingSessionFactory");
SessionFactory<String> one = dsf.getFactoryLocator().getSessionFactory("one");
SessionFactory<String> two = dsf.getFactoryLocator().getSessionFactory("two");
dsf.setThreadKey("two");
SourcePollingChannelAdapter spca = (SourcePollingChannelAdapter) ac.getBean("sftpInbondAdapter");
spca.start();
The delegating session factory was really intended for outbound adapters and gateways. Typically, inbound adapters don't switch to different servers.
Setting the thread key on the main thread like that does nothing.
You need to set/clear the key on the thread that invokes the adapter; this is shown for outbound adapters in the documentation.
For inbound adapters you need to do it on the poller thread.
I don't know what criteria you will use to select the factory, but you can use a smart poller to do it.

Activemq jdbcPersistenceAdapter flushingmessages to database

I have a activemq jdbcPersistenceAdapter configured to use Oracle. The primary broker gets a lock, but when I send messages to the queue, I cannot see them in a database table. I do not want the the messages cached in any way, since they will not be consumed until there are no more producers (long story).
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="primary" useJmx="true" useShutdownHook="false">
<managementContext>
<!-- use appserver provided context instead of creating one,
for jboss use: -Djboss.platform.mbeanserver -->
<managementContext createConnector="false"/>
</managementContext>
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#oracle-ds" createTablesOnStartup="false" lockKeepAlivePeriod="30000"/>
</persistenceAdapter>
<transportConnectors>
<transportConnector uri="tcp://localhost:61616"/>
</transportConnectors>
<!-- Spring JMS Producer Configuration -->
<bean id="jmsProducerTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="defaultDestination" ref="p3dPausedTransactionDestination"/>
<property name="deliveryPersistent" value="true"/>
</bean>
<!-- Message Listener Container -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="p3dPausedTransactionDestination"/>
<property name="messageListener" ref="transactionMessageConsumer" />
<property name="sessionTransacted" value="true"/>
<property name="autoStartup" value="true"/>
<property name="sessionAcknowledgeModeName" value="AUTO_ACKNOWLEDGE"/>
</bean>
<!-- JMS Factory -->
<beans profile="local, test, dev, dev2, sit, uat, tt, prod">
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<property name="targetConnectionFactory">
<bean class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:jboss/activemq/p3dConnectionFactory"/>
<property name="lookupOnStartup" value="true"/>
</bean>
</property>
</bean>
</beans>
Once I added the second broker, the messages started appearing in the database.

Twice dequeued JMS messages using ActiveMq and Atomikos

I use ActiveMq as JMS server and Atomikos as transaction manager.
On ActiveMq Admin web-interface I see that one message was enqueued, but 2 (!) messages were dequeued.
However, jms consumer process message only once, there is no duplication in processing. When I use simple Spring JmsTransactionManager, there are one enqueued and one dequeued messages. The problem appeares only for Atomikos JTA transaction manager.
What is the problem? How to configure Atomikos not to see twice dequeued messages?
My configuration is below. It almost the same as in tutorial. BTW, Atomikos's spring integration example works well but it uses Spring 2.0 whereas I use Spring 3.1.
<bean id="activeMqXaConnectionFactory" class="org.apache.activemq.spring.ActiveMQXAConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>
<bean id="atomikosQueueConnectionFactory" class="com.atomikos.jms.QueueConnectionFactoryBean" init-method="init">
<property name="resourceName" value="xaMq"/>
<property name="xaQueueConnectionFactory" ref="activeMqXaConnectionFactory"/>
</bean>
<bean id="singleConnectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<property name="targetConnectionFactory" ref="atomikosQueueConnectionFactory"/>
</bean>
<bean id="jmsDefaultContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="singleConnectionFactory"/>
<property name="messageListener" ref="consumer"/>
<property name="concurrentConsumers" value="1"/>
<property name="destinationName" value="q.jtaxa"/>
<property name="receiveTimeout" value="3000"/>
<property name="recoveryInterval" value="5000"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="sessionTransacted" value="true"/>
</bean>
<!-- Transactions -->
<!--Construct Atomikos UserTransactionManager, needed to configure Spring-->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init"
destroy-method="close">
<!-- when close is called, should we force transactions to terminate or not? -->
<property name="forceShutdown" value="true"/>
</bean>
<!-- Also use Atomikos UserTransactionImp, needed to configure Spring -->
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="300"/>
</bean>
<!-- Configure the Spring framework to use JTA transactions from Atomikos -->
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="atomikosTransactionManager"/>
<property name="userTransaction" ref="atomikosUserTransaction"/>
<property name="nestedTransactionAllowed" value="true"/>
</bean>
Consumer class is:
#Component
public class Consumer implements MessageListener {
#Override
public void onMessage(Message message) {
if (message instanceof TextMessage) {
try {
TextMessage textMsg = (TextMessage) message;
System.out.println(" " + textMsg.getText());
} catch (JMSException ex) {
ex.printStackTrace();
}
}
}
}
I found what was wrongly configured. It was "atomikosQueueConnectionFactory". I did accroding to the tutorial, but it should be just AtomikosConnectionFactoryBean class not QueueConnectionFactoryBean.
I deleted atomikosQueueConnectionFactory and added atomikosConnectionFactory
<bean id="atomikosConnectionFactory" class="com.atomikos.jms.AtomikosConnectionFactoryBean" init-method="init">
<property name="uniqueResourceName" value="amq1"/>
<property name="xaConnectionFactory" ref="mqXaConnectionFactory"/>
</bean>
It works fine after it.
I found proper configuration here.

Spring DefaultMessageListenerContainer MDP Initialization

What is the best way to perform initialization on DefaultMessageListenerContainer initialization? Currently I am waiting for first message, and keep track of it using a boolean variable, which isn't so pretty. Is there a better way ? I want to read and load certain data into the cache once the Message Driven POJO is started up, so the message processing is faster.
(Edited)
Spring Config Fragement:
<bean id="itemListener" class="com.test.ItemMDPImpl" autowire="byName" />
<bean id="itemListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<property name="delegate" ref="itemListener" />
<property name="defaultListenerMethod" value="processItems" />
<property name="messageConverter" ref="itemMessageConverter" />
</bean>
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="itemMqConnectionFactory" />
<property name="destinationName" value="${item_queue_name}" />
<property name="messageListener" ref="itemListenerAdapter" />
<property name="transactionManager" ref="jtaTransactionManager" />
<property name="sessionTransacted" value="true" />
<property name="concurrentConsumers" value="1" />
<property name="receiveTimeout" value="3000" />
</bean>
I would like to have some initialization done before any message is received by the listener.
Can't you just use #PostConstruct to annotate a method on ItemMDPImpl to perform startup initialization, just like any other Spring bean?
4.9.6 #PostConstruct and #PreDestroy

Resources