How to add a Spring Boot HealthIndicator to an Imap receiver IntegrationFlow - spring-boot

How do you integrate a Spring Boot HealthIndicator with an IntegrationFlow polling email with imap?
I can get exceptions from the IntegrationFlow via the errorChannel but how do I "clear" the exception once the IntegrationFlow starts working again, e.g., after a network outage.
#SpringBootApplication
public class MyApplication {
#Bean
IntegrationFlow mailFlow() {
return IntegrationFlows
.from(Mail.imapInboundAdapter(receiver()).get(),
e -> e.autoStartup(true)
.poller(Pollers.fixedRate(5000)))
.channel(mailChannel()).get();
}
#Bean
public ImapMailReceiver receiver() {
String mailServerPath = format("imaps://%s:%s#%s/INBOX", mailUser,
encode(mailPassword), mailServer);
ImapMailReceiver result = new ImapMailReceiver(mailServerPath);
return result;
}
#Bean
DirectChannel mailChannel() {
return new DirectChannel();
}
#Autowired
#Qualifier("errorChannel")
private PublishSubscribeChannel errorChannel;
#Bean
public IntegrationFlow errorHandlingFlow() {
return IntegrationFlows.from(errorChannel).handle(message -> {
MessagingException ex = (MessagingException) message.getPayload();
log.error("", ex);
}).get();
}
#Bean
HealthIndicator mailReceiverHealthIndicator() {
return () -> {
/*
* How to error check the imap polling ???
*/
return Health.up().build();
};
}
}

I would go with an AtomicReference<Exception> bean and set its value in that errorHandlingFlow. The HealthIndicator impl would consult that AtomicReference to down() when it has a value.
The PollerSpec for the Mail.imapInboundAdapter() could be configured with a ReceiveMessageAdvice:
/**
* Specify AOP {#link Advice}s for the {#code pollingTask}.
* #param advice the {#link Advice}s to use.
* #return the spec.
*/
public PollerSpec advice(Advice... advice) {
Its afterReceive() impl could just clean that AtomicReference up, so your HealthIndicator would return up().
The point is that this afterReceive() is called only when invocation.proceed() doesn't fail with an exception. and it is called independently if there are new messages to process or not.

Related

How to read the messages from rabbitmq other than using listener configuration?

I am attempting to implement Spring Boot API to fetch the RabbitMQ Messages on demand for asynchronous cart notifications in UI. I already have a working implementation with the help of the Registered listener method. But I am looking for an alternative with or without spring.
#Component
public class Receiver {
private CountDownLatch latch = new CountDownLatch(1);
public void receiveMessage(String message) {
System.out.println("Received <" + message + ">");
latch.countDown();
}
public CountDownLatch getLatch() {
return latch;
}
}
Main Class with Reciever Configuration:
#SpringBootApplication
public class MessagingRabbitmqApplication {
static final String topicExchangeName = "spring-boot-exchange";
static final String queueName = "spring-boot";
#Bean
Queue queue() {
return new Queue(queueName, false);
}
#Bean
TopicExchange exchange() {
return new TopicExchange(topicExchangeName);
}
#Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("foo.bar.#");
}
#Bean
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(queueName);
container.setMessageListener(listenerAdapter);
return container;
}
#Bean
MessageListenerAdapter listenerAdapter(Receiver receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(MessagingRabbitmqApplication.class, args).close();
}
}
My Current Implementation Reference is from: https://spring.io/guides/gs/messaging-rabbitmq/
See RabbitTemplate API:
/**
* Receive a message if there is one from a specific queue. Returns immediately,
* possibly with a null value.
*
* #param queueName the name of the queue to poll
* #return a message or null if there is none waiting
* #throws AmqpException if there is a problem
*/
#Nullable
Message receive(String queueName) throws AmqpException;
/**
* Receive a message if there is one from a specific queue and convert it to a Java
* object. Returns immediately, possibly with a null value.
*
* #param queueName the name of the queue to poll
* #return a message or null if there is none waiting
* #throws AmqpException if there is a problem
*/
#Nullable
Object receiveAndConvert(String queueName) throws AmqpException;
And respective docs: https://docs.spring.io/spring-amqp/reference/html/#polling-consumer

How to handle different type of Exception in Spring Integration using java DSL?

