Rabbitmq : Failed message being reprocessed in an infinite loop - java

This is the rabbitmq configuration that I have :
<rabbit:admin connection-factory="rmqConnectionFactory"/>
<bean id="**retryAdvice**" class="org.springframework.amqp.rabbit.config.StatefulRetryOperationsInterceptorFactoryBean">
<property name="retryOperations" ref="retryTemplate"/>
</bean>
<bean id="**retryTemplate**" class="org.springframework.retry.support.RetryTemplate">
<property name="retryPolicy" ref="simpleRetryPolicy"/>
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.FixedBackOffPolicy">
<property name="backOffPeriod" value="5000"/>
</bean>
</property>
<property name="retryContextCache" ref="retryContext"/>
</bean>
<bean id="**retryContext**" class="org.springframework.retry.policy.MapRetryContextCache"/>
<bean id="**simpleRetryPolicy**" class="org.springframework.retry.policy.SimpleRetryPolicy">
<property name="maxAttempts" value="3"/>
</bean>
<!-- Spring AMQP Template -->
<bean id="**rabbitTemplate**" class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<property name="connectionFactory" ref="rmqConnectionFactory"/>
<property name="messageConverter" ref="stdJsonMessageConverter"/>
</bean>
<bean id="**stdJsonMessageConverter**" class="org.springframework.amqp.support.converter.JsonMessageConverter">
<property name="createMessageIds" value="true"/>
</bean>
And my queue is configured as follows :
<rabbit:queue name="${queue}" durable="true">
<rabbit:queue-arguments>
<entry key="x-ha-policy" value="all"/>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:direct-exchange name="${exchange}">
<rabbit:bindings>
<rabbit:binding queue="${queue}" key="${routingKey}"/>
</rabbit:bindings>
</rabbit:direct-exchange>
When I published a message on the exchange for the first time, the listener failed with null ID exception. I purged the queue which had the bad message as well. Inspite of that, everytime I start my service, the failed message processing is retried and it fails continuously until there is a RetryCacheCapacityExceeded exception.
Has my failed message been cached somewhere? Is there a way to clear that?
Also, why do retries continue even though my retrytemplate suggest 3 reattempts at that interval of 5 secs?

When you use stateful retry, the retry state for each message id is kept in a cached (so we know when to stop).
If there's no ID, the message will fail (and keep being delivered) unless you add a MissingMessageIdAdvice to the advice chain (before the retry interceptor), which will allow 1 retry for messages with no id.

Related

Spring Integration JMS/IBM MQ: how can I business logic it if not receiving a response

My project is using jms+ibmmq to send a message out. it's working properly, and I'm able to push a message to the outbound queue, and then receive a response from the inbound queue. Recently, a client asks if we can send out an alert email if unable to get a response within like 2 min. I checked the spring integration messaging related document, seems gatway can implement it? but still can't get an idea how to use it. can someone help me out?
my current xml configuration
<integration:service-activator id="serviceActivator1" input-channel="inputChannel"
ref="messageProcessService" method="callMsgProcessor" output-channel="requestChannel" />
<bean id="ibmConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="targetConnectionFactory">
<bean class="com.ibm.mq.jms.MQQueueConnectionFactory">
<property name="transportType">
<util:constant static-field="com.ibm.msg.client.wmq.WMQConstants.WMQ_CM_CLIENT"/>
</property>
<property name="hostName" value="${hostName}"/>
<property name="queueManager" value="${queue-manager}"/>
<property name="channel" value="${channel}"/>
<property name="port" value="${port}"/>
</bean>
</property>
<property name="sessionCacheSize" value="5"/>
</bean>
<bean id="requestQueue" class="com.ibm.mq.jms.MQQueue">
<constructor-arg value="${request-queue}"/>
<property name="targetClient">
<util:constant static-field="com.ibm.msg.client.wmq.WMQConstants.WMQ_CLIENT_NONJMS_MQ"/>
</property>
</bean>
<jms:outbound-channel-adapter id="request.queue.adapter" connection-factory="ibmConnectionFactory"
destination="requestQueue" channel="requestChannel"/>
<integration:channel id="requestChannel"/>
<bean id="responseQueue" class="com.ibm.mq.jms.MQQueue">
<constructor-arg value="${response-queue}"/>
</bean>
<jms:message-driven-channel-adapter id="responseJMSAdapater"
destination="responseQueue"
channel="responseChannel"
connection-factory="ibmConnectionFactory"
error-channel="errorChannel"
acknowledge="transacted"
/>
<integration:channel id="responseChannel">
<integration:queue capacity="500"/>
<integration:interceptors>
<integration:wire-tap channel="logger"/>
</integration:interceptors>
</integration:channel>
if add a gateway, it looks like
<integration:gateway id="serviceAdaptedGateway" service-interface="com.mycompany.service.gatewayServiceInterface"
default-request-channel="requestChannel"
default-reply-channel="responseChannel"
default-reply-timeout="120000"/>
my question is
seems timeout not working, nothing happen even the waiting time exceed the timeout
although the gateway provide timeout property, where I can add my business logic like send email alert?

