Spring JMS onException retry - java

i have a requirement where i need to send a email but if the email server is down or any error occurs while sending a email that need to be retry for a specific number of times
below is my bean properties
#Bean(destroyMethod = "")
public JndiTemplate jndiTemplate() {
Properties environment = new Properties();
environment.put(Context.INITIAL_CONTEXT_FACTORY, env.getProperty("XXXXXX"));
environment.put(Context.PROVIDER_URL, env.getProperty("XXXXXX"));
JndiTemplate jndiTemplate = new JndiTemplate();
jndiTemplate.setEnvironment(environment);
return jndiTemplate;
}
#Bean(destroyMethod = "")
public JndiObjectFactoryBean jmsConnFactory() {
JndiObjectFactoryBean jmsConnFactory = new JndiObjectFactoryBean();
jmsConnFactory.setJndiTemplate(jndiTemplate());
jmsConnFactory.setJndiName(env.getProperty("XXXXX"));
return jmsConnFactory;
}
#Bean(destroyMethod = "")
public JndiObjectFactoryBean jmsDestanation() {
JndiObjectFactoryBean destination = new JndiObjectFactoryBean();
destination.setJndiTemplate(jndiTemplate());
destination.setJndiName(env.getProperty("XXXXXX"));
return destination;
}
#Bean
public JmsTemplate jmsTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setDefaultDestination(jmsDestanation());
jmsTemplate.setConnectionFactory(jmsConnFactory());
return jmsTemplate;
}
#Bean
public JmsReceiver jmsReciver() {
return new JmsReceiver();
}
#Bean
public JmsExceptionListener jmsExceptionListener(){
return new JmsExceptionListener();
}
#Bean
public JmsErrorHandleListener jmsErrorHandleListener(){
return new JmsErrorHandleListener();
}
#Bean
public DefaultMessageListenerContainer jmsQueueListner() {
DefaultMessageListenerContainer listner = new DefaultMessageListenerContainer();
listner.setDestination(jmsDestanation());
listner.setConnectionFactory(jmsConnFactory());
listner.setMessageListener(jmsReciver());
listner.setExceptionListener(jmsExceptionListener());
listner.setErrorHandler(jmsErrorHandleListener());
return listner;
}
and below is my Listener class and error class
public class JmsReceiver implements MessageListener {
#Autowired
JavaMailSender jMailsender;
#Override
public void onMessage(Message message) {
TextMessage text = (TextMessage) message;
ObjectMapper objectMapper = new ObjectMapper();
MimeMessage mimeMessage = jMailsender.createMimeMessage();
try {
JmsMessage inMessage = objectMapper.readValue(text.getText(), JmsMessage.class);
//this is failing and go to the JmsErrorHandleListener
jMailsender.send(mimeMessage);
} catch (JMSException | IOException | MessagingException ex) {
logger.error("Exception on reading message ",ex);
}
}
}
public class JmsErrorHandleListener implements ErrorHandler {
#Override
public void handleError(Throwable t) {
/// not sure how to retry from hear becoz the message was allready read
/// some how i need to inform the weblogic this message was not read yet
}
}
when the message arrives to the onMessage it will throw an error then executes the JmsErrorHandleListener but since the message is already read im not sure how to call the send method again and again

try with below config, spring DMLC manage Exception's to retry MessageListener execution, if jMailsender.send(mimeMessage); fails JmsReceiver.onMessage will be retried 5s later indefintely, see DMLC backoff property
#Bean
public org.springframework.jms.listener.adapter.MessageListenerAdapter jmsReciver() {
return new org.springframework.jms.listener.adapter.MessageListenerAdapter(receiver());
}
#Bean
public JmsReceiver receiver() {
return new JmsReceiver();
}
public class JmsReceiver {
#Autowired
JavaMailSender jMailsender;
#Override
public void onMessage(Message message) throws JMSException {
TextMessage text = (TextMessage) message;
ObjectMapper objectMapper = new ObjectMapper();
MimeMessage mimeMessage = jMailsender.createMimeMessage();
try {
JmsMessage inMessage = objectMapper.readValue(text.getText(), JmsMessage.class);
//this is failing and go to the JmsErrorHandleListener
jMailsender.send(mimeMessage);
} catch (Throwable ex) {
logger.error("Exception on reading message ",ex);
throw new JMSException(ex.getMessage());
}
}
}

