MessageHandlingException message in error channel - java

I have written an Exception handler class that looks at the default Spring Integration errorChannel for incoming messages of type Message<TradeProcessingContext> message:
#Slf4j
public class ExceptionHandler {
#Autowired
private ExceptionDAO exceptionDAO;
#ServiceActivator(inputChannel = DEFAULT_ERROR_CHANNEL)
public void handleTradeError(Message<TradeProcessingContext> message) {
TradeProcessingContext tradeProcessingContext = message.getPayload();
if (tradeProcessingContext != null) {
//store in database
}
}
}
I have handler implementations as follows:
#Slf4j
#MessageEndpoint
public class ChildHandler extends ParentHandler {
#Autowired
private SomeDAO someDAO;
#ServiceActivator(inputChannel = INPUT_CHANNEL, outputChannel = DEFAULT_ERROR_CHANNEL)
public Message<TradeProcessingContext> handle(final Event event) {
return process(event);
}
#Override
protected TradeDAO getDAO() {
return someDAO;
}
}
That invokes the parent process()
public abstract class ParentHandler implements Handler {
#Resource
private SomeService service;
public Message<TradeProcessingContext> process(final Event event) {
TradeProcessingContext tradeProcessingContext = new TradeProcessingContext();
//set initial context
try {
List<Trade> trades = getDAO().findByEventId(event.getEventId());
for (Trade trade : trades) {
tradeProcessingContext.setRef(trade.getRef());
Future<TradeProcessingContext> thread = service.doSomething(trade, tradeProcessingContext);
tradeProcessingContext = thread.get();
}
} catch (Exception e) {
return MessageBuilder.withPayload(tradeProcessingContext).build();
}
return null;
}
I understand I can get org.springframework.messaging.MessageHandlingException: nested exception is java.lang.ClassCastException when the type is Message<MessageHandlingException> message in handleTradeError().
How can i improve this method so that such errors are taken care of also or the underlying tradeprocessingContext is also extracted from this type?

The error channel gets an ErrorMessage which has a Throwable payload. Usually the Throwable is a message handling exception with the original message in the failedMessage property and the exception in the cause.

Related

org.springframework.amqp.AmqpException: No method found for class java.util.LinkedHashMap

I have a problem with Spring Boot Rabbit Mq. I have 2 listeners which listen the same queue but handle different objects:
#Service
#RabbitListener(queues = "#{changeDataQueue.name}")
public class CreateDayAheadTradeListener implements DayAheadTradeEventListener<CreateDayAheadTradeRequest> {
#Autowired
private DayAheadTradeRepository repository;
#Override
#RabbitHandler
public void process(final CreateDayAheadTradeRequest event) {
// Do something
}
}
And the next one:
#Service
#RabbitListener(queues = "#{changeDataQueue.name}")
public class UpdateDayAheadTradeListener implements DayAheadTradeEventListener<ModifyDayAheadTradeStatusRequest> {
#Autowired
private DayAheadTradeRepository repository;
#Override
#RabbitHandler
public void process(final ModifyDayAheadTradeStatusRequest event) {
// Do something
}
}
The Rabbit Config is:
#EnableRabbit
#Configuration
public class RabbitMqConfig {
private final String createDayAheadTradesRouting = CreateDayAheadTradeRequest.class
.getAnnotation(Routing.class)
.routingKey();
private final String updateDayAheadTradesRouting = ModifyDayAheadTradeStatusRequest.class
.getAnnotation(Routing.class)
.routingKey();
private final String requestDayAheadTradesRouting = DayAheadTradeRequest.class
.getAnnotation(Routing.class)
.routingKey();
#Bean
public TopicExchange topicExchange() {
return new TopicExchange("schedule"); // exchange name
}
#Bean
public Queue changeDataQueue() {
return QueueBuilder
.durable("dayahead.trades.data") // queue template name
.build();
}
#Bean
public Queue requestQueue() {
return QueueBuilder
.nonDurable("dayahead.trades.request") // queue template name
.exclusive()
.build();
}
#Bean
public Binding createDataBinding(final Queue changeDataQueue, final TopicExchange topicExchange) {
return BindingBuilder
.bind(changeDataQueue)
.to(topicExchange)
.with(createDayAheadTradesRouting);
}
#Bean
public Binding updateDataBinding(final Queue changeDataQueue, final TopicExchange topicExchange) {
return BindingBuilder
.bind(changeDataQueue)
.to(topicExchange)
.with(updateDayAheadTradesRouting);
}
#Bean
public Binding requestBinding(final Queue requestQueue, final TopicExchange topicExchange) {
return BindingBuilder
.bind(requestQueue)
.to(topicExchange)
.with(requestDayAheadTradesRouting);
}
#Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
final ObjectMapper mapper = EventUtils.createMapper();
mapper.registerModule(new MarketdataModule());
return new Jackson2JsonMessageConverter(mapper);
}
}
The problem is that if I create 2 separate queues for different types of objects - everything works fine, but the idea is to use one queue for several types of objects. But I have the following exception:
org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener method 'no match' threw exception
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:219)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandlerAndProcessResult(MessagingMessageListenerAdapter.java:143)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:132)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1569)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1488)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1476)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1467)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1411)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:958)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:908)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:81)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1279)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1185)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: org.springframework.amqp.AmqpException: No method found for class java.util.LinkedHashMap
at org.springframework.amqp.rabbit.listener.adapter.DelegatingInvocableHandler.getHandlerForPayload(DelegatingInvocableHandler.java:149)
at org.springframework.amqp.rabbit.listener.adapter.DelegatingInvocableHandler.invoke(DelegatingInvocableHandler.java:129)
at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:61)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:211)
... 13 more
I can't find any information about it, I hope someone knows about it. Thanks