Spring Batch and Spring Integration. Can not configure JobListener

I'm new in Spring. Recently I do try to make spring batch and spring integration work together. I want to have JobListener which will listen for message comes to specific channel and launch Spring Batch Job.
I found example on github(https://github.com/chrisjs/spring-batch-scaling/tree/master/message-job-launch) and I tried to configure some way copy Spring Batch and Spring Integration together and this looks like:
<!--Incomming channel OneToOne-->
<int:channel id="requests-channel"/>
<!--For multiple consumers OneToMany-->
<int:publish-subscribe-channel id="reply-channel"/>
<!--Channel for file adapter-->
<int:channel id="file-adapter-reply-channel"/>
<int:channel id="statuses">
<int:queue capacity="10"/>
</int:channel>
<int:channel id="jobLaunchReplyChannel"/>
<!--Intercept request-->
<int-http:inbound-gateway request-channel="requests-channel"
supported-methods="PUT"
path="/testData/setProfileDescription"
reply-timeout="30000"
reply-channel="reply-channel">
</int-http:inbound-gateway>
<!--Sending HTTP response back to user OR either 'no reply received within timeout'-->
<bean id="profileDescriptionActivator"
class="ru.tcsbank.service.integrations.activators.ProfileDescriptionActivator"/>
<int:service-activator ref="profileDescriptionActivator"
input-channel="requests-channel"
output-channel="reply-channel"
method="httpMessageActivator"/>
<!--Write profile description to file-->
<bean id="custom-file-name-generator"
class="ru.tcsbank.service.integrations.transformers_generators.ProfilesFileAdapterNameGenerator"/>
<file:outbound-channel-adapter channel="file-adapter-reply-channel"
directory="file:out"
filename-generator="custom-file-name-generator"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" lazy-init="true" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/testdb"/>
<property name="username" value="test_user"/>
<property name="password" value="qwerty123"/>
</bean>
<bean id="stepScope" class="org.springframework.batch.core.scope.StepScope">
<property name="autoProxy" value="true"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="jobRepositoryInDB" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="transactionManager" ref="transactionManager"/>
</bean>
<bean id="itemProcessor" class="ru.tcsbank.service.batch_processing.CustomItemProcessor"/>
<bean id="itemReader" class="ru.tcsbank.service.batch_processing.CustomReader" scope="step">
<property name="resource" value="classpath:fileOut/*.csv" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="delimiter" value=","/>
<property name="names" value="id,firstName,lastName"/>
</bean>
</property>
<property name="fieldSetMapper">
<bean class="ru.tcsbank.service.batch_processing.ProfileDescriptionLineMapper"/>
</property>
</bean>
</property>
</bean>
<bean id="itemWriter" class="ru.tcsbank.service.batch_processing.CustomWriter"/>
<batch:job id="helloWorldJob" job-repository="jobRepositoryInDB">
<batch:listeners>
<batch:listener ref="jobListener"/>
</batch:listeners>
<batch:step id="step1">
<batch:tasklet>
<batch:chunk reader="itemReader" writer="itemWriter" processor="itemProcessor" commit-interval="10"/>
</batch:tasklet>
</batch:step>
</batch:job>
<int:transformer input-channel="reply-channel" output-channel="file-adapter-reply-channel">
<bean class="ru.tcsbank.service.batch_processing.FileMessageToJobRequest">
<property name="job" ref="helloWorldJob"/>
<property name="fileParameterName" value="input.file.name"/>
</bean>
</int:transformer>
<bean id="jobListener" class="ru.tcsbank.service.batch_processing.CustomJobExecutionListener">
<constructor-arg index="0" ref="notificationSender"/>
</bean>
<batch-int:job-launching-gateway request-channel="reply-channel"
reply-channel="file-adapter-reply-channel"/>
<int:logging-channel-adapter channel="jobLaunchReplyChannel"/>
<int:channel id="notificationsChannel"/>
<int:gateway id="notificationSender"
service-interface="ru.tcsbank.service.batch_processing.NotificationSender"
default-request-channel="notificationsChannel"/>
I expect my helloWorldJob run when(as I understand correctly) my jobListener receives message from notificationsChannel. But it do not work(do not receives message from notificationsChannel) Beyond then it throws error like:
Dispatcher has no subscribers for channel
'application.notificationsChannel'.; nested exception is >org.springframework.integration.MessageDispatchingException: Dispatcher >has no subscribers, failedMessage=GenericMessage [payload=TEST. >Image processing job ran for: 0 minutes, 0 seconds.
It's hard to understand what you would like to achieve with all this custom code, but what I can say, that there is no subscribers for that notificationsChannel in your configuration. You indeed send messages to it via notificationSender gateway, but you don't provide any endpoint to consume that notificationsChannel.
In the sample you mention in the link we have something like this:
<int-jms:outbound-channel-adapter id="notifications" destination-name="notifications"
channel="notificationsChannel"/>
So, messages sent to the notificationsChannel are landed in the notifications queue on JMS broker. Your sample are leaking such a subscriber. Therefore I only can explain a reason of the exception, but definitely can't tell you what do to.
UPDATE
You may not use notificationSender in your solution. Looks like it just a result of the CustomJobExecutionListener. So, if you don't need to listen for job process, just remove that CustomJobExecutionListener and, therefore, this notificationSender declaration together with the notificationsChannel definition.
Everything else you are asking in comments is out of the scope of this SO question. Please, consider to raise those concerns in the separate SO thread.

how to enforce the redelivery for un acknowledge jms message

I read on : http://www.javaworld.com/article/2074123/java-web-development/transaction-and-redelivery-in-jms.html?page=2
"Generally, acknowledging a particular message acknowledges all prior messages the session receives" ( in Client acknowledgement mode )
"Message redelivery is not automatic, but messages are redelivered under certain circumstances"
My questions :
how can I ensure there is a new session every time I recive a message (but reuse the connection)?
how to enforce the redelivery for un acknowledge message ?
Im using this configration :
<bean id="jmsConnectionFactory" class="com.ibm.mq.jms.MQQueueConnectionFactory"
lazy-init="true">
<property name="queueManager" value="${queueManager}" />
<property name="hostName" value="${hostName}" />
<property name="transportType" value="${transportType}" />
<property name="port" value="${port}" />
<property name="channel" value="${channel}" />
<property name="SSLCipherSuite" value="${SSLCipherSuite}" />
</bean>
<bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory">
<property name="maxConnections" value="10"/>
<property name="maximumActive" value="100"/>
<property name="connectionFactory" ref="jmsConnectionFactory"/>
</bean>
<bean id="jmsConfig" class="org.apache.camel.component.jms.JmsConfiguration">
<property name="connectionFactory" ref="pooledConnectionFactory"/>
<property name="transacted" value="false"/>
</bean>
<bean id="mqNonJmsDestRes" class="calypsox.tk.util.NonJmsMQQueueDestinationResolver" />
<bean id="jms" class="org.apache.camel.component.jms.JmsComponent">
<property name="configuration" ref="jmsConfig" />
<property name="acknowledgementModeName" value="CLIENT_ACKNOWLEDGE" />
<property name="destinationResolver" ref="mqNonJmsDestRes" />
</bean>
and I use camel processor as endpoint bean as singleton
That article you referenced is from 2002. All of the MQ based systems have received a lot of work since then. On your AMQ PooledConnectionFactory there are settings to control how long your connections last before they are destroyed and what you should do if you encounter an error. I recommend reading into some of the newer documentation since there has been a lot of changes in the last 14 years. So some things have become much easier.
You can also check into the exceptionListener on "org.apache.camel.component.jms.JmsComponent" to configure how to manage exceptions and even write your own if the current options don't suit your needs.

Spring : JMS Consumer Sessions/Connections not removed on Message Broker post Batch Job Completion

I have a Batch Job Configured to read messages from JMS Destination and write to a XML file using Chuck Tasklet. The JMS reader is custom implemented which inturn invokes JMSTemplate's receive method. I am using webMethods Broker as JMS Broker. During Batch run, we observed that the Consumer session created while reading the messages from Broker Destination are not being destroyed up on completion of the batch. They are only destroyed after I shutdown the JVM. I have provided more details below,
JMS Spring XML Configuration :
<bean id="JMS.SourceQueue.JndiTemplate" class="org.springframework.jndi.JndiTemplate">
<property name="environment">
<map>
<entry key="java.naming.provider.url" value="wmjmsnaming://Broker #1#X.X.X.X:6849" />
<entry key="java.naming.factory.initial" value="com.webmethods.jms.naming.WmJmsNamingCtxFactory" />
<entry key="com.webmethods.jms.naming.clientgroup" value="IS-JMS" />
</map>
</property>
</bean>
<bean id="JMS.SourceQueue.JmsConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate" ref="JMS.SourceQueue.JndiTemplate" />
<property name="jndiName" value="SmartBatchConnectionFactory" />
<property name="lookupOnStartup" value="true" />
<property name="cache" value="false" />
<property name="proxyInterface" value="javax.jms.ConnectionFactory" />
</bean>
<bean id="JMS.SourceQueue.ConnectionFactory"
class="org.springframework.jms.connection.CachingConnectionFactory">
<constructor-arg ref="JMS.SourceQueue.JmsConnectionFactory" />
</bean>
<bean id="JMS.SourceQueue.DefaultDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate" ref="JMS.SourceQueue.JndiTemplate" />
<property name="jndiName" value="SourceQueue" />
</bean>
<bean id="JMS.SourceQueue.MessageTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="JMS.SourceQueue.ConnectionFactory" />
<property name="receiveTimeout" value="10000" />
<property name="sessionTransacted" value="true" />
<property name="defaultDestination" ref="JMS.SourceQueue.DefaultDestination" />
</bean>
Any help on pointing out the actual issue would be great.
Call destroy() on the JMS.SourceQueue.ConnectionFactory at the end of the last step (or otherwise when the job is completed).
Using the caching connection factory is recommended to avoid creating connections/consumers for each message, but it needs to be told when to physically release the resources.

How do you listen to a JMS topic using Spring JMS

I have a HornetQ server which has topic and queue's. I am not aware of the config on the other side but the way I connect to the Queue is:
<bean id="connectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate" ref ="jndiTemplate"/>
<property name="jndiName" value="ConnectionFactory"/>
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="defaultDestination" ref="destination"/>
<property name="pubSubDomain" value="false"/>
<property name="deliveryPersistent" value="true"/>
<property name="deliveryMode" value="2"/>
</bean>
<bean id="destination" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate" ref="jndiTemplate"/>
<property name="jndiName" value="QUEUE_NAME"/>
</bean>
<!-- Listener Asynchronous -->
<bean id="queueListener" class="com.my.queueListener"/>
<jms:listener-container concurrency="5-10">
<jms:listener destination="QUEUE_NAME" ref="queueListener"/>
</jms:listener-container>
I had set the pubSubDomain to true and it worked (strange but true). Anyhow now I want to connect to a topic. I set the pubSubDomain as true and still it gives me an error saying
WARNING: Setup of JMS message listener invoker failed for destination 'Activate_NTD' - trying to recover. Cause: There is no queue with name TOPIC_NAME
I know HornetQ behaves a bit differently as specified here:
exact example for JMS Topic in HornetQ
but I am unable to figure out what to do and how to get this working.
Set the destination-type on the container that defaults to queue, something like
<jms:listener-container destination-type="topic" concurrency="5-10">
<jms:listener destination="TOPIC_NAME" ref="topicListener"/>
</jms:listener-container>

Categories