Related

Springboot may conflict with integrating netty and rabbitmq at the same time, causes #RabbitListener to not take effect

Some important maven dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.43.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
netty server:
#Component
#Order(value = 0)
#Async
public class UdpServer implements CommandLineRunner {
private Logger logger = LoggerFactory.getLogger(UdpServer.class);
#Value("${udp.port:8896}")
private int udpPort;
#Autowired
UdpServerHandler udpServerHandler;
private EventLoopGroup eventLoopGroup;
#Override
public void run(String... args) {
eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
.option(ChannelOption.SO_REUSEADDR,true)
.handler(udpServerHandler);
logger.info("udp server start udp port: ",udpPort);
ChannelFuture cf2 = bootstrap.bind(udpPort).sync();
cf2.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
}
}
#PreDestroy
public void closeUdp(){
logger.info("udp server closed");
eventLoopGroup.shutdownGracefully();
}
}
UdpServerHandler:
#Component
#SuppressWarnings("all")
public class UdpServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
private Logger logger = LoggerFactory.getLogger(UdpServerHandler.class);
#Autowired
UdpService udpService;
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
try {
Channel channel = ctx.channel();
SocketAddress socketAddress = channel.remoteAddress();
if (socketAddress != null){
String channelKey = socketAddress.toString();
logger.info("channel :{} connected", channelKey);
}
}catch (Exception e){
e.printStackTrace();
}
super.channelActive(ctx);
}
#Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket datagramPacket) {
try {
String sender = datagramPacket.sender().toString();
logger.info(sender);
}catch (Exception e){
e.printStackTrace();
}
try {
udpService.solveData(ctx, datagramPacket);
}catch (Exception e){
e.printStackTrace();
}
}
#Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String channelKey = ctx.channel().remoteAddress().toString();
logger.info("channel :{} closed", channelKey);
super.channelInactive(ctx);
}
}
Rabbitmq config:
#Configuration
public class RabbitConfig {
#Value("${spring.rabbitmq.host}")
private String host;
#Value("${spring.rabbitmq.port}")
private int port;
#Value("${spring.rabbitmq.username}")
private String username;
#Value("${spring.rabbitmq.password}")
private String password;
#Value("${spring.rabbitmq.virtual-host}")
private String virtualHost;
#Autowired
DownlinkUdpConfig downlinkUdpConfig;
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host, port);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost(virtualHost);
connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
return connectionFactory;
}
#Bean
#Primary
#Scope("prototype")
Encoder multipartFormEncoder() {
return new SpringFormEncoder();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public RabbitTemplate rabbitTemplate() {
return new RabbitTemplate(connectionFactory());
}
#Bean
public Queue DownlinkQueue() {
Map<String, Object> arguments=new HashMap<>(3);
arguments.put("x-dead-letter-exchange", downlinkUdpConfig.getDeadExchange());
arguments.put("x-dead-letter-routing-key", downlinkUdpConfig.getDeadRoutingKey());
arguments.put("x-message-ttl",downlinkUdpConfig.getxMessageTtl());
return new Queue(downlinkUdpConfig.getQueue(), true,false,false,arguments);
}
#Bean
public DirectExchange DownlinkExchange() {
return new DirectExchange(downlinkUdpConfig.getExchange());
}
#Bean
public Binding bindingDownlinkQueue() {
return BindingBuilder.bind(DownlinkQueue()).to(DownlinkExchange()).with(downlinkUdpConfig.getRoutingKey());
}
#Bean
public Queue DownlinkDeadQueue(){
return new Queue(downlinkUdpConfig.getDeadQueue(),true);
}
#Bean
public DirectExchange DownlinkDeadExchange(){
return new DirectExchange(downlinkUdpConfig.getDeadExchange(),true,false);
}
#Bean
public Binding bindingDownlinkDead(){
return BindingBuilder.bind(DownlinkDeadQueue()).to(DownlinkDeadExchange()).with(downlinkUdpConfig.getDeadRoutingKey());
}
}
DownlinkUdpListener:
#Component
public class DownlinkUdpListener {
Logger logger = LoggerFactory.getLogger(this.getClass());
#RabbitListener(queues = "downlink_udp_queue")
public void process(Channel channel, Message message) {
//1.get message from rabbitMq
String rabbitMessage = new String(message.getBody());
logger.info("get result from rabbitMq is:{}", rabbitMessage);
//1.1 ack get message
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
logger.info("ack error, message is:{}", rabbitMessage);
}
}
}
The problem now is that I have no consumers in the downlink_udp_queue queue on the rabbitmq management interface. I suspect that the SimpleChannelInboundHandler of UdpServerHandler causes #RabbitListener to not take effect.
I had the same problem and what worked for me was to wait until the Spring Boot server was fully started.
So I implemented a SpringApplicationRunListener. Then in the running method added the startup code for the Netty Server.
For the EventLoop, create a component and refer to it through the context
#Component
public class NioEventLoopGroupPojo {
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
public NioEventLoopGroup getEventLoopGroup() {
return eventLoopGroup;
}
#PreDestroy
public void closeUdp() {
eventLoopGroup.shutdownGracefully();
}
}
Then the SpringApplicationRunListener starts the Netty server
public class SpringBootContextListener implements SpringApplicationRunListener
{
private SpringApplication app;
private String[] args;
public SpringBootContextListener(SpringApplication app, String[] args){
super();
this.app = app;
this.args = args;
}
#Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
// Does Nothing
}
#Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
// Does Nothing
}
#Override
public void contextPrepared(ConfigurableApplicationContext context) {
// Does Nothing
}
#Override
public void contextLoaded(ConfigurableApplicationContext context) {
// Does Nothing
}
#Override
public void started(ConfigurableApplicationContext context) {
// Does Nothing
}
#Override
public void running(ConfigurableApplicationContext context) {
int udpPort = context.getEnvironment().getProperty("udp.port");
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(context.getBean(NioEventLoopGroupPojo.class).getEventLoopGroup())
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
.option(ChannelOption.SO_REUSEADDR,true)
.handler(context.getBean(UdpServerHandler));
ChannelFuture cf2 = bootstrap.bind(udpPort).sync();
cf2.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
// Does Nothing
}
}
Then do not forget to create the spring.factories file under resources/META-INF with your listener:
org.springframework.boot.SpringApplicationRunListener=your-package.SpringBootContextListener

