I have a simple publisher, which sends messages to a queue.
<int:channel id="publishChannel"/>
<int-jms:outbound-channel-adapter channel="publishChannel" destination="testQueue" session-transacted="true"/>
#Publisher(channel = "publishChannel")
public String sendMessage (String text) {
return text;
}
If the broker crashes, the publisher throws an MessageHandlingException.
Is it possible to block the publisher, till the broker is available again or to make a periodic retry?
You can add a retry advice to the outbound adapter.
<int-jms:outbound-channel-adapter channel="publishChannel" destination="testQueue" session-transacted="true>
<int:request-handler-advice-chain>
<ref bean="myRetryAdvice" />
</request-handler-advice-chain>
</int-jms:outbound-channel-adapter>
You can configure the advice with a backoff policy (e.g. exponential) and to take some action when retries are exhausted (rather than throwing the final exception).
Related
There is a channel :
<integration:channel id="sampleChannel">
</integration:channel>
And there is a kafka outbound channel with sampleChannel value for channel property :
<int-kafka:outbound-channel-adapter id="kafkaOutboundChannelAdapter"
kafka-template="template"
auto-startup="false"
channel="sampleChannel"
topic="foo"
sync="false"
send-failure-channel="errorChannel"
partition-id-expression="1">
</int-kafka:outbound-channel-adapter>
When message sends and get to sampleChannel, this exception throws :
org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'org.springframework.context.support.ClassPathXmlApplicationContext#1623b78d.sampleChannel'
Why kafka outbound can't take message from the sampleChannel ?
Your auto-startup is set to false, so at the time when Application Context is started there is no subscriber to the sampleChannel.
Set auto-startup to true. Or change your sampleChannel to be pollable of publish-subscribe channel.
I am using Spring Integration with JTA support through Atomikos and JMS bound to different Webshpere MQ and a Oracle Database.
Obviosly for others it seems to be the same thread as in Spring Integration JMS with JTA rollback when message goes to errorChannel but it isn't not at all.
The flow is the following:
message-driven-channel-adapter receive the message within transaction
some transformations
ServiceActivator with deeper business logic
DataBase Update
Everything works really fine expect for one szenario:
If an unchecked exception occurs (maybe due to inkonsistent data which shouldn't be) within the ServiceActivator the message is rethrown and handled within an ErrorHandler (via ErrorChannel).
There - in some cases - the orgininal incoming message should be send to a DeadLetter Queue (Webshere MQ). This is done with outbound-channel-adapter.
See enclosed configuration:
<jms:message-driven-channel-adapter id="midxMessageDrivenAdapter"
channel="midxReplyChannel"
acknowledge="auto"
transaction-manager="transactionManager"
connection-factory="connectionFactory"
concurrent-consumers="1"
error-channel="errorChannel"
max-concurrent-consumers="${cmab.integration.midx.max.consumer}"
idle-task-execution-limit="${cmab.integration.midx.idleTaskExecutionLimit}"
receive-timeout="${cmab.integration.midx.receiveTimeout}"
destination="midxReplyQueue"/>
................
<int:service-activator input-channel="midxReplyProcessChannel" ref="processMidxReplyDbWriter" />
<int:service-activator input-channel="errorChannel" ref="inputErrorHandler" />
<jms:outbound-channel-adapter id="deadLetterOutboundChannelAdapter"
channel="errorDlChannel" destination="deadLetterQueue"
delivery-persistent="true" connection-factory="nonXAConnectionFactory"/>
Some important hints:
message-driven-channel-adapter:
connectionFactory is a MQXAQueueConnectionFactory wihtin an AtomikosConnectionFactoryBean
transaction-manager is Spring JtaTransactionManager
outbound-channel-adapter:
connection-factory is a nonXAConnectionFactory.
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
.....
cachingConnectionFactory.setTargetConnectionFactory(mqQueueConnectionFactory);
return cachingConnectionFactory;
And now the error which I observed:
Although I handled the unchecked exception in my ErrorHandler (ref="inputErrorHandler"; send the message to DeadLetter-Queue) an rollback is initiated and the message-driven-channel-adapter receives the message again and again.
Curious is the fact that indeed the message is delivered to the DeadLetterQueue via the outbound-channel-adapter. The destination (deadLetterQueue) contains the failed messages.
Question: What I'm doing wrong? The failed original incoming message is rolled back although I handled the exception in my ErrorHandler.
Any help really appreciate. Many thanks in advance.
concerning to my comment see enclose code of my InputErrorHandler:
if (throwable instanceof MessageHandlingException) {
MessageHandlingException messageHandlingException = (MessageHandlingException) throwable;
if (messageHandlingException.getCause() != null
&& (messageHandlingException.getCause() instanceof CmabProcessDbException
|| messageHandlingException.getCause() instanceof CmabReplyFormatException)) {
String appMessage = ((CmabException) messageHandlingException.getCause()).getAppMessagesAsString();
LOGGER.error(String.format("cmab rollback exception occured: %s", appMessage));
LOGGER.error("******** initiate rollback *********");
throw throwable.getCause();
} else {
ErrorDto payload = fillMessagePayload(messageHandlingException, throwableClassName);
sendMessageToTargetQueue(payload);
}
As I mentioned the "business" ServiceActivator throws an unchecked exception so in this case the ELSE-Statements are calling.
Within that I build up a Message with MessagBuilder and sends it ti the errorDlChannel (s. above the outbound-channel-adapter!).
private void sendMessageToDeadLetterQueue(Message<?> message, String description, String cause) {
.......
Message<?> mb =
MessageBuilder.withPayload(message.getPayload())
.copyHeaders(message.getHeaders())
.setHeaderIfAbsent("senderObject", this.getClass().getName())
.setHeaderIfAbsent("errorText", description)
.setHeaderIfAbsent("errorCause", errorCause).build();
errorDlChannel.send(mb);
}
That's all. This is for this case the last statement. There is nothing anything else in the main method of my ErrorHandler. No rethrow or other stuff.
So this is the reason of my confusion. For me the exception is handled by sending it to the errorDlChannel (outbound-channel-adapter -> DeadLetterQueue).
I saw the message on the DeadLetter Queue but nevertheless a jta rollback occurs...and IMO this shouldn't bee.
I am using Spring AMQP for processing the messages in RabbitMQ. Below is the issue:
1. (say) there are 3 messages in ready state inside RabbitMQ
2. First one is picked up by MessageListener and starts processing. (say) It ends up throwing an exception
3. In this case, the container remains up but the remaining 2 messages are not processed until i restart the container. Also the first messages stays in unacknowledged state.
Is it the expected behavior? If not, how to make sure that other 2 messages will be processed irrespective first one failed processing?
MQ configuraion:
<rabbit:connection-factory id="connectionFactory" host="localhost" username="guest" password="guest" />
<rabbit:admin connection-factory="connectionFactory" />
<rabbit:listener-container
connection-factory="connectionFactory"
concurrency="1"
acknowledge="auto">
<rabbit:listener queue-names="testQueue" ref="myProcessorListener " />
</rabbit:listener-container>
MessageListener class:
public class MyProcessorListener implements MessageListener{
....
#Override
public void onMessage(Message message) {
try{
...Some logic...
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
The message is redelivered over and over again; in order to reject it (and discard or route to a dead letter queue), you need to throw AmqpRejectAndDontRequeueException or set the container's requeue-rejected property to false. When configuring with Java it's defaultRequeueRejected.
You can also use a custom error handler.
This is all explained in the reference manual.
My poller fetches data from DB and pass it to a service activator.
If any exception happens in the service activator method,i should roll back the data fetched to its previous state and should again send the same data to the service activater only for a specific retry-count(say 3). **Is it possible to do this in a xml configuration. ? For details i will share the poller configurations and the service activator.
poller.xml
<int-jdbc:inbound-channel-adapter id="datachannel"
query="select loyalty_id,process_id,mobile_uid from TBL_RECEIPT where r_cre_time
=(select min(r_cre_time) from TBL_RECEIPT where receipt_status=0)"
data-source="dataSource" max-rows-per-poll="1"
update="update TBL_RECEIPT set receipt_status=11 where process_id in (:process_id)">
<int:poller fixed-rate="5000">
<int:transactional/>
</int:poller>
</int-jdbc:inbound-channel-adapter>
<int:channel id="errors">
<int:queue/>
</int:channel>
<bean id="poller" class="main.java.com.as.poller.PollerService" />
<int:channel id="executerchannel">
<int:dispatcher task-executor="taskExecutor" />
</int:channel>
<task:executor id="taskExecutor" pool-size="2" />
<int:service-activator input-channel="datachannel"
output-channel="executerchannel" ref="poller" method="getRecordFromPoller">
<int:request-handler-advice-chain>
<int:retry-advice recovery-channel="errors" />
</int:request-handler-advice-chain>
</int:service-activator>
<int:service-activator input-channel="executerchannel"
ref="poller" method="getDataFromExecuterChannel">
</int:service-activator>
service activator method
#SuppressWarnings({ "unchecked", "rawtypes" })
#ServiceActivator
public void processMessage(Message message) throws IOException {
int capLimit = Integer.parseInt(env.getProperty("capping_limit"));
List<Map<String, Object>> rows = (List<Map<String, Object>>) message
.getPayload();
for (Map<String, Object> row : rows) {
String loyaltyId = (String) row.get("loyalty_id");
String processId = (String) row.get("process_id");
String xid=(String)row.get("mobile_uid");
i have heard about int:transactional being used in poller configuration.But when i added it,its taking the same record even after successful transaction.(means its getting rolled back everytime).
Can anyone please help me on this ?
You can add a retry request-handler-advice to the service activator.
To make retry stateful (throw the exception so the transaction will rollback) you need to provide a RetryStateGenerator. Otherwise the thread will simply be suspended during the retries.
Regardless of using stateful or stateless retry, you really should use a transactional poller so that the update is only applied after success.
means its getting rolled back everytime
The transactional poller will only rollback if an exception is thrown. So if you're seeing the same row, something must be failing.
Turn on DEBUG logging for all of org.springframework to follow the message flow and transaction activity.
I have this requirement to push messages to a single queue but receive them at more than one place (a java class).
For that I used a topic-exchange and bound it to a queue that receives messages based on different patterns. Now when I try to receive them at the other end the listener-container send the messages to both the listeners rather than bifurcating them based on the pattern decided.
I have attached the configuration and the code below for a quick look
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<rabbit:connection-factory id="connectionFactory" host="localhost" username="guest" password="guest" />
<rabbit:admin connection-factory="connectionFactory" />
<rabbit:template id="pgTemplate" connection-factory="connectionFactory" exchange="PG-EXCHANGE"/>
<rabbit:queue id="pgQueue" />
<rabbit:topic-exchange id="pgExchange" name="PG-EXCHANGE">
<rabbit:bindings>
<rabbit:binding queue="pgQueue" pattern="pg"></rabbit:binding>
<rabbit:binding queue="pgQueue" pattern="txn"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<bean id="pgReceiver" class="com.pg.mq.service.impl.MessageReceiver"/>
<bean id="txnReceiver" class="com.pg.mq.service.impl.MessageReceiver"/>
<rabbit:listener-container id="ListenerContainer" connection-factory="connectionFactory">
<rabbit:listener ref="pgReceiver" queues="pgQueue" method="handleMessage"/>
<rabbit:listener ref="txnReceiver" queues="pgQueue" method="handleMessage"/>
</rabbit:listener-container>
</beans>
Message Sender
public String pushMessagePG(Object object) {
if(object != null && object instanceof Rabbit)
pgTemplate.convertAndSend("pg", object); // send
return null;
}
Message Receivers
PG RECEIVER
public void handleMessage(Rabbit message) {
System.out.println("inside Message Receiver");
System.out.println("Listener received message----->" + message);
}
TXN RECEIVER
public void handleMessage(Rabbit message) {
System.out.println("inside TXN Message Receiver");
System.out.println("Listener received message----->" + message);
}
Calling Code
Sender sender = (Sender) context.getBean("messageSender");
for(int i = 0; i < 30; i++) sender.pushMessagePG(new Rabbit(5));
Thread.sleep(2000);
for(int i = 0; i < 30; i++) sender.pushMessageTXN(new Rabbit(2));
Sorry, but you didn't understand the AMQP specification a bit.
Messages are sent to the exchange by the routingKey
Queues are bound to the exchange by the routingKey or pattern for topic-exchange
Only single consumer can receive a message from queue.
There is no mechanism like distribution in the AMQP. I mean something similar like Topic concept in JMS.
topic-exchange puts messages to all queues which routingKey matches to patterns of the bindings.
This article describes that in the best way.
Since you bind the same queue for both patterns all messages are placed to the same queue.
From other consumers are listen on the queues and just only queues. They knows nothing about the routingKey.
Since you have two listeners for the same queue it isn't surprise that both of them can handle messages from that queue and it is independently of the routingKey.
You really should use different queues for different patterns. Or consider to use Spring Integration with its Message Routing power. You can use the standard AmqpHeaders.RECEIVED_ROUTING_KEY (Spring Integration) header for that case.