Java Spring send back message to queue from consumer

I have a service that sends message to rabbitmq and the consumer do some manipulation of the message and re-queue them.
I can successfully send to rabbitmq the initial message but the problem is i cannot resend to rabbitmq any consumed message if the message requires modifications.
#Service
public class MyService {
/**
* The template
*/
#Autowired
private AmqpTemplate amqpTemplate;
private final RabbitMQConfig config;
public void send(String message) {
try {
amqpTemplate.convertAndSend("ex", "r", message);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Then in my config i have setup:
#Bean
public ConnectionFactory connectionFactory() { /* working code */ }
#Bean
public Queue myQueue() { return new Queue("my-queue");
// etc...
#Bean
MessageListenerAdapter myListenerAdapter(MyListener listener) {
return new MessageListenerAdapter(listener, "listener");
}
#Bean
MyListener myListener() {
return new MyListener();
}
then...
public class MyListener {
public void receiveMessage(String message) {
// ... some code
// if message requires modification, then repush
new Repush().push(message);
}
}
I tried to create a new class with new but the "myService" always null
#Component
public class Repush {
#Autowired
private MyService myService;
public void push(String message) {
// myService is null at this point
}
}
Don't use new for bean creation. Spring injects fields only in beans. Your MyListener is a bean. Just add Repush field with #Autowired annotation in this class.
public class MyListener {
#Autowired
private Repush repush;
public void receiveMessage(String message) {
// ... some code
// if message requires modification, then repush
repush.push(message);
}
}
If you declare myService as a bean in the application context as well as Repush as a bean you can then inject it into MyListener using #Autowired.
By creating Repush using new at point-in-time within the listener method, you are not getting a bean that is cognizant of the context you are in.

How do I cover a method being called with MethodInvoker under aspect?

method to be covered for metrics generation:
#Override
#Metric(metricName = "rmq.onMessage", type = { MetricType.METER, MetricType.HISTOGRAM })
public void onMessage(Object messageBytes) {
//some processing
}
Registering Method here:
MessageListenerAdapter listenerAdapter = new MessageListenerAdapter(messageConsumer, "onMessage");
if any messages come in the queue then onMessage will be invoked using:
protected Object invokeListenerMethod(String methodName, Object[] arguments, Message originalMessage)
throws Exception {
try {
MethodInvoker methodInvoker = new MethodInvoker();
methodInvoker.setTargetObject(getDelegate());
methodInvoker.setTargetMethod(methodName);
methodInvoker.setArguments(arguments);
methodInvoker.prepare();
return methodInvoker.invoke();
}
My Aspect:
#Around("#annotation(myAnnotation.graphite.annotations.Metric)")
public Object graphiteAspect(ProceedingJoinPoint joinPoint) {
//metrics reporting done here
}

RabbitListenerTestHarness injects actual objects in listeners

Scenario:
Junit for a microservice which listens to a queue and posts to an exchange in rabbitMQ after data extraction.
Issue:
RabbitListenerTestHarness is creating mock object for the Rabbit
Listener class alone, Actual objects are being instantiated for
Listeners Autowired components
I couldnt find a way to manually inject mock beans into the listener. This causes Junit to post the test messages to the actual queues configured in the microservice during Junit Execution.
Workaround: The only way I could use the rabbit-test project is to configure test exchange for posting the messages during Junit execution.
Query:
I wanted to understand, if there is any way better way of writing Junit for a Rabbit Listener. Also i wanted to understand if there is a way to maually inject mock objects to the Rabbit Listeners autowired components.
Sample code Snippet:
Rabbit Listener Class
#RabbitListener(id = "id", bindings = #QueueBinding(value = #Queue(value = "sampleQueue", durable = "true", autoDelete = "false"),key = "sampleRoutingKey", exchange = #Exchange(value = "sampleExchange", durable = "true", ignoreDeclarationExceptions = "true", type = EXCHANGE_TYPE)))
public void getMessageFromQueue(#Payload EventModel event) throws ListenerExecutionFailedException, JAXBException {
dataExporterService.exportDataAndPostToRabbit(event);
}
Service class
#Autowired
DataExtractorRepository dataExtractorRepository;
#Autowired
DataPublihserRepository dataPublisherRepo;
public void exportDataAndPostToRabbit(EventModel event) throws JAXBException {
dataPublisherRepo.sendMessageToExchange(dataExtractorRepository.extractOrderData(event), exchangeName, routingKeyValue);
}
DataPublihserRepository has rabbitTemplate internally Autowired. DataExtractorRepository connects to DB internally for retriving the message.
Test class
#Autowired
private RabbitListenerTestHarness harness;
#Autowired
private RabbitTemplate rabbitTemplate;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
DataExporterController = this.harness.getSpy("id");
}
#Test
public void shouldReceiveMessage() throws Exception {
LatchCountDownAndCallRealMethodAnswer answer = new LatchCountDownAndCallRealMethodAnswer(1);
doAnswer(answer).when(DataExporterController).getMessageFromQueue(any(EventModel.class));
rabbitTemplate.convertAndSend("sampleExchange", "sampleRoutingKey", createMessage());
assertTrue(answer.getLatch().await(10, TimeUnit.SECONDS));
verify(DataExporterController, times(1)).getMessageFromQueue(any(OrderEventsModel.class));
verify(orderDataExporterController, times(1)).getMessageFromQueue(any(OrderEventsModel.class));
}
private Message createMessage() {
String inputObject = "{\"id\":12345}";
MessageProperties props = MessagePropertiesBuilder.newInstance().setContentType(MessageProperties.CONTENT_TYPE_JSON).build();
return new Message(inputObject.getBytes(), props);
}
The harness is intended as a mechanism to verify that the listener received the data in an integration test. To unit test a listener, invoke its onMessage Method.
For example, using Mockito, given
public class MyListener {
#Autowired
private SomeService service;
#RabbitListener(id = "myListener", queues = "foo")
public void listen(Foo foo) {
this.service.process(foo);
}
}
and
public interface SomeService {
void process(Foo foo);
}
then
#RunWith(SpringRunner.class)
public class So53136882ApplicationTests {
#Autowired
private RabbitListenerEndpointRegistry registry;
#Autowired
private SomeService service;
#Test
public void test() throws Exception {
SimpleMessageListenerContainer container = (SimpleMessageListenerContainer) this.registry
.getListenerContainer("myListener");
ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener();
Message message = MessageBuilder.withBody("{\"bar\":\"baz\"}".getBytes())
.andProperties(MessagePropertiesBuilder.newInstance()
.setContentType("application/json")
.build())
.build();
listener.onMessage(message, mock(Channel.class));
verify(this.service).process(new Foo("baz"));
}
#Configuration
#EnableRabbit
public static class config {
#Bean
public ConnectionFactory mockCf() {
return mock(ConnectionFactory.class);
}
#Bean
public MessageConverter converter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(mockCf());
factory.setMessageConverter(converter());
factory.setAutoStartup(false);
return factory;
}
#Bean
public MyListener myListener() {
return new MyListener();
}
#Bean
public SomeService service() {
return mock(SomeService.class);
}
}
}
Notice that the container factory does not start the listener container.
For testing publishing, inject a mock RabbitOperations which is implemented by RabbitTemplate.
For example, given
public class SomeServiceImpl implements SomeService {
#Autowired
private RabbitOperations rabbitOperations;
#Override
public void process(Foo foo) {
this.rabbitOperations.convertAndSend(
"someExchange", "someRoutingKey", new Foo(foo.getBar().toUpperCase()));
}
}
and
#Bean
public SomeService service() {
return new SomeServiceImpl();
}
#Bean
public RabbitOperations rabbitTemplate() {
return mock(RabbitOperations.class);
}
then
#Test
public void test() throws Exception {
SimpleMessageListenerContainer container = (SimpleMessageListenerContainer) this.registry
.getListenerContainer("myListener");
ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener();
Message message = MessageBuilder.withBody("{\"bar\":\"baz\"}".getBytes())
.andProperties(MessagePropertiesBuilder.newInstance()
.setContentType("application/json")
.build())
.build();
listener.onMessage(message, mock(Channel.class));
verify(this.rabbitTemplate).convertAndSend("someExchange", "someRoutingKey", new Foo("BAZ"));
}

Could not inject Spring service into akka service

I have Spring service, which is actually actor, it is received info, but I cant pass it to another Spring service, because injection fails.
#Service("mailContainer")
#Scope("prototype")
#Component
public class MailContainer extends UntypedActor {
private final LoggingAdapter LOG = Logging.getLogger(getContext().system(), this);
private Mail value;
private List<Mail> mailList = new ArrayList<Mail>();
private Integer size;
#Autowired
#Qualifier("springService")
private SpringService springService;
//#Autowired
public void setSpringService(SpringService springService) {
this.springService = springService;
}
public MailContainer(Mail value) {
this.value = value;
}
#Override
public void onReceive(Object message) throws Exception {
// LOG.debug("+ MailContainer message: {} ", message);
if (message instanceof Mail) {
value = (Mail) message;
System.out.println("MailContainer get message with id " + value.getId());
System.out.println("With time " + value.getDateSend());
//getSender().tell(value, getSelf()); //heta uxarkum
//this.saveIt(value);
springService.add(value);
}
}
and second service
#Service("springService")
//#Component
#Scope("session")
public class SpringService {
private List<Mail> mailList = new ArrayList<Mail>();
public void add(Mail mail) {
System.out.println("Saving mail from Spring " +mail.getId());
mailList.add(mail);
}
public List<Mail> getMailList() {
return mailList;
}
}
Spring config, this is from akka spring example
#Configuration
//#EnableScheduling
//EnableAsync
#ComponentScan(basePackages = {"com"}, excludeFilters = {
#ComponentScan.Filter(Configuration.class)})
//#ImportResource("classpath:META-INF/spring/spring-data-context.xml")
//#EnableTransactionManagement
//#EnableMBeanExport
//#EnableWebMvc
public class CommonCoreConfig {
// the application context is needed to initialize the Akka Spring Extension
#Autowired
private ApplicationContext applicationContext;
/**
* Actor system singleton for this application.
*/
#Bean
public ActorSystem actorSystem() {
ActorSystem system = ActorSystem.create("AkkaJavaSpring");
// initialize the application context in the Akka Spring Extension
SpringExtProvider.get(system).initialize(applicationContext);
return system;
}
}
So, how I can inject just another Spring service?????????
Based on our discussions, I think it is due to the way you create the MailContainer actor. You aren't using the SpringExtProvider and instead are using Props.create directly. This means that Spring doesn't get the opportunity to perform dependency injection on your new actor.
Try changing this code:
#Override
public void preStart() throws Exception {
System.out.println("Mail collector preStart: {} ");
getContext().actorOf(Props.create(MailContainer.class, result), "one");
}
to use the the SpringExtProvider like this:
#Override
public void preStart() throws Exception {
System.out.println("Mail collector preStart: {} ");
getContext().actorOf(SpringExtProvider.get(getContext().system()).props("mailContainer"), "one");
}
This way you are asking the Spring extension to create the new actor and inject any required dependecnies.

Categories