How to bind RabbitListeners to CloudAMQP?

I am currently encountering issues in implementing RabbitMQ messaging between my two applications (web/worker). My RabbitMQ service is hosted on CloudAMQP (Heroku addon). However, any #RabbitListener I declare seems to attempt connecting to localhost rather than the cloud service.
Upon adding the following component into my worker app:
#Service
public class TaskConsumer {
#RabbitListener(queues = "worker.rpc.requests", containerFactory = "rabbitListenerContainerFactory")
public String fetch(String p) {
return p;
}
}
I encounter the following error:
2021-07-05 14:38:23.006 INFO 18840 --- [ntContainer#0-3] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672]
2021-07-05 14:38:32.145 WARN 18840 --- [ntContainer#0-3] o.s.a.r.l.SimpleMessageListenerContainer : Consumer raised exception, processing can restart if the connection factory supports it. Exception summary: org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused: connect
2021-07-05 14:38:32.145 INFO 18840 --- [ntContainer#0-3] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer#32c8d668: tags=[[]], channel=null, acknowledgeMode=AUTO local queue size=0
How may I bind the RabbitListener such that it will connect to the AMQP environment instead? Here is my configuration:
#Configuration
#EnableRabbit
public class RabbitConfig {
protected final String workerQueueName = "worker.rpc.requests";
protected final String routingKeyName = "rpc";
protected final String directExcName = "worker.exchange";
#Bean
public ConnectionFactory connectionFactory() {
final URI ampqUrl;
try {
ampqUrl = new URI(getEnvOrThrow("CLOUDAMQP_URL"));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
final CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setUsername(ampqUrl.getUserInfo().split(":")[0]);
factory.setPassword(ampqUrl.getUserInfo().split(":")[1]);
factory.setHost(ampqUrl.getHost());
factory.setPort(ampqUrl.getPort());
factory.setVirtualHost(ampqUrl.getPath().substring(1));
try {
factory.getRabbitConnectionFactory().setUri(ampqUrl);
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return factory;
}
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
SimpleMessageListenerContainer container = factory
.createListenerContainer();
factory.setConcurrentConsumers(50);
factory.setMaxConcurrentConsumers(100);
container.setStartConsumerMinInterval(3000);
container.setQueues(queue());
factory.setMaxConcurrentConsumers(5);
return factory;
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setRoutingKey(this.workerQueueName);
template.setDefaultReceiveQueue(this.workerQueueName);
return template;
}
#Bean
public Queue queue() {
return new Queue(this.workerQueueName);
}
#Bean
public DirectExchange direct() {
return new DirectExchange(this.directExcName);
}
#Bean
public Binding binding(DirectExchange direct,
Queue autoDeleteQueue1) {
return BindingBuilder.bind(autoDeleteQueue1)
.to(direct)
.with(this.routingKeyName);
}
/**
* Required for executing adminstration functions against an AMQP Broker
*/
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(connectionFactory());
}
private static String getEnvOrThrow(String name) {
final String env = getenv(name);
if (env == null) {
throw new IllegalStateException("Environment variable [" + name + "] is not set.");
}
return env;
}
}

Spring integration - Publisher Confirms with timeout?

This is my current setup:
queue1 and queue2 are marged together with integration flow to channel1:
#Bean
public IntegrationFlow q1f() {
return IntegrationFlows
.from(queue1InboundAdapter())
...
.channel(amqpInputChannel())
.get();
}
#Bean
public IntegrationFlow q2f() {
return IntegrationFlows
.from(queue2InboundAdapter())
...
.channel(amqpInputChannel())
.get();
}
then, everything is aggregated and then confirmed after aggregated message is confirmed by rabbitmq:
#Bean
public IntegrationFlow aggregatingFlow() {
return IntegrationFlows
.from(amqpInputChannel())
.aggregate(...
.expireGroupsUponCompletion(true)
.sendPartialResultOnExpiry(true)
.groupTimeout(TimeUnit.SECONDS.toMillis(10))
.releaseStrategy(new TimeoutCountSequenceSizeReleaseStrategy(200, TimeUnit.SECONDS.toMillis(10)))
)
.handle(amqpOutboundEndpoint())
.get();
}
#Bean
public AmqpOutboundEndpoint amqpOutboundEndpoint() {
AmqpOutboundEndpoint outboundEndpoint = new AmqpOutboundEndpoint(ackTemplate());
outboundEndpoint.setConfirmAckChannel(manualAckChannel());
outboundEndpoint.setConfirmCorrelationExpressionString("#root");
outboundEndpoint.setExchangeName(RABBIT_PREFIX + "ix.archiveupdate");
outboundEndpoint.setRoutingKeyExpression(routingKeyExpression()); //forward using patition id as routing key
return outboundEndpoint;
}
ackTemplate() is set with cf that has springFactory.setPublisherConfirms(true);.
The problem I see is that once in 10 days, there are some messages that are stuck in unacknowledged state in rabbitmq.
My guess is that somehow publish of message is waiting for rabbit to do PUBLISHER CONFIRMS but it never gets it and times out? In this case, I never ACK message in queue1. Is this possible?
So just one more time complete workflow:
[two queues -> direct channel -> aggregator (keeps channel and tag values) -> publish to rabbit -> rabbit returns ACK via publisher confirms -> spring confirms all messages on channel+values that it kept in memory for aggregated message]
I also have my implementation of aggregator (since I need to manually ack messages from both q1 and q2):
public abstract class AbstractManualAckAggregatingMessageGroupProcessor extends AbstractAggregatingMessageGroupProcessor {
public static final String MANUAL_ACK_PAIRS = PREFIX + "manualAckPairs";
private AckingState ackingState;
public AbstractManualAckAggregatingMessageGroupProcessor(AckingState ackingState){
this.ackingState = ackingState;
}
#Override
protected Map<String, Object> aggregateHeaders(MessageGroup group) {
Map<String, Object> aggregatedHeaders = super.aggregateHeaders(group);
List<ManualAckPair> manualAckPairs = new ArrayList<>();
group.getMessages().forEach(m -> {
Channel channel = (Channel)m.getHeaders().get(AmqpHeaders.CHANNEL);
Long deliveryTag = (Long)m.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
manualAckPairs.add(new ManualAckPair(channel, deliveryTag, ackingState));
});
aggregatedHeaders.put(MANUAL_ACK_PAIRS, manualAckPairs);
return aggregatedHeaders;
}
}
UPDATE
This is how rabbit admin looks (2 unacked messages for a long time, and it will not be ACKED untill restart - when it is redelivered):
In Spring AMQP version 2.1 (Spring Integration 5.1), We added a Future<?> and returned message to the CorrelationData to assist with this kind of thing. If you are using an older version, you can subclass CorrelationData (and you'd have to handle setting the future and returned message in your code).
This, together with a scheduled task, can detect missing acks...
#SpringBootApplication
#EnableScheduling
public class Igh2755Application {
public static void main(String[] args) {
SpringApplication.run(Igh2755Application.class, args);
}
private final BlockingQueue<CorrelationData> futures = new LinkedBlockingQueue<>();
#Bean
public ApplicationRunner runner(RabbitTemplate template) {
return args -> {
SuccessCallback<? super Confirm> successCallback = confirm -> {
System.out.println((confirm.isAck() ? "A" : "Na") + "ck received");
};
FailureCallback failureCallback = throwable -> {
System.out.println(throwable.getMessage());
};
// Good - ack
CorrelationData correlationData = new CorrelationData("good");
correlationData.getFuture().addCallback(successCallback, failureCallback);
this.futures.put(correlationData);
template.convertAndSend("", "foo", "data", correlationData);
// Missing exchange nack, no return
correlationData = new CorrelationData("missing exchange");
correlationData.getFuture().addCallback(successCallback, failureCallback);
this.futures.put(correlationData);
template.convertAndSend("missing exchange", "foo", "data", correlationData);
// Missing queue ack, with return
correlationData = new CorrelationData("missing queue");
correlationData.getFuture().addCallback(successCallback, failureCallback);
this.futures.put(correlationData);
template.convertAndSend("", "missing queue", "data", correlationData);
};
}
#Scheduled(fixedDelay = 5_000)
public void checkForMissingAcks() {
System.out.println("Checking pending acks");
CorrelationData correlationData = this.futures.poll();
while (correlationData != null) {
try {
if (correlationData.getFuture().get(10, TimeUnit.SECONDS).isAck()) {
if (correlationData.getReturnedMessage() == null) {
System.out.println("Ack received OK for " + correlationData.getId());
}
else {
System.out.println("Message returned for " + correlationData.getId());
}
}
else {
System.out.println("Nack received for " + correlationData.getId());
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Interrupted");
}
catch (ExecutionException e) {
System.out.println("Failed to get an ack " + e.getCause().getMessage());
}
catch (TimeoutException e) {
System.out.println("Timed out waiting for ack for " + correlationData.getId());
}
correlationData = this.futures.poll();
}
System.out.println("No pending acks, exiting");
}
}
.
Checking pending acks
Ack received OK for good
Nack received for missing exchange
Message returned for missing queue
No pending acks, exiting
With Spring Integration there is a confirmCorrelationExpression which can be used to create the CorrelationData instance.
EDIT
With Spring Integration...
#SpringBootApplication
#EnableScheduling
public class Igh2755Application {
public static void main(String[] args) {
SpringApplication.run(Igh2755Application.class, args);
}
private final BlockingQueue<CorrelationData> futures = new LinkedBlockingQueue<>();
public interface Gate {
void send(#Header("exchange") String exchange, #Header("rk") String rk, String payload);
}
#Bean
#DependsOn("flow")
public ApplicationRunner runner(Gate gate) {
return args -> {
gate.send("", "foo", "good");
gate.send("junque", "rk", "missing exchange");
gate.send("", "junque", "missing queue");
};
}
#Bean
public IntegrationFlow flow(RabbitTemplate template) {
return IntegrationFlows.from(Gate.class)
.handle(Amqp.outboundAdapter(template)
.confirmCorrelationExpression("#correlationCreator.create(#root)")
.exchangeNameExpression("headers.exchange")
.routingKeyExpression("headers.rk")
.returnChannel(returns())
.confirmAckChannel(acks())
.confirmNackChannel(acks()))
.get();
}
#Bean
public MessageChannel acks() {
return new DirectChannel();
}
#Bean
public MessageChannel returns() {
return new DirectChannel();
}
#Bean
public IntegrationFlow ackFlow() {
return IntegrationFlows.from("acks")
/*
* Work around a bug because the correlation data is wrapped and so the
* wrong future is completed.
*/
.handle(m -> {
System.out.println(m);
if (m instanceof ErrorMessage) { // NACK
NackedAmqpMessageException nme = (NackedAmqpMessageException) m.getPayload();
CorrelationData correlationData = (CorrelationData) nme.getCorrelationData();
correlationData.getFuture().set(new Confirm(false, "Message was returned"));
}
else {
((CorrelationData) m.getPayload()).getFuture().set(new Confirm(true, null));
}
})
.get();
}
#Bean
public IntegrationFlow retFlow() {
return IntegrationFlows.from("returns")
.handle(System.out::println)
.get();
}
#Bean
public CorrelationCreator correlationCreator() {
return new CorrelationCreator(this.futures);
}
public static class CorrelationCreator {
private final BlockingQueue<CorrelationData> futures;
public CorrelationCreator(BlockingQueue<CorrelationData> futures) {
this.futures = futures;
}
public CorrelationData create(Message<String> message) {
CorrelationData data = new CorrelationData(message.getPayload());
this.futures.add(data);
return data;
}
}
#Scheduled(fixedDelay = 5_000)
public void checkForMissingAcks() {
System.out.println("Checking pending acks");
CorrelationData correlationData = this.futures.poll();
while (correlationData != null) {
try {
if (correlationData.getFuture().get(10, TimeUnit.SECONDS).isAck()) {
if (correlationData.getReturnedMessage() == null
&& !correlationData.getId().equals("Message was returned")) {
System.out.println("Ack received OK for " + correlationData.getId());
}
else {
System.out.println("Message returned for " + correlationData.getId());
}
}
else {
System.out.println("Nack received for " + correlationData.getId());
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Interrupted");
}
catch (ExecutionException e) {
System.out.println("Failed to get an ack " + e.getCause().getMessage());
}
catch (TimeoutException e) {
System.out.println("Timed out waiting for ack for " + correlationData.getId());
}
correlationData = this.futures.poll();
}
System.out.println("No pending acks, exiting");
}
}
you can declare connection as bean
#Bean
public ConnectionFactory createConnectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("127.0.0.1", 5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
connectionFactory.setPublisherReturns(true);
connectionFactory.setPublisherConfirmType(ConfirmType.SIMPLE);
return connectionFactory;
}
Then RabbitTemplate as
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback(callback);
return rabbitTemplate;
}
Where callback is implementation of ConfirmCallback interface
and while sending you can just wait for confirmation
System.out.println("Sending message...");
rabbitTemplate.convertAndSend(rabbitMQProperties.getEXCHANGENAME(),
rabbitMQProperties.getQUEUENAME(), "hello from rabbit");
rabbitTemplate.waitForConfirms(1);
waitforconfirms will take time in milisecond. I put it as 1 for testing purpose.

Message without handler issue for Apache Qpid in Spring Boot

I am trying to use Apache Qpid through Spring Boot application using Jms Qpid client. I am able to configure it but when I am trying to receive message from the queue, the logger is printing:
Dispatcher(918480905)Received a message(878303980)[1] from queue 1 )without a handler - rejecting(requeue)...
Here is my code:
JmsConfiguration.java
#Configuration
public class JmsConfiguration {
#Primary
#Bean
public Context createContext()
{
Properties properties=new Properties();
System.setProperty("IMMEDIATE_PREFETCH", "true");
Context context=null;
try {
properties.load(this.getClass().getResourceAsStream("application.properties"));
context = new InitialContext(properties);
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return context;
}
#Primary
#Bean
public ConnectionFactory createConnectionFactory(Context context)
{
ConnectionFactory connectionFactory=null;
try {
connectionFactory = (ConnectionFactory) context.lookup("qpidConnectionFactory");
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return connectionFactory;
}
#Primary
#Bean
public Connection jmsConnection(ConnectionFactory connectionFactory) throws Exception
{
Connection connection = connectionFactory.createConnection();
connection.start();
return connection;
}
#Primary
#Bean
public Queue jmsQueue(Context context) throws Exception
{
Queue queue = (Queue) context.lookup("myqueue");
return queue;
}
}
application.properties
java.naming.factory.initial = org.apache.qpid.jndi.PropertiesFileInitialContextFactory
connectionfactory.qpidConnectionFactory = amqp://guest:guest#clientid/?brokerlist='tcp://localhost:5672?maxprefetch='0''
queue.myqueue = queue1
ScheduledTask.java It just run send and receive messages in intervals.
#Component
public class ScheduledTasks
{
Connection connection;
Queue queue;
#Autowired
public ScheduledTasks(Connection connection, Queue queue) {
this.connection=connection;
this.queue=queue;
}
MessageListener messageListener = new MessageListener() {
#Override
public void onMessage(Message message) {
System.out.println("Received id is------>");
System.out.println(message);
}
};
#Scheduled(fixedDelay = 2000)
public void sendMessage() throws Exception
{
Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
Message message=session.createTextMessage();
MessageProducer messageProducer=session.createProducer(queue);
message.setStringProperty("value", "BOOM");
messageProducer.send(message);
session.commit();
messageProducer.close();
//connection.close();
System.out.println("---------------Message Sent");
}
//#JmsListener(destination="queue1")
#Scheduled(initialDelay=5000, fixedDelay = 5000)
public void receiveMessage() throws Exception
{
Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
MessageConsumer messageConsumer = session.createConsumer(queue);
// if(messageConsumer.getMessageListener()==null)
// messageConsumer.setMessageListener(messageListener);
Message message = messageConsumer.receive(3000);
if(message!=null)
System.out.println("----------------->"+message.getStringProperty("value"));
session.commit();
messageConsumer.close();
//connection.close();
System.out.println("--------------->Got Message");
}
}
You create an instance implementing MessageListener but you don't do anything with it.
In Spring you should use DefaultMessageListenerContainer or SimpleMessageListenerContainer from spring-jms and create it as a Spring Bean in the JmsConfiguration class. After setting connection details (ConnectionFactory, Queue, sessionTransacted etc.) you also need to set the JMS MessageListener implementing class.

Mocking constructor and a private function inside it using Mockito

I am in a scenario where I need to Unit test on class which is involving some bean formation and it require real data which I dont have, for more reference Below is the code.
the adapter class which I want to Mock
public class TIBCOAdapter {
public TIBCOAdapter(final GIAFProperties giafProperties) throws Exception {
if (giafProperties != null) {
this.giafProperties = giafProperties;
} else {
LOG.info("Error: No properties found");
}
init();
}
public void init() throws IOException {
factory = initializeQueueConnectionFactory();
requestQueue = initializeRequestQueue();
}
private QueueConnectionFactory initializeQueueConnectionFactory() {
final DurationRecord start = DurationLog.logBefore();
final JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiTemplate(new JndiTemplate(giafProperties.getProperties()));
bean.setJndiName(GIAFPropertyUtil.getPropertyString(giafProperties, "externalJndiName"));
try {
bean.afterPropertiesSet();
} catch (Exception e) {
throw new GIAFRuntimeException(e);
}
final ConnectionFactory targetConnectionFactory = (ConnectionFactory) bean
.getObject();
LOG.info("Got target connection factory: " + targetConnectionFactory);
final MultiCachingConnectionFactory factoryLocal = new MultiCachingConnectionFactory(
targetConnectionFactory, giafProperties);
DurationLog.logAfter(start);
return factoryLocal;
}
private Queue initializeRequestQueue() {
final JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiTemplate(new JndiTemplate(giafProperties.getProperties()));
bean.setJndiName(GIAFPropertyUtil.getPropertyString(giafProperties,
"request-queue"));
try {
bean.afterPropertiesSet();
} catch (Exception e) {
throw new GIAFRuntimeException(e);
}
return (Queue) bean.getObject();
}
}
The actual class where its object is created, which I don't want and that's why I want to mock creation of TIBCOAdapter
public class SomeClass {
public String getResponse(TestClientFilter testClientFilter) throws ICAException {
if (!filterValid(testClientFilter)) {
return null;
}
try {
Properties properties = new Properties(); // Sucess
GIAFProperties giafProperties = new GIAFProperties(properties, null); // sucess
addProperties(properties, testClientFilter); // sucess
TIBCOAdapter tibcoAdapter = new TIBCOAdapter(giafProperties); // ERROR This is the line which I want to mock
return (String) tibcoAdapter.invokeRequestResponse(testClientFilter.getMessage());
} catch (Exception e) {
LOG.error(e.getMessage(), e);
throw new ICAException(e);
}
}
}
and this is my TEST
public class TestClientBusinessTest {
#Mock
private TIBCOAdapter tibco;
#InjectMocks
#Autowired
private SomeClass test;
#BeforeClass
public void setUp() throws NamingException {
MockitoAnnotations.initMocks(this);
}
private String returnStatement;
#Test(dataProvider = "getTestClientResponseBusiness", dataProviderClass = StaticDataProvider.class)
public void getResponse(TestClientFilter testClientFilter) throws Exception {
when(tibco.invokeRequestResponse(Matchers.any(TestClientFilter.class))).thenReturn(new Object());
test.getResponse(testClientFilter);
tibco.invokeRequestResponse(testClientFilter.getMessage());
}
}
These lines of code are making problem from TIBCOAdapters internal functions.
bean.setJndiTemplate(new JndiTemplate(giafProperties.getProperties()));
bean.setJndiName(GIAFPropertyUtil.getPropertyString(giafProperties, "externalJndiName"));

Categories

Resources