Spring AMQP - Return Exception Message From RabbitListenerErrorHandler - spring

I'm trying to handle exception with an implementation of RabbitListenerErrorHandler in my project which uses Spring AMQP.
Here is how I defined my consumer:
#RabbitListener(queues = "inqueue", autoStartup = "true", concurrency = "3", returnExceptions = "true", errorHandler = "customRabbitListenerErrorHandler")
Here is my customRabbitListenerErrorHandler:
#Override
public Object handleError(org.springframework.amqp.core.Message message, Message<?> message1, ListenerExecutionFailedException e) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
return MessageBuilder.withBody(objectMapper.writeValueAsString(myCustomData).getBytes()).andProperties(e.getFailedMessage().getMessageProperties()).build();
}
My problem is that I can't deliver this exception message to consumer side. I'm getting this exception:
Caused by: org.springframework.amqp.AmqpException: Cannot determine ReplyTo message property value: Request message does not contain reply-to property, and no default response Exchange was set.
From this message I understood that there is no replyTo property in my messageProperties but I don't know how can get/found it. How can I send this exception message to consumer side?

The error handler should not return a org.springframework.amqp.core.Message message, it should just return myCustomData.
However, that should not prevent finding the replyTo header; please provide an MCRE so I can see what's wrong.

Related

spring amqp (rabbitmq) and sending to DLQ when exception occurs

I am using org.springframework.boot:spring-boot-starter-amqp:2.6.6 .
According to the documentation, I set up #RabbitListener - I use SimpleRabbitListenerContainerFactory and the configuration looks like this:
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ObjectMapper om) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
factory.setConcurrentConsumers(rabbitProperties.getUpdater().getConcurrentConsumers());
factory.setMaxConcurrentConsumers(rabbitProperties.getUpdater().getMaxConcurrentConsumers());
factory.setMessageConverter(new Jackson2JsonMessageConverter(om));
factory.setAutoStartup(rabbitProperties.getUpdater().getAutoStartup());
factory.setDefaultRequeueRejected(false);
return factory;
}
The logic of the service is to receive messages from rabbitmq, contact an external service via the rest API (using rest template) and put some information into the database based on the results of the response (using spring data jpa). The service implemented it successfully, but during testing it ran into problems that if any exceptions occur during the work of those thrown up the stack, the message is not sent to the configured dlq, but simply hangs in the broker as unacked. Can you please tell me how you can tell spring amqp that if any error occurs, you need to redirect the message to dlq?
The listener itself looks something like this:
#RabbitListener(
queues = {"${rabbit.updater.consuming.queue.name}"},
containerFactory = "rabbitListenerContainerFactory"
)
#Override
public void listen(
#Valid #Payload MessageDTO message,
Channel channel,
#Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag
) {
log.debug(DebugMessagesConstants.RECEIVED_MESSAGE_FROM_QUEUE, message, deliveryTag);
messageUpdater.process(message);
channel.basicAck(deliveryTag, false);
log.debug(DebugMessagesConstants.PROCESSED_MESSAGE_FROM_QUEUE, message, deliveryTag);
}
In rabbit managment it look something like this:
enter image description here
and unacked will hang until the queue consuming application stops
See error handling documentation: https://docs.spring.io/spring-amqp/docs/current/reference/html/#annotation-error-handling.
So, you just don't do an AcknowledgeMode.MANUAL and rely on the Dead Letter Exchange configuration for those messages which are rejected in case of error.
Or try to use a this.channel.basicNack(deliveryTag, false, false) in case of messageUpdater.process(message); exception...

AWS SQS Consumer not consuming messages

I am using the spring-cloud-starter-aws-messaging in my Spring Boot application and #sqslistener to consume SQS messages. my consumer weirdly stops getting messages out of nowhere and the ApproximateNumberOfMessagesVisible gradually increases triggering a CloudWatch alarm. I could see no error logs that are generated before it stops getting any more messages. Am I missing something?
#SqsListener(value = "${sqs.queue.url.indexSavedSetQueue}",
deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
public void listenIndexSavedSetEvent(#NonNull String message) throws IOException {
log.info("Index saved set event received, message: {}", message);
IndexSavedSetPayloadDto indexSavedSetPayloadDto = objectMapper
.readValue(message, IndexSavedSetPayloadDto.class);
String setName = indexSavedSetPayloadDto.getSetName();
indexerService.indexSet(setName);
}

Value {} for parameter ReceiptHandle is invalid. Reason: The receipt handle has expired

I have a FIFO queue in SQS, where I keep a JSON with some information to be read and processed by a service in Spring Boot.
I have verified that the queue is showing an error message [The title of the issue] and is not processing some messages.
Follow my code:
#JmsListener(destination = "document_analysis.fifo")
public void documentAnalysis(#payload String document) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
DocumentAnalysisDTO documentDTO = objectMapper.readValue(document, DocumentAnalysisDTO.class);
this.clientService.prepareCustomerDocument(documentDTO);
}

Testing error handling with direct channels and source with poller

