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

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.

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 add a Spring Boot HealthIndicator to an Imap receiver IntegrationFlow

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.

How to build a nonblocking Consumer when using AsyncRabbitTemplate with Request/Reply Pattern

I'm new to rabbitmq and currently trying to implement a nonblocking producer with a nonblocking consumer. I've build some test producer where I played around with typereference:
#Service
public class Producer {
#Autowired
private AsyncRabbitTemplate asyncRabbitTemplate;
public <T extends RequestEvent<S>, S> RabbitConverterFuture<S> asyncSendEventAndReceive(final T event) {
return asyncRabbitTemplate.convertSendAndReceiveAsType(QueueConfig.EXCHANGE_NAME, event.getRoutingKey(), event, event.getResponseTypeReference());
}
}
And in some other place the test function that gets called in a RestController
#Autowired
Producer producer;
public void test() throws InterruptedException, ExecutionException {
TestEvent requestEvent = new TestEvent("SOMEDATA");
RabbitConverterFuture<TestResponse> reply = producer.asyncSendEventAndReceive(requestEvent);
log.info("Hello! The Reply is: {}", reply.get());
}
This so far was pretty straightforward, where I'm stuck now is how to create a consumer which is non-blocking too. My current listener:
#RabbitListener(queues = QueueConfig.QUEUENAME)
public TestResponse onReceive(TestEvent event) {
Future<TestResponse> replyLater = proccessDataLater(event.getSomeData())
return replyLater.get();
}
As far as I'm aware, when using #RabbitListener this listener runs in its own thread. And I could configure the MessageListener to use more then one thread for the active listeners. Because of that, blocking the listener thread with future.get() is not blocking the application itself. Still there might be the case where all threads are blocking now and new events are stuck in the queue, when they maybe dont need to. What I would like to do is to just receive the event without the need to instantly return the result. Which is probably not possible with #RabbitListener. Something like:
#RabbitListener(queues = QueueConfig.QUEUENAME)
public void onReceive(TestEvent event) {
/*
* Some fictional RabbitMQ API call where i get a ReplyContainer which contains
* the CorrelationID for the event. I can call replyContainer.reply(testResponse) later
* in the code without blocking the listener thread
*/
ReplyContainer replyContainer = AsyncRabbitTemplate.getReplyContainer()
// ProcessDataLater calls reply on the container when done with its action
proccessDataLater(event.getSomeData(), replyContainer);
}
What is the best way to implement such behaviour with rabbitmq in spring?
EDIT Config Class:
#Configuration
#EnableRabbit
public class RabbitMQConfig implements RabbitListenerConfigurer {
public static final String topicExchangeName = "exchange";
#Bean
TopicExchange exchange() {
return new TopicExchange(topicExchangeName);
}
#Bean
public ConnectionFactory rabbitConnectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("localhost");
return connectionFactory;
}
#Bean
public MappingJackson2MessageConverter consumerJackson2MessageConverter() {
return new MappingJackson2MessageConverter();
}
#Bean
public RabbitTemplate rabbitTemplate() {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(rabbitConnectionFactory());
rabbitTemplate.setMessageConverter(producerJackson2MessageConverter());
return rabbitTemplate;
}
#Bean
public AsyncRabbitTemplate asyncRabbitTemplate() {
return new AsyncRabbitTemplate(rabbitTemplate());
}
#Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
#Bean
Queue queue() {
return new Queue("test", false);
}
#Bean
Binding binding() {
return BindingBuilder.bind(queue()).to(exchange()).with("foo.#");
}
#Bean
public SimpleRabbitListenerContainerFactory myRabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(rabbitConnectionFactory());
factory.setMaxConcurrentConsumers(5);
factory.setMessageConverter(producerJackson2MessageConverter());
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return factory;
}
#Override
public void configureRabbitListeners(final RabbitListenerEndpointRegistrar registrar) {
registrar.setContainerFactory(myRabbitListenerContainerFactory());
}
}
I don't have time to test it right now, but something like this should work; presumably you don't want to lose messages so you need to set the ackMode to MANUAL and do the acks yourself (as shown).
UPDATE
#SpringBootApplication
public class So52173111Application {
private final ExecutorService exec = Executors.newCachedThreadPool();
#Autowired
private RabbitTemplate template;
#Bean
public ApplicationRunner runner(AsyncRabbitTemplate asyncTemplate) {
return args -> {
RabbitConverterFuture<Object> future = asyncTemplate.convertSendAndReceive("foo", "test");
future.addCallback(r -> {
System.out.println("Reply: " + r);
}, t -> {
t.printStackTrace();
});
};
}
#Bean
public AsyncRabbitTemplate asyncTemplate(RabbitTemplate template) {
return new AsyncRabbitTemplate(template);
}
#RabbitListener(queues = "foo")
public void listen(String in, Channel channel, #Header(AmqpHeaders.DELIVERY_TAG) long tag,
#Header(AmqpHeaders.CORRELATION_ID) String correlationId,
#Header(AmqpHeaders.REPLY_TO) String replyTo) {
ListenableFuture<String> future = handleInput(in);
future.addCallback(result -> {
Address address = new Address(replyTo);
this.template.convertAndSend(address.getExchangeName(), address.getRoutingKey(), result, m -> {
m.getMessageProperties().setCorrelationId(correlationId);
return m;
});
try {
channel.basicAck(tag, false);
}
catch (IOException e) {
e.printStackTrace();
}
}, t -> {
t.printStackTrace();
});
}
private ListenableFuture<String> handleInput(String in) {
SettableListenableFuture<String> future = new SettableListenableFuture<String>();
exec.execute(() -> {
try {
Thread.sleep(2000);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
future.set(in.toUpperCase());
});
return future;
}
public static void main(String[] args) {
SpringApplication.run(So52173111Application.class, args);
}
}

How to Bind Publisher Message to Custom class in my Receiver with out #Payload

In my application i am publishing Message from one of FileProcess service(which will process CSV file and convert that to CSVPojo and publish to queue by using RabbitTemplate.
rabbitTemplate.convertAndSend("spring-boot-rabbitmq-BulkSolve.async_BulkSolve_Msg", "BulkSolve_GeneralrequestQueue", pojo);
I have another service BusinessProcess service that have to Listen to this queue and get messages and do some business process on those messages.To do this we intended to do this using SpringBatch, so i created a job which will listen queue and process. The trigger point for the job is as below.
#EnableRabbit
public class Eventscheduler {
#Autowired
Job csvJob;
#Autowired
private JobLauncher jobLauncher;
//#Scheduled(cron="0 */2 * ? * *")
#RabbitListener(queues ="BulkSolve_GeneralrequestQueue")
public void trigger(){
Reader.batchstatus=false;
Map<String,JobParameter> maps= new HashMap<String,JobParameter>();
maps.put("time", new JobParameter(System.currentTimeMillis()));
JobParameters jobParameters = new JobParameters(maps);
JobExecution execution=null;
try {
//JobLauncher jobLauncher = new JobLauncher();
execution=jobLauncher.run(csvJob, jobParameters);
} catch (JobExecutionAlreadyRunningException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JobRestartException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JobInstanceAlreadyCompleteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JobParametersInvalidException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("JOB Executed:" + execution.getStatus());
}
}
so my job will trigger when there is a msg published to this Queue. And after my job triggered in my job iam getting exception in my reader. In reader i am getting below exception.
org.springframework.amqp.support.converter.MessageConversionException: failed to resolve class name [com.comcast.FileProcess.Pojo.CSVPojo]
Below is my Reader class which i used to read message as receiver.
#Component
public class Reader extends AmqpItemReader<List<RequestPojo>>{
#Autowired
#Qualifier("rabbitTemplate")
private RabbitTemplate rabbitTemplate;
public static boolean batchstatus;
private List<RequestPojo> reqList = new ArrayList<RequestPojo>();
/* #Autowired
private SimpleMessageListenerContainer messagelistener;*/
public Reader(AmqpTemplate rabbitTemplate) {
super(rabbitTemplate);
// TODO Auto-generated constructor stub
}
List<RequestPojo> msgList = new ArrayList<RequestPojo>();
#Override
#SuppressWarnings("unchecked")
public List<RequestPojo> read() {
if(!batchstatus){
RequestPojo msg=(RequestPojo)rabbitTemplate.receiveAndConvert("BulkSolve_GeneralrequestQueue");
//return (List<RequestPojo>) rabbitTemplate.receive();
System.out.println("I am inside Reader" );
msgList.add((RequestPojo) msg);
//Object result = rabbitTemplate.receiveAndConvert();
batchstatus=true;
return msgList;
}
return null;
}
}
Here Consumer is Getting the Pojo class with its pacakge name from publisher.
I am able to consume Messages by using #Payload below is my code using which successfully consumed messages(below is that code) but i want to consume messages by using RabbitTemplate.receiveAndConvert("QueueName") Which i showed in my Reader class.
/*Below code sucesfully consumed messages from receiver side using #Payload*/
#RabbitHandler
#RabbitListener(containerFactory = "simpleMessageListenerContainerFactory", queues ="BulkSolve_GeneralrequestQueue")
public void subscribeToRequestQueue(#Payload RequestPojo sampleRequestMessage, Message message) throws InterruptedException {
System.out.println(sampleRequestMessage.toString());
}
Can any one help on this to resolve my error to consume published messages from Receiver using RabbitTemplate.receiveAndConvert("QueueName")
As per your suggestion i have made some configuration changes as below for Jackson2JsonMessageConverter to bind the message to my custom class RequestPojo as per below but it still not bind the message to my custom class. Can you please suggest me what i am doing wrong here and suggest me what to do to make it work.
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(jsonMessageConverter());
return template;
}
#Bean
public MessageConverter jsonMessageConverter() {
return jsonCustomMessageConverter();
}
#Bean
public Jackson2JsonMessageConverter jsonCustomMessageConverter() {
Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter();
jsonConverter.setClassMapper(classMapper());
return jsonConverter;
}
#Bean
public DefaultClassMapper classMapper() {
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = new HashMap<String, Class<?>>();
idClassMapping.put("RequestPojo", RequestPojo.class);
// idClassMapping.put("bar", Bar.class);
classMapper.setIdClassMapping(idClassMapping);
return classMapper;
}
Changed as per your suggestion but getting below error .
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]
Caused by: org.springframework.amqp.support.converter.MessageConversionException: Cannot handle message
... 15 common frames omitted
Caused by: org.springframework.messaging.converter.MessageConversionException: Cannot convert from [[B] to [com.comcast.BusinessProcess.Pojos.RequestPojo] for GenericMessage [payload=byte[230], headers={amqp_receivedDeliveryMode=PERSISTENT, amqp_receivedRoutingKey=BulkSolve_SummaryrequestQueue, amqp_contentEncoding=UTF-8, amqp_receivedExchange=spring-boot-rabbitmq-BulkSolve_summary.async_BulkSolve_Msg, amqp_deliveryTag=1, amqp_consumerQueue=BulkSolve_SummaryrequestQueue, amqp_redelivered=false, id=d79db57c-3cd4-d104-a343-9373215400b8, amqp_consumerTag=amq.ctag-sYwuWA5pmN07gnEUTO-p6A, contentType=application/json, __TypeId__=com.comcast.FileProcess.Pojo.CSVPojo, timestamp=1535661077865}]
at org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver.resolveArgument(PayloadArgumentResolver.java:142) ~[spring-messaging-4.3.11.RELEASE.jar!/:4.3.11.RELEASE]
at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:112) ~[spring-messaging-4.3.11.RELEASE.jar!/:4.3.11.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:135) ~[spring-messaging-4.3.11.RELEASE.jar!/:4.3.11.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:107) ~[spring-messaging-4.3.11.RELEASE.jar!/:4.3.11.RELEASE]
at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:49) ~[spring-rabbit-1.7.4.RELEASE.jar!/:na]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:126) ~[spring-rabbit-1.7.4.RELEASE.jar!/:na]
... 14 common frames omitted
Below is my RabbitConfiguration class.
#Configuration("asyncRPCConfig")
#EnableScheduling
#EnableRabbit
public class RabbitMqConfiguration {
public static String replyQueue;
public static String directExchange;
public static String requestRoutingKey;
public static String replyRoutingKey;
//public static final int threads=3;
/*#Bean
public ExecutorService executorService(){
return Executors.newFixedThreadPool(threads);
}*/
/*#Bean
public CsvPublisher csvPublisher(){
return new CsvPublisher();
}
#Bean
public ExcelPublisher excelPublisher(){
return new ExcelPublisher();
}*/
/*#Bean
public GeneralQueuePublisher generalQueuePublisher(){
return new GeneralQueuePublisher();
}
*/
/*#Bean
public SummaryQueuePublisher summaryQueuePublisher(){
return new SummaryQueuePublisher();
}*/
/*#Bean
public Subscriber subscriber(){
return new Subscriber();
}*/
/*#Bean
public Subscriber1 subscriber1(){
return new Subscriber1();
}
#Bean
public Subscriber2 subscriber2(){
return new Subscriber2();
}
#Bean
public RestClient restClient(){
return new RestClient();
}*/
/*#Bean
public SubscriberGeneralQueue1 SubscriberGeneralQueue1(){
return new SubscriberGeneralQueue1();
}*/
/*#Bean
public SubscriberSummaryQueue1 SubscriberSummaryQueue1(){
return new SubscriberSummaryQueue1();
}*/
#Bean
public Eventscheduler Eventscheduler(){
return new Eventscheduler();
}
#Bean
public Executor taskExecutor() {
return Executors.newCachedThreadPool();
}
#Bean
public SimpleRabbitListenerContainerFactory simpleMessageListenerContainerFactory(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setTaskExecutor(taskExecutor());
configurer.configure(factory, connectionFactory);
return factory;
}
/* #Bean
public SimpleRabbitListenerContainerFactory simpleMessageListenerContainerFactory_Summary(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setTaskExecutor(taskExecutor());
configurer.configure(factory, connectionFactory);
return factory;
}*/
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(jsonMessageConverter());
return template;
}
#Bean
public MessageConverter jsonMessageConverter() {
return jsonCustomMessageConverter();
}
#Bean
public Jackson2JsonMessageConverter jsonCustomMessageConverter() {
Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter();
jsonConverter.setClassMapper(classMapper());
return jsonConverter;
}
#Bean
public DefaultClassMapper classMapper() {
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = new HashMap<String, Class<?>>();
idClassMapping.put("com.comcast.FileProcess.Pojo.CSVPojo", RequestPojo.class);
// idClassMapping.put("bar", Bar.class);
classMapper.setIdClassMapping(idClassMapping);
return classMapper;
}
#Bean
public Queue replyQueueRPC() {
return new Queue("BulkSolve_GeneralreplyQueue");
}
#Bean
public Queue requestQueueRPC() {
return new Queue("BulkSolve_GeneralrequestQueue");
}
/*below are the newly added method for two other queues*/
#Bean
public Queue summaryreplyQueueRPC() {
return new Queue("BulkSolve_SummaryreplyQueue");
}
#Bean
public Queue summaryrequestQueueRPC() {
return new Queue("BulkSolve_SummaryrequestQueue");
}
#Bean
public SimpleMessageListenerContainer rpcGeneralReplyMessageListenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory);
simpleMessageListenerContainer.setQueues(replyQueueRPC());
simpleMessageListenerContainer.setTaskExecutor(taskExecutor());
//simpleMessageListenerContainer.setMessageListener(listenerAdapter1);
return simpleMessageListenerContainer;
}
#Bean
public SimpleMessageListenerContainer rpcSummaryReplyMessageListenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory);
simpleMessageListenerContainer.setQueues(summaryreplyQueueRPC());
//simpleMessageListenerContainer.setMessageListener(listenerAdapter2);
simpleMessageListenerContainer.setTaskExecutor(taskExecutor());
return simpleMessageListenerContainer;
}
/* #Bean
#Qualifier("listenerAdapter1")
MessageListenerAdapter listenerAdapter1(SubscriberGeneralQueue1 generalReceiver) {
return new MessageListenerAdapter(generalReceiver, "receivegeneralQueueMessage");
}*/
/* #Bean
#Qualifier("listenerAdapter2")
MessageListenerAdapter listenerAdapter2(SubscriberSummaryQueue1 summaryReceiver) {
return new MessageListenerAdapter(summaryReceiver, "receivesummaryQueueMessage");
}*/
#Bean
public RequestPojo requestPojo(){
return new RequestPojo();
}
/* #Bean
#Qualifier("asyncGeneralRabbitTemplate")
public AsyncRabbitTemplate asyncGeneralRabbitTemplate(ConnectionFactory connectionFactory) {
AsyncRabbitTemplate asyncGeneralRabbitTemplate = new AsyncRabbitTemplate(rabbitTemplate(connectionFactory),
rpcGeneralReplyMessageListenerContainer(connectionFactory),
"spring-boot-rabbitmq-BulkSolve.async_BulkSolve_Msg" + "/" + "BulkSolve_GeneralreplyQueue");
AsyncRabbitTemplate at = new AsyncRabbitTemplate(connectionFactory, "spring-boot-rabbitmq-examples.async_rpc", "rpc_request", "replyQueueRPC","replyQueueRPC");
return asyncGeneralRabbitTemplate;
}
template defined for other 2 queues
#Bean
#Qualifier("asyncSummaryRabbitTemplate")
public AsyncRabbitTemplate asyncSummaryRabbitTemplate(ConnectionFactory connectionFactory) {
AsyncRabbitTemplate asyncSummaryRabbitTemplate = new AsyncRabbitTemplate(rabbitTemplate(connectionFactory),
rpcSummaryReplyMessageListenerContainer(connectionFactory),
"spring-boot-rabbitmq-BulkSolve_summary.async_BulkSolve_Msg" + "/" + "BulkSolve_SummaryreplyQueue");
AsyncRabbitTemplate at = new AsyncRabbitTemplate(connectionFactory, "spring-boot-rabbitmq-examples.async_rpc", "rpc_request", "replyQueueRPC","replyQueueRPC");
return asyncSummaryRabbitTemplate;
}*/
#Bean
public DirectExchange directExchange() {
return new DirectExchange("spring-boot-rabbitmq-BulkSolve.async_BulkSolve_Msg");
}
//Added new exchange
#Bean
public DirectExchange directExchange1() {
return new DirectExchange("spring-boot-rabbitmq-BulkSolve_summary.async_BulkSolve_Msg");
}
#Bean
public List<Binding> bindings() {
return Arrays.asList(
BindingBuilder.bind(requestQueueRPC()).to(directExchange()).with("BulkSolve_GeneralrequestQueue"),
BindingBuilder.bind(replyQueueRPC()).to(directExchange()).with("BulkSolve_GeneralreplyQueue"),
BindingBuilder.bind(summaryrequestQueueRPC()).to(directExchange1()).with("BulkSolve_SummaryrequestQueue"),
BindingBuilder.bind(summaryreplyQueueRPC()).to(directExchange1()).with("BulkSolve_SummaryreplyQueue")
);
}
}
//Below is my Reader class
#Component
public class Reader extends AmqpItemReader<List<RequestPojo>>{
#Autowired
#Qualifier("rabbitTemplate")
private RabbitTemplate rabbitTemplate;
public static boolean batchstatus;
private List<RequestPojo> reqList = new ArrayList<RequestPojo>();
/* #Autowired
private SimpleMessageListenerContainer messagelistener;*/
public Reader(AmqpTemplate rabbitTemplate) {
super(rabbitTemplate);
// TODO Auto-generated constructor stub
}
List<RequestPojo> msgList = new ArrayList<RequestPojo>();
#Override
#SuppressWarnings("unchecked")
public List<RequestPojo> read() {
if(!batchstatus){
RequestPojo msg=(RequestPojo)rabbitTemplate.receiveAndConvert("BulkSolve_GeneralrequestQueue" );
//rabbitTemplate.receiveandco
//return (List<RequestPojo>) rabbitTemplate.receive();
System.out.println("I am inside Reader" + msg);
msgList.add(msg);
//Object result = rabbitTemplate.receiveAndConvert();
batchstatus=true;
return msgList;
}
return null;
}
}
Below is my Trigger point code which trigger Job when message is there in queue.
#EnableRabbit
public class Eventscheduler {
#Autowired
Job csvJob;
#Autowired
private JobLauncher jobLauncher;
//#Scheduled(cron="0 */2 * ? * *")
#RabbitListener(queues ="BulkSolve_GeneralrequestQueue")
public void trigger(){
Reader.batchstatus=false;
Map<String,JobParameter> maps= new HashMap<String,JobParameter>();
maps.put("time", new JobParameter(System.currentTimeMillis()));
JobParameters jobParameters = new JobParameters(maps);
JobExecution execution=null;
try {
//JobLauncher jobLauncher = new JobLauncher();
execution=jobLauncher.run(csvJob, jobParameters);
} catch (JobExecutionAlreadyRunningException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JobRestartException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JobInstanceAlreadyCompleteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JobParametersInvalidException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("JOB Executed:" + execution.getStatus());
}
}
Thanks.
As you noticed there is slight difference between subscribeToRequestQueue(#Payload RequestPojo sampleRequestMessage) and rabbitTemplate.receiveAndConvert("BulkSolve_GeneralrequestQueue"). And right, that one is exactly that #Payload RequestPojo which is missed in the raw receiveAndConvert(). Therefore when this method is performed there is no target type to consult for the expected conversion. This way we just fallback to whatever we have in the incoming message. In your case that is a __TypeId__ header with the source type from the producer com.comcast.FileProcess.Pojo.CSVPojo.
If you really would like to the enforce the conversion on the consumer side into the RequestPojo, you need to consider to use an overloaded receiveAndConvert variant:
/**
* 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. Requires a
* {#link org.springframework.amqp.support.converter.SmartMessageConverter}.
*
* #param queueName the name of the queue to poll
* #param type the type to convert to.
* #param <T> the type.
* #return a message or null if there is none waiting
* #throws AmqpException if there is a problem
* #since 2.0
*/
<T> T receiveAndConvert(String queueName, ParameterizedTypeReference<T> type) throws AmqpException;

Aspect around methods annotated with #RabbitHandler

What I want is to have an aspect around all methods annotated with #RabbitHandler so that AssertionErrors won't kill the handler thread.
I just want to wrap them inside RuntimeExceptions and rethrow.
Motivation: there is additional error handling that I want to use which works well except for these AssertionErrors.
I could add a try-catch for AssertionErrors in each method but there are too many places and instead I was thinking of using aspects.
#Aspect
public class RabbitAssertionErrorHandlerAspect {
#Around("#annotation(org.springframework.amqp.rabbit.annotation.RabbitHandler)")
public Object intercept(ProceedingJoinPoint pjp) throws Throwable {
try {
return pjp.proceed();
} catch (AssertionError e) {
throw new RuntimeException(e);
}
}
}
All nice and elegant but it doesn't get called. I assume this has something to do with the way these methods are discovered in the first place.
Any reasonable workarounds anyone sees?
The #RabbitListener on the class with those #RabbitHandlers can be configured with:
/**
* Set an {#link org.springframework.amqp.rabbit.listener.RabbitListenerErrorHandler}
* to invoke if the listener method throws an exception.
* #return the error handler.
* #since 2.0
*/
String errorHandler() default "";
So, consider to go the custom RabbitListenerErrorHandler way instead.
It works with Spring AOP...
#SpringBootApplication
public class So48324210Application {
public static void main(String[] args) {
SpringApplication.run(So48324210Application.class, args);
}
#Bean
public MethodInterceptor interceptor() {
return i -> {
try {
System.out.println("here");
return i.proceed();
}
catch (AssertionError e) {
throw new RuntimeException(e);
}
};
}
#Bean
public static BeanNameAutoProxyCreator proxyCreator() {
BeanNameAutoProxyCreator pc = new BeanNameAutoProxyCreator();
pc.setBeanNames("foo");
pc.setInterceptorNames("interceptor");
return pc;
}
#Bean
public Foo foo() {
return new Foo();
}
public static class Foo {
#RabbitListener(queues = "one")
public void listen(Object in) {
System.out.println(in);
}
}
}
or, as Artem said, a custom error handler will work too.

Resources