I am currently facing a problem which only happens sometimes and is very hard to reproduce. Therefore I have problems actually creating a compelling test case.
Our setup looks like this:
spring integration with spring boot
rabbitmq listener
custom bus which manages transaction aware messages
The setup is not the newest anymore and probably a lot of code could be replaced by more idiomatic spring wiring.
Though this is the exception I am getting when the service is started:
Dispatcher has no subscribers, failedMessage=GenericMessage [payload=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage#2ba0efb2, headers={id=f750a792-6b01-16d3-8206-6e553a03f8fa, type=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage, amqp_deliveryMode=PERSISTENT, timestamp=1505797680967}], failedMessage=GenericMessage [payload=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage#2ba0efb2, headers={id=f750a792-6b01-16d3-8206-6e553a03f8fa, type=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage, amqp_deliveryMode=PERSISTENT, timestamp=1505797680967}]
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:93)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:423)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:373)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:105)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.convertAndSend(AbstractMessageSendingTemplate.java:143)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.convertAndSend(AbstractMessageSendingTemplate.java:135)
at org.springframework.integration.gateway.MessagingGatewaySupport.send(MessagingGatewaySupport.java:392)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invokeGatewayMethod(GatewayProxyFactoryBean.java:477)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.doInvoke(GatewayProxyFactoryBean.java:429)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invoke(GatewayProxyFactoryBean.java:420)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy223.send(Unknown Source)
at com.foobar.library.messaging.bus.TransactionAwareBus.afterCommit(TransactionAwareBus.java:50)
at org.springframework.transaction.support.TransactionSynchronizationUtils.invokeAfterCommit(TransactionSynchronizationUtils.java:133)
at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerAfterCommit(TransactionSynchronizationUtils.java:121)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerAfterCommit(AbstractPlatformTransactionManager.java:958)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:803)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
at com.foobar.service.greencheckout.silo.domain.modifyingservice.TransitionService$$EnhancerBySpringCGLIB$$de36730c.execute(<generated>)
at com.foobar.service.greencheckout.silo.domain.statemachine.OrderFsm.invokeCreationalCommandMethod(OrderFsm.java:380)
at com.foobar.service.greencheckout.silo.domain.statemachine.OrderFsm.lambda$allowCreational$1(OrderFsm.java:345)
at akka.japi.pf.FSMStateFunctionBuilder$2.apply(FSMStateFunctionBuilder.java:80)
at akka.japi.pf.FSMStateFunctionBuilder$2.apply(FSMStateFunctionBuilder.java:77)
at akka.japi.pf.CaseStatement.apply(CaseStatements.scala:18)
at scala.PartialFunction$class.applyOrElse(PartialFunction.scala:123)
at akka.japi.pf.CaseStatement.applyOrElse(CaseStatements.scala:13)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:167)
at scala.PartialFunction$class.applyOrElse(PartialFunction.scala:123)
at akka.japi.pf.CaseStatement.applyOrElse(CaseStatements.scala:13)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:167)
at scala.PartialFunction$class.applyOrElse(PartialFunction.scala:123)
at akka.japi.pf.CaseStatement.applyOrElse(CaseStatements.scala:13)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:167)
at scala.PartialFunction$class.applyOrElse(PartialFunction.scala:123)
at akka.japi.pf.CaseStatement.applyOrElse(CaseStatements.scala:13)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:167)
at akka.actor.FSM$class.processEvent(FSM.scala:663)
at akka.actor.AbstractFSM.processEvent(AbstractFSM.scala:36)
at akka.actor.FSM$class.akka$actor$FSM$$processMsg(FSM.scala:657)
at akka.actor.FSM$$anonfun$receive$1.applyOrElse(FSM.scala:651)
at akka.actor.Actor$class.aroundReceive(Actor.scala:497)
at akka.actor.AbstractFSM.aroundReceive(AbstractFSM.scala:36)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:526)
at akka.actor.ActorCell.invoke(ActorCell.scala:495)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257)
at akka.dispatch.Mailbox.run(Mailbox.scala:224)
at akka.dispatch.Mailbox.exec(Mailbox.scala:234)
at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
Caused by: org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers, failedMessage=GenericMessage [payload=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage#2ba0efb2, headers={id=f750a792-6b01-16d3-8206-6e553a03f8fa, type=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage, amqp_deliveryMode=PERSISTENT, timestamp=1505797680967}]
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:154)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:121)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:89)
... 58 more
The code to set this up looks like:
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.expression.ExpressionParser;
import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.ExecutorChannel;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
import org.springframework.messaging.SubscribableChannel;
import java.util.concurrent.Executors;
#Configuration
public class MessagingOutboundConfiguration
{
#Autowired
private AmqpTemplate amqpTemplate;
#Autowired
private Exchange publisherExchange;
#Autowired
private AmqpAdmin amqpAdmin;
#Autowired
private Exchange errorExchange;
#Autowired
private Queue errorQueue;
#Autowired
private BeanFactory beanFactory;
#Autowired
private ExpressionParser expressionParser;
#Autowired
private MessagingSettings messagingSettings;
#Bean
#DependsOn({"connectionFactory", "consumer"})
public AsynchronousBus asyncBus(SubscribableChannel amqpOutboundChannel) throws Exception
{
GatewayProxyFactoryBean factoryBean = new GatewayProxyFactoryBean(AsynchronousBus.class);
factoryBean.setBeanFactory(beanFactory);
factoryBean.setDefaultRequestChannel(amqpOutboundChannel);
factoryBean.afterPropertiesSet();
return (AsynchronousBus) factoryBean.getObject();
}
#Bean
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public TransactionAwareBus transactionAwareBus()
{
return new TransactionAwareBus();
}
/**
* Channel from message bus to the outbound channel adapter
*/
#Bean
public SubscribableChannel amqpOutboundChannel(HeaderChannelInterceptor headerChannelInterceptor)
{
DirectChannel channel = new DirectChannel();
channel.setComponentName("amqp-outbound-channel");
channel.addInterceptor(headerChannelInterceptor);
return channel;
}
/**
* Outbound Channel Adapter
*/
#Bean
public AmqpOutboundEndpoint endpoint(SubscribableChannel confirmationChannel)
{
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper();
String[] allowedHeaders = new String[]{"*"};
headerMapper.setRequestHeaderNames(allowedHeaders);
headerMapper.setReplyHeaderNames(allowedHeaders);
AmqpOutboundEndpoint endpoint = new PhasedAmqpOutboundEndpoint(amqpTemplate);
endpoint.setHeaderMapper(headerMapper);
endpoint.setExchangeName(publisherExchange.getName());
endpoint.setRoutingKeyExpression(expressionParser.parseExpression("headers.type"));
endpoint.setConfirmCorrelationExpression(expressionParser.parseExpression("#this"));
if (messagingSettings.getPublisherConfirmations()) {
endpoint.setConfirmNackChannel(confirmationChannel);
endpoint.setConfirmAckChannel(new NullChannel());
}
return endpoint;
}
#Bean
public HeaderChannelInterceptor headerChannelInterceptor()
{
return new HeaderChannelInterceptor();
}
/**
* Shovels messages from channel to outbound channel adapter
*/
#Bean
public EventDrivenConsumer consumer(SubscribableChannel amqpOutboundChannel, AmqpOutboundEndpoint endpoint)
{
final EventDrivenConsumer consumer = new EventDrivenConsumer(amqpOutboundChannel, endpoint);
consumer.setBeanName("amqp-outbound-consumer");
return consumer;
}
/**
* Outbound Channel Adapter
*/
#Bean
public AmqpOutboundEndpoint errorEndpoint()
{
amqpAdmin.declareBinding(BindingBuilder.bind(errorQueue).to(errorExchange).with("#").noargs());
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper();
String[] allowedHeaders = new String[1];
allowedHeaders[0] = "*";
headerMapper.setRequestHeaderNames(allowedHeaders);
AmqpOutboundEndpoint endpoint = new PhasedAmqpOutboundEndpoint(amqpTemplate);
endpoint.setHeaderMapper(headerMapper);
endpoint.setExchangeName(errorExchange.getName());
endpoint.setRoutingKeyExpression(expressionParser.parseExpression("headers.routing"));
return endpoint;
}
#Bean
public EventDrivenConsumer errorConsumer(SubscribableChannel errorChannel, AmqpOutboundEndpoint errorEndpoint)
{
final EventDrivenConsumer consumer = new EventDrivenConsumer(errorChannel, errorEndpoint);
consumer.setBeanName("amqp-error-consumer");
return consumer;
}
#Bean
public SubscribableChannel errorChannel()
{
DirectChannel channel = new DirectChannel();
channel.setComponentName("amqp-error-channel");
return channel;
}
#Bean
public SubscribableChannel confirmationChannel()
{
ExecutorChannel channel = new ExecutorChannel(Executors.newSingleThreadExecutor());
channel.setComponentName("amqp-confirmation-channel");
return channel;
}
}
From my understanding:
This message normally means (especially in the shutdown case) that beans are not correctly wired or that the context does not know the best order for stopping them.
What I do not understand: What's wrong with my setup? :(
UPDATE:
As you can see the code is called from inside an actor. The actor system is configured this way:
#Bean
#DependsOn({"asyncBus", "prototypedTransactionAwareBus", "transactionAwareBus", "syncBus"})
public SpringActorSystem actorSystem() throws Exception
{
String profile = environment.getActiveProfiles()[0];
ActorSystem system = ActorSystem.create(akkaSettings.getSystemName(), akkaConfiguration(profile));
if ("testing".equals(profile)) {
Cluster.get(system).joinSeedNodes(Lists.newArrayList(Cluster.get(system).selfAddress()));
}
if ("kubernetes".equals(profile)) {
joinKubernetesSeedNodes(system);
}
SpringExtension.SpringExtProvider.get(system).initialize(context);
return new SpringActorSystem(system);
}
And the SpringActorSystem:
public class SpringActorSystem implements ApplicationListener<ContextClosedEvent>
{
private static final Logger LOGGER = LoggerFactory.getLogger(SpringActorSystem.class);
private final ActorSystem actorSystem;
public SpringActorSystem(ActorSystem actorSystem)
{
this.actorSystem = actorSystem;
}
public ActorSystem system()
{
return actorSystem;
}
#Override
public void onApplicationEvent(ContextClosedEvent event)
{
final Cluster cluster = Cluster.get(this.actorSystem);
cluster.leave(cluster.selfAddress());
LOGGER.info("SpringActorSystem shutdown initiated");
this.actorSystem.terminate();
try {
Await.result(actorSystem.whenTerminated(), Duration.create(10, TimeUnit.SECONDS));
} catch (Exception e) {
LOGGER.info("Exception while waiting for termination", e);
}
LOGGER.info("SpringActorSystem shutdown finished");
}
}
Well, I think
SpringExtension.SpringExtProvider.get(system).initialize(context);
in the initializing phase is really too early.
Consider to implement SmartLifecycle for your SpringActorSystem to mover that initialize(context) to the start() method.
Related
I am trying to using Spring MQTT Integration to build a client that is subscribe to MQTT broker. The code works as expected, no issues. I am struggling configuring it so that when the connection is lost, it subscribes automatically. What is happening now, is that when it disconnects, the connection is established but no is not subscribed anymore to my topic.
What should I do to capture the event correctly, and resubscribe again when connection is lost?
Here is my configuration
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mqtt.core.ConsumerStopAction;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
#Configuration
public class MqttBeans {
Logger logger = LoggerFactory.getLogger(MqttBeans.class);
#Bean
public MqttConnectOptions mqttConnectOptions() {
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[] { "ssl://URL:8883" });
options.setUserName("ubidot_bridge");
String pass = "PASS";
options.setPassword(pass.toCharArray());
options.setCleanSession(false);
options.setAutomaticReconnect(true);
options.setConnectionTimeout(30);
options.setKeepAliveInterval(90);
options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);
return options;
}
#Bean
public MqttPahoClientFactory mqttClientFactory(MqttConnectOptions options) {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions( options );
factory.setConsumerStopAction(ConsumerStopAction.UNSUBSCRIBE_NEVER);
logger.info("Reconnected to the broker");
return factory;
}
#Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
#Bean
public MqttPahoMessageDrivenChannelAdapter mqttPahoMessageDrivenChannelAdapterConfig(MqttConnectOptions options) {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("ubidot_bridge_in",
mqttClientFactory(options), "#");
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(2);
adapter.setOutputChannel(mqttInputChannel());
logger.info("Setting up inbound channel");
return adapter;
}
#Bean
public MessageProducer inbound(MqttPahoMessageDrivenChannelAdapter adapter) {
return adapter;
}
#Bean
#ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler handler() {
logger.info("Setting up msg receiver handler");
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
logger.info("Msg received .. Topic: " + topic);
logger.info("Payload " + message.getPayload());
System.out.println();
}
};
}
#Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound( MqttConnectOptions options ) {
// clientId is generated using a random number
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler("ubidot_bridge_out", mqttClientFactory(options));
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("#");
messageHandler.setDefaultRetained(false);
return messageHandler;
}
}
Thank you in advance for the help
T.
I am trying to use rabbitmq #RabbitListener annotation in my methods so that whenever any message arrives, my method can get auto executed. Based on the official documentation provided here, I made the folowing config class
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class RabbitConfiguration {
public static final String QUEUE_NAME="myQueue";
public static final String EXCHANGE_NAME="my_EXCHANGE";
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setMaxConcurrentConsumers(5);
return factory;
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory =
new CachingConnectionFactory("localhost");
return connectionFactory;
}
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(connectionFactory());
}
#Bean
public RabbitTemplate rabbitTemplate() {
return new RabbitTemplate(connectionFactory());
}
#Bean
public Queue myQueue() {
return new Queue(QUEUE_NAME);
}
#Bean
MessageListenerContainer messageListenerContainer() {
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
simpleMessageListenerContainer.setConnectionFactory(connectionFactory());
simpleMessageListenerContainer.setQueues(myQueue());
simpleMessageListenerContainer.setMessageListener(new RabbitMQListner());
return simpleMessageListenerContainer;
}
#Bean
FanoutExchange exchange() {
return new FanoutExchange(EXCHANGE_NAME);
}
#Bean
Binding binding(Queue queue, FanoutExchange exchange) {
return BindingBuilder.bind(queue).to(exchange);
}
#Bean
MessagingService messagingService(){
return new MessagingService();
}
}
Then from my service class I used like this:-
#Component
public class MessagingService {
public void send(String msg){
ApplicationContext context =
new AnnotationConfigApplicationContext(RabbitConfiguration.class);
AmqpTemplate template = context.getBean(AmqpTemplate.class);
template.convertAndSend(QUEUE_NAME,"Hello from template "+msg);
}
#RabbitListener(queues=QUEUE_NAME)
private void receiveMessage(String order) {
System.out.println("Hello"+order);
}
The send method is working fine. But the receiveMessage method does not print the expected output. It should print the message as soon as it arrives in the queue. Instead, When I tried to apply #EnableRabbit annotation in my configuration file, I got java.lang.ArrayStoreException exception on runtime. The method receiveMessage does not even get executed.
You can ignore typo.
You need a method annotated with #RabbitHandler as well.
Example :
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class Consumer {
#RabbitListener(queues = "myQueue")
#RabbitHandler
public void handle(String msg) {
System.out.println("RCVD :: " + msg);
}
}
I have a spring boot application that uses spring-JMS. Is there any way to tell the test method to wait the jms lister util it finishes executing without using latches in the actual code that will be tested?
Here is the JMS listener code:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.jms.Message;
import javax.jms.QueueSession;
#Component
public class MyListener {
#Autowired
MyProcessor myProcessor;
#JmsListener(destination = "myQueue", concurrency = "1-4")
private void onMessage(Message message, QueueSession session) {
myProcessor.processMessage(message, session);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.jms.Message;
import javax.jms.QueueSession;
#Component
public class MyProcessor {
public void processMessage(Message msg, QueueSession session) {
//Here I have some code.
}
}
import org.apache.activemq.command.ActiveMQTextMessage;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.QueueSession;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
#SpringBootTest
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
public class IntegrationTest {
#Autowired
private JmsTemplate JmsTemplate;
#Test
public void myTest() throws JMSException {
Message message = new ActiveMQTextMessage();
jmsTemplate.send("myQueue", session -> message);
/*
Here I have some testing code. How can I tell the application
to not execute this testing code until all JMS lister threads
finish executing.
*/
}
}
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.util.SocketUtils;
import javax.jms.ConnectionFactory;
#EnableJms
#Configuration
#Profile("test")
public class JmsTestConfig {
public static final String BROKER_URL =
"tcp://localhost:" + SocketUtils.findAvailableTcpPort();
#Bean
public BrokerService brokerService() throws Exception {
BrokerService brokerService = new BrokerService();
brokerService.setPersistent(false);
brokerService.addConnector(BROKER_URL);
return brokerService;
}
#Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory(BROKER_URL);
}
#Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
return jmsTemplate;
}
}
Note: Is it applicable to solve this without adding testing purpose code to the implementation code (MyListener and MyProcessor).
Proxy the listener and add an advice to count down a latch; here's one I did for a KafkaListener recently...
#Test
public void test() throws Exception {
this.template.send("so50214261", "foo");
assertThat(TestConfig.latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(TestConfig.received.get()).isEqualTo("foo");
}
#Configuration
public static class TestConfig {
private static final AtomicReference<String> received = new AtomicReference<>();
private static final CountDownLatch latch = new CountDownLatch(1);
#Bean
public static MethodInterceptor interceptor() {
return invocation -> {
received.set((String) invocation.getArguments()[0]);
return invocation.proceed();
};
}
#Bean
public static BeanPostProcessor listenerAdvisor() {
return new ListenerWrapper(interceptor());
}
}
public static class ListenerWrapper implements BeanPostProcessor, Ordered {
private final MethodInterceptor interceptor;
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
public ListenerWrapper(MethodInterceptor interceptor) {
this.interceptor = interceptor;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Listener) {
ProxyFactory pf = new ProxyFactory(bean);
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(this.interceptor);
advisor.addMethodName("listen");
pf.addAdvisor(advisor);
return pf.getProxy();
}
return bean;
}
}
(but you should move the countDown to after the invocation proceed()).
A method annotated with #JmsListener deletes the message after it finishes, so a good option is to read the queue for existing messages and assume the queue is empty after your method is done. Here is the piece of code for counting the messages from the queue.
private int countMessages() {
return jmsTemplate.browse(queueName, new BrowserCallback<Integer>() {
#Override
public Integer doInJms(Session session, QueueBrowser browser) throws JMSException {
return Collections.list(browser.getEnumeration()).size();
}
});
}
Following is the code for testing the countMessages() method.
jmsTemplate.convertAndSend(queueName, "***MESSAGE CONTENT***");
while (countMessages() > 0) {
log.info("number of pending messages: " + countMessages());
Thread.sleep(1_000l);
}
// continue with your logic here
I've based my solution on the answer given by Gary Russell, but rather put the CountDownLatch in an Aspect, using Spring AOP (or the spring-boot-starter-aop variant).
public class TestJMSConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(TestJMSConfiguration.class);
public static final CountDownLatch countDownLatch = new CountDownLatch(1);
#Component
#Aspect
public static class LatchCounterAspect {
#Pointcut("execution(public void be.infrabel.rocstdm.application.ROCSTDMMessageListener.onMessage(javax.jms.TextMessage))")
public void onMessageMethod() {};
#After(value = "onMessageMethod()")
public void countDownLatch() {
countDownLatch.countDown();
LOGGER.info("CountDownLatch called. Count now at: {}", countDownLatch.getCount());
}
}
A snippet of the test:
JmsTemplate jmsTemplate = new JmsTemplate(this.embeddedBrokerConnectionFactory);
jmsTemplate.convertAndSend("AQ.SOMEQUEUE.R", message);
TestJMSConfiguration.countDownLatch.await();
verify(this.listenerSpy).putResponseOnTargetQueueAlias(messageCaptor.capture());
RouteMessage outputMessage = messageCaptor.getValue();
The listenerSpy is a #SpyBean annotated field of the type of my MessageListener. The messageCaptor is a field of type ArgumentCaptor<MyMessageType> annotated with #Captor. Both of these are coming from mockito so you need to run/extend your test with both MockitoExtension (or -Runner) along with the SpringExtension (or -Runner).
My code puts an object on an outbound queue after processing the incoming message, hence the putResponseOnTargetQueueAlias method. The captor is to intercept that object and do my assertions accordingly. The same strategy could be applied to capture some other object in your logic.
In spring boot project using kafka template to send the message
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Service;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
#Service
#Slf4j
class KafkaMessagePublisherImpl {
#Autowired
private KafkaTemplate kafkaAsyncPublisher;
public void sendMessage() {
ListenableFuture listenableFuture = kafkaAsyncPublisher.send(
"test_topikc",
"key_1",
"{\"greeting\":\"Hello\"}");
listenableFuture.addCallback(new ListenableFutureCallback<SendResult<?, ?>>() {
#Override
public void onSuccess(final SendResult<?, ?> message) {
System.out.println("Sent");
}
#Override
public void onFailure(final Throwable throwable) {
System.out.println("Message sending failed");
}
});
}
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
"brokers1:9092,brokers2:9092");
configProps.put(
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
StringSerializer.class);
configProps.put(
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
StringSerializer.class);
return new DefaultKafkaProducerFactory<>(configProps);
}
#Bean(name = "kafkaAsyncPublisher")
public KafkaTemplate<String, String> kafkaAsyncPublisher() {
return new KafkaTemplate<>(producerFactory());
}
}
While using the sendMessage method in another class, the following error is coming
Caused by: org.springframework.kafka.core.KafkaProducerException: Failed to send; nested exception is org.apache.kafka.common.errors.TimeoutException: Failed to update metadata after 60000 ms.
at org.springframework.kafka.core.KafkaTemplate$1.onCompletion(KafkaTemplate.java:259)
at org.apache.kafka.clients.producer.KafkaProducer.doSend(KafkaProducer.java:760)
at org.apache.kafka.clients.producer.KafkaProducer.send(KafkaProducer.java:701)
at org.springframework.kafka.core.DefaultKafkaProducerFactory$CloseSafeProducer.send(DefaultKafkaProducerFactory.java:170)
at org.springframework.kafka.core.KafkaTemplate.doSend(KafkaTemplate.java:245)
at org.springframework.kafka.core.KafkaTemplate.send(KafkaTemplate.java:157)
at com.tesco.fps.messaging.service.impl.KafkaMessagePublisherImpl.lambda$sendMessage$0(KafkaMessagePublisherImpl.java:52)
... 34 common frames omitted
Caused by: org.apache.kafka.common.errors.TimeoutException: Failed to update metadata after 60000 ms.
Springboot:1.5.10.RELEASE and compile('org.apache.kafka:kafka_2.12:0.11.0.0') are being used as gradle dependency along with spring-cloud-stream
Only minor change was done to make it working
#Service
#Slf4j
class KafkaMessagePublisherImpl {
#Autowired
private KafkaTemplate<String,String> kafkaAsyncPublisher;
//Rest is same.
}
I have requirement to move messages from queues on one ActiveMQ instance to another ActiveMQ instance. Is there a way to connect to two different ActiveMQ instances using spring boot configuration?
Do I need to create multiple connectionFactories? If so then how does JmsTemplate know which ActiveMQ instance to connect to?
#Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory(JMS_BROKER_URL);
}
Any help and code examples would be useful.
Thanks in advance.
GM
Additionally to the response of #Chris
You have to create different BrokerService instances using differents ports and create different ConnectionFactory to connect to each broker and create different JmsTemplate using these different factories to send messages to differents brokers.
For example :
import javax.jms.ConnectionFactory;
import javax.jms.QueueConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.core.JmsTemplate;
#Configuration
public class ActiveMQConfigurationForJmsCamelRouteConsumeAndForward {
public static final String LOCAL_Q = "localQ";
public static final String REMOTE_Q = "remoteQ";
#Bean
public BrokerService broker() throws Exception {
final BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:5671");
broker.setBrokerName("broker");
broker.setUseJmx(false);
return broker;
}
#Bean
public BrokerService broker2() throws Exception {
final BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:5672");
broker.setBrokerName("broker2");
broker.setUseJmx(false);
return broker;
}
#Bean
#Primary
public ConnectionFactory jmsConnectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:5671");
return connectionFactory;
}
#Bean
public QueueConnectionFactory jmsConnectionFactory2() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:5672");
return connectionFactory;
}
#Bean
#Primary
public JmsTemplate jmsTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(jmsConnectionFactory());
jmsTemplate.setDefaultDestinationName(LOCAL_Q);
return jmsTemplate;
}
#Bean
public JmsTemplate jmsTemplate2() {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(jmsConnectionFactory2());
jmsTemplate.setDefaultDestinationName(REMOTE_Q);
return jmsTemplate;
}
#Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
#Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory2(
#Qualifier("jmsConnectionFactory2") ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
}
To move messages from one AMQ instance to another instance you can use JmsBridgeConnectors :
Note that by the example below you cannot have multiple consumers on the queue from which you want to forward the messages because Camel or JmsBridgeConnectors consume the message and forward it. If you want a only copy of the message to be forwarded you have some solutions :
1- Convert your queue to a topic, manage the messages for offline consumers by a durable subscriptions or retroactive consumers.
2- convert your queue to a composite queue and use DestinationsInterceptors to copy messages to another queue.
3- use NetworkConnector for Networkof brokers
#Bean
public BrokerService broker() throws Exception {
final BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:5671");
SimpleJmsQueueConnector simpleJmsQueueConnector = new SimpleJmsQueueConnector();
OutboundQueueBridge bridge = new OutboundQueueBridge();
bridge.setLocalQueueName(LOCAL_Q);
bridge.setOutboundQueueName(REMOTE_Q);
OutboundQueueBridge[] outboundQueueBridges = new OutboundQueueBridge[] { bridge };
simpleJmsQueueConnector.getReconnectionPolicy().setMaxSendRetries(ReconnectionPolicy.INFINITE);
simpleJmsQueueConnector.setOutboundQueueBridges(outboundQueueBridges);
simpleJmsQueueConnector.setLocalQueueConnectionFactory((QueueConnectionFactory) jmsConnectionFactory());
simpleJmsQueueConnector.setOutboundQueueConnectionFactory(jmsConnectionFactory2());
JmsConnector[] jmsConnectors = new JmsConnector[] { simpleJmsQueueConnector };
broker.setJmsBridgeConnectors(jmsConnectors);
broker.setBrokerName("broker");
broker.setUseJmx(false);
return broker;
}
Or by using Camel like this below :
#Bean
public CamelContext camelContext() throws Exception {
CamelContext context = new DefaultCamelContext();
context.addComponent("inboundQueue", ActiveMQComponent.activeMQComponent("tcp://localhost:5671"));
context.addComponent("outboundQueue", ActiveMQComponent.activeMQComponent("tcp://localhost:5672"));
context.addRoutes(new RouteBuilder() {
public void configure() {
from("inboundQueue:queue:" + LOCAL_Q).to("outboundQueue:queue:" + REMOTE_Q);
}
});
context.start();
return context;
}
your Producer must be like this to use differents JmsTemplates :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
#Component
public class Producer implements CommandLineRunner {
#Autowired
private JmsTemplate jmsTemplate;
#Autowired
#Qualifier("jmsTemplate2")
private JmsTemplate jmsTemplate2;
#Override
public void run(String... args) throws Exception {
send("Sample message");
}
public void send(String msg) {
this.jmsTemplate.convertAndSend(ActiveMQConfigurationForJmsCamelRouteConsumeAndForward.LOCAL_Q, msg);
this.jmsTemplate2.convertAndSend(ActiveMQConfigurationForJmsCamelRouteConsumeAndForward.REMOTE_Q, msg);
}
}
and Consumer :
import javax.jms.Session;
import org.apache.activemq.ActiveMQSession;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
#Component
public class Consumer {
#JmsListener(destination = ActiveMQConfigurationForJmsCamelRouteConsumeAndForward.REMOTE_Q, containerFactory = "jmsListenerContainerFactory2")
public void receiveQueue(Session session, String text) {
System.out.println(((ActiveMQSession) session).getConnection().getBrokerInfo());
System.out.println(text);
}
}
You would need to instantiate multiple JmsTemplate instances as Beans in your application and then use a combination of #Qualifier and #Primary annotations to indicate which JmsTemplate instance should go where.
For example
#Bean("queue1")
#Primary
public JmsTemplate getQueue1(#Qualifier("connectionFactory1")ConnectionFactory factory...){
...
}
#Bean("queue2")
#Primary
public JmsTemplate getQueue2(#Qualifier("connectionFactory2")ConnectionFactory factory...){
...
}
...
#Autowired
#Qualifier("queue1")
private JmsTemplate queue1;
...
See here for more info.
You can use the Spring Boot default for the queue consumer
#JmsListener(destination = “queue.name")
public void consumer(String message) {
// consume the message
}
And for the producer you can create another JmsTemplate #Bean
#Bean
public JmsTemplate jmsTemplate() {
return new JmsTemplate(new ActiveMQConnectionFactory("tcp://localhost:5671"));
}
This way you can register as many brokers/listeners as you wish dynamically:
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.JmsListenerConfigurer;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerEndpointRegistrar;
import org.springframework.jms.config.SimpleJmsListenerEndpoint;
import javax.jms.Message;
import javax.jms.MessageListener;
#Configuration
public class CustomJmsConfigurer implements JmsListenerConfigurer {
#Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
ActiveMQConnectionFactory amqConnectionFactory = new ActiveMQConnectionFactory();
amqConnectionFactory.setBrokerURL("brokerUrl");
amqConnectionFactory.setUserName("user");
amqConnectionFactory.setPassword("password");
amqConnectionFactory.setExclusiveConsumer(true);
DefaultJmsListenerContainerFactory containerFactory = new DefaultJmsListenerContainerFactory();
containerFactory.setConnectionFactory(amqConnectionFactory);
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("someIdentifier");
endpoint.setDestination("queueName");
endpoint.setMessageListener(new MessageListener() {
#Override
public void onMessage(Message message) {
// Do your stuff
}
});
registrar.registerEndpoint(endpoint, containerFactory);
}
}