I have following simple proxy integration flow. The main task of which is to take request from the proxy send it to the actual endpoint, get the respond and send it back to the client. I would like to handle different type of exceptions.
#SpringBootApplication
#EnableIntegration
public class IntegrationApp {
#Value("${narko.pin}")
private String pinUrl;
public static void main(String[] args) {
SpringApplication.run(MinzdravApplication.class, args);
}
#Bean
public DirectChannel requestPinChannel() {
return new DirectChannel();
}
#Bean
public DirectChannel replyPinChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow httpProxyFlowPin() throws Exception {
return IntegrationFlows
.from(Http.inboundGateway("/narko/api/patient/by-pinpp")
.requestChannel(requestPinChannel())
.mappedRequestHeaders("activityid")
.errorChannel("httpProxyErrorFlow.input")
)
.wireTap(sf->sf.handle(new InwardMessageHandler()))
.enrichHeaders(h -> h.header("Content-Type", "application/json"))
.handle(Http.outboundGateway(pinUrl).charset("utf-8")
.expectedResponseType(String.class))
.get();
}
#Bean
public IntegrationFlow httpProxyErrorFlow() {
return f -> f
.transform(Throwable::getCause)
.<RuntimeException>handle(
(p, h) ->
MessageBuilder.fromMessage(new Message<ErrorDto>() {
final Map<String, Object> headers=new HashMap<>();
#Override
public ErrorDto getPayload() {
if(p instanceof JSONException){
headers.put(HttpHeaders.STATUS_CODE,HttpStatus.BAD_REQUEST);
return new ErrorDto(HttpStatus.BAD_REQUEST.value(),p.getMessage());
}else if(p instanceof MethodNotAllowedException){
headers.put(HttpHeaders.STATUS_CODE,HttpStatus.METHOD_NOT_ALLOWED);
return new ErrorDto(HttpStatus.METHOD_NOT_ALLOWED.value(),p.getMessage());
}
else{
headers.put(HttpHeaders.STATUS_CODE,HttpStatus.INTERNAL_SERVER_ERROR);
return new ErrorDto(HttpStatus.INTERNAL_SERVER_ERROR.value(),p.getMessage());
}
}
#Override
public MessageHeaders getHeaders() {
return new MessageHeaders(headers);
}
})
).transform(Transformers.toJson())
;
}
As you can see the code above I have to check every possible exception type, then form corresponding ErrorDto, which makes the code difficult to maintain. Is it possible to handle them as one can do it with #ControllerAdvice? For instance :
#ControllerAdvice
public class ApiExceptionHandler {
#ExceptionHandler(JSONException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<ApiError> onRuntimeException(JSONException ex) {
ErrorDto apiError = new ErrorDto(HttpStatus.BAD_REQUEST, ex.getMessage(), ex);
return buildResponseEntity(apiError);
}
#ExceptionHandler(MethodNotAllowedException.class)
#ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
public ResponseEntity<ApiError> onIllegalException(MethodNotAllowedException ex) {
ErrorDto apiError = new ErrorDto(HttpStatus.METHOD_NOT_ALLOWED, ex.getMessage(), ex);
return buildResponseEntity(apiError);
}
...
}
Sure! You can do something similar with Spring Integration. See an ErrorMessageExceptionTypeRouter and its Java DSL routeByException():
/**
* Populate the {#link ErrorMessageExceptionTypeRouter} with options from the {#link RouterSpec}.
* Typically, used with a Lambda expression:
* <pre class="code">
* {#code
* .routeByException(r -> r
* .channelMapping(IllegalArgumentException.class, "illegalArgumentChannel")
* .subFlowMapping(MessageHandlingException.class, sf ->
* sf.handle(...))
* )
* }
* </pre>
* #param routerConfigurer the {#link Consumer} to provide {#link ErrorMessageExceptionTypeRouter} options.
* #return the current {#link BaseIntegrationFlowDefinition}.
* #see ErrorMessageExceptionTypeRouter
*/
public B routeByException(
Consumer<RouterSpec<Class<? extends Throwable>, ErrorMessageExceptionTypeRouter>> routerConfigurer) {
https://docs.spring.io/spring-integration/docs/current/reference/html/message-routing.html#router-implementations-exception-router
You also can just throw those exceptions back to the proxy and have that #ControllerAdvice for handling them on the MVC level.

Kafka Consumer Invalid Payload Error Handler

I have the below configuration. When the message is invalid I want to send an email and for errors I want to save it in database. How can I handle this in errorHandler() ?
#Configuration
#EnableKafka
public class KafkaConsumerConfig implements KafkaListenerConfigurer{
#Bean
ErrorHandler errorHandler() {
return new SeekToCurrentErrorHandler((rec, ex) ->
{ dbService.saveErrorMsg(rec); }
,new FixedBackOff(5000, 3)) ;
}
#Override
public void configureKafkaListeners(KafkaListenerEndpointRegistrar registrar) {
registrar.setValidator(this.validator);
}
#KafkaListener(topics = "mytopic", concurrency = "3", groupId = "mytopic-1-groupid")
public void consumeFromTopic1(#Payload #Valid ValidatedClass val, ConsumerRecordMetadata meta) throws Exception
{
dbservice.callDB(val,"t");
}
I presume your emai code is in dbService.saveErrorMsg.
Spring Boot should automatically detect the ErrorHandler #Bean and wire it into the container factory.
See Boot's KafkaAnnotationDrivenConfiguration class and ConcurrentKafkaListenerContainerFactoryConfigurer.

How to bind to topic with Spring AMQP only if exchange exists?

I need to bind a queue to a topic exchange, but:
Only if the topic exists
If the topic exists, use the existing settings (e.g. durable, auto-delete, etc)
Reason is, I need a 3rd party application to create the exchange with whatever settings they want to use, I don't want to modify the topic settings.
I put the code below together by reading RabbitMQ Spring AMQP tutorial. It works, but creates an exchange if doesn't exist.
#Configuration
public class BeanConfiguration {
#Bean
public TopicExchange topic() {
return new TopicExchange("MyTopicExchange", true, false);
}
#Bean
public Queue queue() {
return QueueBuilder.durable("MyQueue").build();
}
#Bean
public Binding binding(TopicExchange topicExchange, Queue queue) {
return BindingBuilder.bind(queue).to(topicExchange).with("purchases.*");
}
}
I found a way by using superclass method setShouldDeclareFalse:
#Bean
public TopicExchange topic() {
TopicExchange topicExchange = new TopicExchange("MyTopicExchange", true, false);
topicExchange.setShouldDeclare(false);
return topicExchange;
}
Skip the exchange declaration bean and ignore the binding declaration failure.
#SpringBootApplication
public class So59994152Application {
public static void main(String[] args) {
SpringApplication.run(So59994152Application.class, args);
}
#Bean
public Queue queue() {
return QueueBuilder.durable("MyQueue").build();
}
#Bean
public Binding binding(Queue queue, AmqpAdmin admin) {
((RabbitAdmin) admin).setIgnoreDeclarationExceptions(true);
return new Binding("MyQueue", DestinationType.QUEUE, "MyTopicExchange", "purchases.*", null);
}
#Bean
public ApplicationRunner runner(CachingConnectionFactory cf) {
return args -> {
cf.createConnection();
cf.destroy();
};
}
}
If you are not using Spring Boot; set the admin property in the admin bean.

Spring-Rabbitmq MessageConverter - not invoking custom object handleMessage

I am implementing a consumer class that binds to fanout exchange in RabbitMQ and receives the message published as json. For some reason, the handleMessage within the Consumer class is not being invoked when its argument is a custom object. Same code works when the handleMessage is changed to take Object. Would appreciate your help in identity the missing piece.
Here is the configuration and consumer classes. This is not a SpringBoot application. My Configuration class has #Configuration annotation and not #SpringBootApplication.
#Bean
public SimpleMessageListenerContainer messageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory());
container.setQueueNames(QUEUE_NAME);
container.setMessageListener(listenerAdapter());
container.setMessageConverter(new Jackson2JsonMessageConverter());
container.setMissingQueuesFatal(false);
return container;
}
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(rabbitConnectionFactory());
}
#Bean
public Queue queue() {
return new Queue(QUEUE_NAME, false, false, false);
}
#Bean
public FanoutExchange exchange() {
return new FanoutExchange(EXCHANGE_NAME, false, false);
}
#Bean
public Binding inboundEmailExchangeBinding() {
return BindingBuilder.bind(queue()).to(exchange());
}
#Bean
public ConnectionFactory rabbitConnectionFactory() {
return new CachingConnectionFactory("localhost");
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(rabbitConnectionFactory());
rabbitTemplate.setExchange(EXCHANGE_NAME);
return rabbitTemplate;
}
#Bean
MessageListenerAdapter listenerAdapter() {
return new MessageListenerAdapter(new Consumer(), "receiveMessage");
}
Here is the consumer ...
public class Consumer {
// This works
/*
public void receiveMessage(Object message) {
System.out.println("Received <" + message + ">");
}
*/
// This does not works, whereas I expect this to work.
public void receiveMessage(CustomObject message) {
System.out.println("Received <" + message + ">");
}
}
where CustomObject class is a plain POJO.
Here is an example of what is being published in RabbitMQ.
{
"state": "stable",
"ip": "1.2.3.4"
}
Its being published as json content-type
exchange.publish(message_json, :content_type => "application/json")
Appreciate all your help in making me understand the problem. Thanks.
The Jackson2JsonMessageConverter needs to be told what object to map the json to.
This can be provided via information in a __TypeId__ header (which would be the case if Spring was on the sending side); the header can either contain the full class name, or a token that is configured to map to the class name.
Or, you need to configure the converter with a class mapper.
For convenience there is a DefaultClassMapper that be configured with your target class:
ClassMapper classMapper = new DefaultClassMapper();
classMapper.setDefaultType(CustomObject.class);
converter.setClassMapper(classMapper);

Resources