I have a following flow implemented in Spring Integration DSL:
Take feed from HTTP
Enrich errorChannel header (point of handling all exception downstream here).
Transform message to be a Collection
Split Collection into sepearate messages
Send each message to next processing channels
#Bean
public IntegrationFlow inboundHttpFlow(
Puller puller,
HeaderEnricher errorHandlingChannelHeaderEnricher,
FeedTransformer feedTransformer,
MessageChannel outputFeedIntegrationChannel
) {
final Consumer<SourcePollingChannelAdapterSpec> pollingSpec = spec ->
spec
.poller(Pollers.cron(SCHEDULE_EVERY_HALF_MINUTE)
.errorChannel(INBOUND_ERROR_CHANNEL));
return IntegrationFlows
.from(puller, pollingSpec)
.enrichHeaders(errorHandlingChannelHeaderEnricher)
.transform(feedTransformer)
.split()
.channel(outputFeedIntegrationChannel)
.get();
}
Where my errorHandlingChannelHeaderEnricher is:
#Component
public class ErrorHandlingChannelHeaderEnricher implements HeaderEnricher {
#Override
public void accept(HeaderEnricherSpec spec) {
spec.header(
MessageHeaders.ERROR_CHANNEL,
INBOUND_ERROR_CHANNEL,
true
);
}
}
When feedTransformer throws an exception in working app then it goes to set errorChannel as expected. But I don't know how to write a test to test if thrown exception goes to errorChannel defined in header?
When I'm trying to simulate it in test given way, it doesn't work because exception is thrown back into caller instead of errorChannel:
// given
Throwable transformerException = new IllegalStateException();
when(feedTransformerMock.apply(any())).thenThrow(transformerException);
// when
var testFeedMessage = MessageBuilder
.withPayload(pullerResult)
.build();
inboundHttpFlow.getInputChannel().send(testFeedMessage); // excetpion returns to caller here
// then
verify(errorHandlerSpy).accept(transformerException);
And exception is typical:
org.springframework.integration.transformer.MessageTransformationException: Failed to transform Message; nested exception is org.springframework.messaging.MessageHandlingException: nested exception is java.lang.IllegalStateException, failedMessage=GenericMessage [payload=test-payload, headers={errorChannel=inboundErrorChannel, id=f77a6a01-9bca-5af3-8352-7edb4c5e94b0, timestamp=1556019833867}]
, failedMessage=GenericMessage [payload=test-payload, headers={errorChannel=inboundErrorChannel, id=f77a6a01-9bca-5af3-8352-7edb4c5e94b0, timestamp=1556019833867}]
I assume that because of DirectChannel and lack of poller in this test example in compare to real flow (with poller).
Is there any way to simulate that throwing exception and checking if it really goes to errorChannel defined in header?
It's not clear what you are trying to test.
Why do you need to test the framework?
You don't need to enrich the headers since you already have an error channel on the poller.
You are correct; sending to a DirectChannel will result in the exception being thrown to the caller.
If you really want to test the framework, mock the Puller instead of sending to the input channel.

Solace Session Recovery , message redelivery

I am using Spring DefaultMessageListenerContainer and JMS Message Listener to consume messages from Solace Queue. Client Acknowledgement is set to true.
In case of exception messages remain in the queue since they were not acknowledged and these are not redelivered. New messages that were pumped post exception are processed.
Have read about using session.recover but how do we get handle to session . Also tried setting maxredelivery to 3 . But not working.
public void onMessage(Message message) {
String text = null;
ALNTLogger.trace(CLAZZ_NAME, "onMessage()", "Message Received:");
try {
TextMessage textMessage = (TextMessage) message;
text = textMessage.getText();
ALNTLogger.trace(CLAZZ_NAME, "onMessage()", "Message Received: " + text);
Document xmlDocument = parseXml(text);
Map < String, String > values = getValues(xmlDocument);
saveValues(values);
message.acknowledge();
} catch (Exception ex) {
ALNTLogger.error(CLAZZ_NAME, "onMessage()", "Failed to process message:" + text);
throw new RuntimeException(ex);
}
}
Any help will be appreciated
It is expected that the message is not redelivered when using CLIENT_ACKNOWLEDGE acknowledgement mode with a DefaultMessageListenerContainer.
The Spring documentation states the following:
The listener container offers the following message acknowledgment
options:
"sessionAcknowledgeMode" set to "AUTO_ACKNOWLEDGE" (default):
Automatic message acknowledgment before listener execution; no
redelivery in case of exception thrown.
"sessionAcknowledgeMode" set
to "CLIENT_ACKNOWLEDGE": Automatic message acknowledgment after
successful listener execution; no redelivery in case of exception
thrown.
"sessionAcknowledgeMode" set to "DUPS_OK_ACKNOWLEDGE": Lazy
message acknowledgment during or after listener execution; potential
redelivery in case of exception thrown.
"sessionTransacted" set to
"true": Transactional acknowledgment after successful listener
execution; guaranteed redelivery in case of exception thrown.
You can use the last option, transactional acknowledgements, in order to have the message redelivered when the onMessage() method does not return normally.

Resources