looking for a sample code for springboot, activemq(topic) - spring-boot

i found many code samples to create a JMS queue project but didn't find code sample for activemq( topic) which is called as PUB-SUB(Publisher-Subscriber).
for Spring i found below code but am looking for spring boot complete code .
Topic topic = topicConsumerSession.createTopic("customerTopic");
// Consumer1 subscribes to customerTopic
MessageConsumer consumer1 = topicConsumerSession.createSubscriber(topic);
consumer1.setMessageListener(new ConsumerMessageListener(
"Consumer1"));
// Consumer2 subscribes to customerTopic
MessageConsumer consumer2 = topicConsumerSession.createSubscriber(topic);
consumer2.setMessageListener(new ConsumerMessageListener(
"Consumer2"));
Big Thanks to #Gary Russell first of all.
this is what i have implemented from #Gary Russell suggestions. is there any good practice for separation of concerns and more scalable way.
application.properties
spring.jms.pub-sub-domain=true
#spring.jms.template.default-destination=testTopic
Publisher.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
#Component
public class Publisher implements CommandLineRunner{
#Autowired
private JmsTemplate jmsTemplate ;
#Autowired
private Topic topic1;
#Autowired
private Topic topic2;
#Override
public void run(String... arg0) throws Exception {
// TODO Auto-generated method stub
Thread.sleep(5000); // wait for subscriptions, unless they are durable
this.jmsTemplate.convertAndSend(this.topic1,"-----> 1st message from publisher -- topic 1");
Thread.sleep(5000);
this.jmsTemplate.convertAndSend(this.topic1,"-----> 2nd message from publisher -- topic 1");
/**
* for topic2
*/
// TODO Auto-generated method stub
Thread.sleep(5000); // wait for subscriptions, unless they are durable
this.jmsTemplate.convertAndSend(this.topic2,"-----> 1st message from publisher -- topic 2");
Thread.sleep(5000);
this.jmsTemplate.convertAndSend(this.topic2,"-----> 2nd message from publisher -- topic 2");
}
}
Subscriber.java
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
#Component
public class Subscriber {
#JmsListener(destination = "Topic1")
public void listener1(String in) {
System.out.println("Listener1: " + in);
}
#JmsListener(destination = "Topic1,Topic2")
public void listener2(String in) {
System.out.println("Listener2: " + in);
}
#JmsListener(destination = "Topic2")
public void listener3(String in) {
System.out.println("Listener3: " + in+"\n listener 3 is just ");
}
}
mainclass : springBootApplication
PubSubJmsBootApplication.java
import javax.jms.Topic;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
#SpringBootApplication
#EnableJms
public class PubSubJmsBootApplication {
#Bean
public Topic topic1() {
return new ActiveMQTopic("Topic1");
}
#Bean
public Topic topic2() {
return new ActiveMQTopic("Topic2");
}
public static void main(String[] args) {
SpringApplication.run(PubSubJmsBootApplication.class, args);
}
}

you can find spring-boot samples here

In application.properties.
spring.jms.pub-sub-domain=true
spring.jms.template.default-destination=testTopic
Then...
#SpringBootApplication
public class So42173236Application implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(So42173236Application.class, args)
.close();
}
#Autowired
private JmsTemplate jmsTemplate;
#Override
public void run(String... arg0) throws Exception {
Thread.sleep(5000); // wait for subscriptions, unless they are durable
this.jmsTemplate.convertAndSend("foo");
Thread.sleep(5000);
}
#JmsListener(destination = "testTopic")
public void listener1(String in) {
System.out.println("Listener1: " + in);
}
#JmsListener(destination = "testTopic")
public void listener2(String in) {
System.out.println("Listener2: " + in);
}
}
If you build the JmsTemplate or listener container yourself (rather than using Boot's auto configuration), just setPubSubDomain(true).

you can find some easy code in this repo spring-boot-quick
https://github.com/vector4wang/spring-boot-quick/tree/master/quick-activemq
https://github.com/vector4wang/spring-boot-quick/tree/master/quick-activemq2
If you have a lot of news queues and a lot of msg, such as a few million, it is best not to use activemq. I use it in the production environment here, and I am pitted. I need to fill in all kinds of pits. You can use RabbitMQ

Related

How to listen to multiple queues simultaneously on same queue manager?

I want to listen to multiple (2) queues on the same queue manager from my spring boot (kotlin) app. I have the following MQ connection factory but not sure how to add the listeners for my 2 queues.
#Configuration
open class MqConfig {
#Value("\${jms.mq.port}")
private var port: Int = 0
#Value("\${jms.mq.channel}")
private var channel: String? = null
#Value("\${jms.mq.host}")
private var host: String? = null
#Value("\${jms.mq.manager}")
private var queueManager: String? = null
#Value("\${jms.mqA.queue}")
private var jmsMqA: String? = null
#Value("\${jms.mqB.queue}")
private var jmsMqB: String? = null
#Bean
open fun jmsMQConnectionFactory(): MQQueueConnectionFactory {
val mqQueueConnectionFactory = MQQueueConnectionFactory()
mqQueueConnectionFactory.port = port
mqQueueConnectionFactory.channel = channel
mqQueueConnectionFactory.hostName = host
mqQueueConnectionFactory.queueManager = queueManager
mqQueueConnectionFactory.transportType = WMQConstants.WMQ_CM_CLIENT;
mqQueueConnectionFactory.sslSocketFactory = sslSocketfactory;
}
As you can see in the code above, the two queues I want to listen to are jmsMqA and jmsMqB but not sure where/how to use them.
I am new to queues and MQ, so if anyone can point me in the right direction on how to implement a listener and (possibly) publisher for those 2 queues, that will be really helpful.
You can use #JmsListener to listen the queues. Here is the link.
For example:
#Component
class QueueAListener {
#JmsListener(
destination = "\${jms.mqA.queue}",
containerFactory = "jmsMQContainerFactory"
)
fun onMessage(message: Message) {
// yours logic
}
}
For queue B you can create new component or add method to the same class(don't forget to change method name).
UPD:
Container factory:
#Bean(name = "jmsMQContainerFactory")
public JmsListenerContainerFactory<?> topicJmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setPubSubDomain(true);
factory.setConnectionFactory(jmsMQConnectionFactory());
return factory;
}
You can use 'JmsTemplate' to send messages to queues. Please, check this manual
Note: This answer is based on the sample in https://github.com/ibm-messaging/mq-dev-patterns/tree/master/Spring-JMS/src/main/java/com/ibm/mq/samples/jms/spring/level114
Your jmsMQConnectionFactory bean should look something like (this is java)
import com.ibm.mq.jms.MQConnectionFactory;
import com.ibm.mq.samples.jms.spring.globals.handlers.OurDestinationResolver;
import com.ibm.mq.samples.jms.spring.globals.handlers.OurMessageConverter;
import com.ibm.mq.spring.boot.MQConfigurationProperties;
import com.ibm.mq.spring.boot.MQConnectionFactoryFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.QosSettings;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
#Configuration
public class MQConfiguration114 {
protected final Log logger = LogFactory.getLog(getClass());
#Bean
public MQConnectionFactory mqConnectionFactory() throws JMSException {
MQConfigurationProperties properties = new MQConfigurationProperties();
// Properties will be a mix of defaults, and those found in application.properties
// under ibm.mq
// Here we can override any of the properties should we need to
MQConnectionFactoryFactory mqcff = new MQConnectionFactoryFactory(properties,null);
MQConnectionFactory mqcf = mqcff.createConnectionFactory(MQConnectionFactory.class);
return mqcf;
}
#Bean
public JmsListenerContainerFactory<?> myContainerFactory114() throws JMSException {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(mqConnectionFactory());
// ... any customisations go here
return factory;
}
// If you are going to be sending messages then
#Bean("myJmsTemplate114")
public JmsTemplate myJmsTemplate114() throws JMSException {
JmsTemplate jmsTemplate = new JmsTemplate(mqConnectionFactory());
// .. Any customisations go here
return jmsTemplate;
}
}
Your listener code would look something like:
package com.ibm.mq.samples.jms.spring.level114;
import com.ibm.mq.samples.jms.spring.globals.data.OurData;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
//#Component
public class MessageConsumer114 {
protected final Log logger = LogFactory.getLog(getClass());
#JmsListener(destination = "${queue.name1}", containerFactory = "myContainerFactory114")
public void receiveRequest(OurData message) {
logger.info("");
logger.info( this.getClass().getSimpleName());
logger.info("Received message of type: " + message.getClass().getSimpleName());
logger.info("Received message :" + message);
}
}
#JmsListener(destination = "${queue.name2}", containerFactory = "myContainerFactory114")
public void receiveRequest(OurData message) {
logger.info("");
logger.info( this.getClass().getSimpleName());
logger.info("Received message of type: " + message.getClass().getSimpleName());
logger.info("Received message :" + message);
}
}
Your sender logic would look like:
import com.ibm.mq.samples.jms.spring.globals.data.OurData;
import com.ibm.mq.samples.jms.spring.globals.handlers.OurMessageConverter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;
//#Service
public class SendMessageService114 {
protected final Log logger = LogFactory.getLog(getClass());
#Value("${queue.name1}")
public String sendQueue1;
#Value("${queue.name2}")
public String sendQueue2;
final private JmsTemplate myJmsTemplate114;
final private OurMessageConverter ourConverter = new OurMessageConverter();
SendMessageService114(JmsTemplate myJmsTemplate114) {
this.myJmsTemplate114 = myJmsTemplate114;
}
public void send1(OurData msg) {
logger.info("Sending Message");
myJmsTemplate114.convertAndSend(sendQueue1, msg);
}
public void send2(OurData msg) {
logger.info("Sending Message");
myJmsTemplate114.convertAndSend(sendQueue2, msg);
}
}

spring boot app to integrate kafka with active mq

i'm trying to build a spring boot app that reads messages from kafka and put them into activeMQ
and vice versa (read from activeMQ and write to kafka)
i didn't find any useful tutorial to jumpstart my project
See Spring Integration and the Spring Integration Extension for Apache Kafka.
Use inbound and outbound channel adapters
jms -> kafka
kafka -> jms
Kafka Connect also has some capabilities in this space, but I am not familiar with it.
EDIT
This simple Spring Boot app shows transferring data from Kafka to RabbitMQ and vice versa:
package com.example.demo;
import org.apache.kafka.clients.admin.NewTopic;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.config.TopicBuilder;
import org.springframework.kafka.core.KafkaTemplate;
#SpringBootApplication
public class So61069735Application {
public static void main(String[] args) {
SpringApplication.run(So61069735Application.class, args);
}
#Autowired
private KafkaTemplate<String, String> kafkaTemplate;
#Autowired
private RabbitTemplate rabbitTemplate;
#Bean
public ApplicationRunner toKafka() {
return args -> this.kafkaTemplate.send("so61069735-1", "foo");
}
#KafkaListener(id = "so61069735-1", topics = "so61069735-1")
public void listen1(String in) {
System.out.println("From Kafka: " + in);
this.rabbitTemplate.convertAndSend("so61069735-2", in.toUpperCase());
}
#RabbitListener(queues = "so61069735-2")
public void listen2(String in) {
System.out.println("From Rabbit: " + in);
this.kafkaTemplate.send("so61069735-3", in + in);
}
#KafkaListener(id = "so61069735-3", topics = "so61069735-3")
public void listen(String in) {
System.out.println("Final: " + in);
}
#Bean
public NewTopic topic1() {
return TopicBuilder.name("so61069735-1").partitions(1).replicas(1).build();
}
#Bean
public Queue queue() {
return QueueBuilder.durable("so61069735-2").build();
}
#Bean
public NewTopic topic2() {
return TopicBuilder.name("so61069735-3").partitions(1).replicas(1).build();
}
}
spring.kafka.consumer.auto-offset-reset=earliest
Result
From Kafka: foo
From Rabbit: FOO
Final: FOOFOO

How to wait for a spring jms listener thread to finish executing in Junit test

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.

How can configure activemq ex. queue time to live , or keep the message in queue until period of time or action?

I am building notification system using spring boot and websocket, I used ActiveMQ to keep Queues for offlines users, it's working perfect.
I need to edit some configuration like queue time to live, keep message in queue until user read it, I don't know how can configure it?
The below is its implementation:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
/*config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");*/
config
.setApplicationDestinationPrefixes("/app")
.setUserDestinationPrefix("/user")
.enableStompBrokerRelay("/topic","/queue","/user")
.setRelayHost("localhost")
.setRelayPort(61613)
.setClientLogin("guest")
.setClientPasscode("guest");
}
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").withSockJS();
}
}
And:
#Service
public class NotificationWebSocketService {
#Autowired
private SimpMessagingTemplate messagingTemplate;
public void initiateNotification(WebSocketNotification notificationData) throws InterruptedException {
messagingTemplate.convertAndSendToUser(notificationData.getUserID(), "/reply", notificationData.getMessage());
}
}
After invoke NotificationWebSocketService it will create queue "/user/Johon/reply" in activemq contains message when user subscribe in this queue message will received.
How can configure queue time to live , keep message in queue until user read it?
Unit-test to illustrate how to set up expiration of message in user queue.
Required tomcat-embedded, spring-messaging, and active-mq
import org.apache.catalina.Context;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.http11.Http11NioProtocol;
import org.apache.tomcat.util.descriptor.web.ApplicationListener;
import org.apache.tomcat.websocket.server.WsContextListener;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.stomp.*;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.web.SpringServletContainerInitializer;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import org.springframework.web.socket.WebSocketHttpHeaders;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.messaging.WebSocketStompClient;
import org.springframework.web.socket.sockjs.client.SockJsClient;
import org.springframework.web.socket.sockjs.client.WebSocketTransport;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import static java.util.concurrent.TimeUnit.SECONDS;
public class Test48402361 {
private static final Logger logger = LoggerFactory.getLogger(Test48402361.class);
private static TomcatWebSocketTestServer server = new TomcatWebSocketTestServer(33333);
#BeforeClass
public static void beforeClass() throws Exception {
server.deployConfig(Config.class);
server.start();
}
#AfterClass
public static void afterClass() throws Exception {
server.stop();
}
#Test
public void testUser() throws Exception {
WebSocketStompClient stompClient = new WebSocketStompClient(new SockJsClient(Collections.singletonList(new WebSocketTransport(new StandardWebSocketClient()))));
BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();
StompSession session = stompClient
.connect("ws://localhost:" + server.getPort() + "/test", new WebSocketHttpHeaders(), new StompSessionHandlerAdapter() {
})
.get();
// waiting until message 2 expired
Thread.sleep(3000);
session.subscribe("/user/john/reply", new StompFrameHandler() {
#Override
public Type getPayloadType(StompHeaders headers) {
return byte[].class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
String message = new String((byte[]) payload);
logger.debug("message: {}, headers: {}", message, headers);
blockingQueue.add(message);
}
});
String message = blockingQueue.poll(1, SECONDS);
Assert.assertEquals("1", message);
message = blockingQueue.poll(1, SECONDS);
Assert.assertEquals("3", message);
}
public static class Config extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { Mvc.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
#Configuration
#EnableWebSocketMessageBroker
public static class Mvc extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/test")
.withSockJS()
.setWebSocketEnabled(true);
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/user").setRelayHost("localhost").setRelayPort(61614);
}
#Autowired
private SimpMessagingTemplate template;
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(message);
switch (sha.getCommand()) {
case CONNECT:
// after connect we send 3 messages to user john, one will purged after 2 seconds.
template.convertAndSendToUser("john", "/reply", "1");
Map<String, Object> headers = new HashMap<>();
headers.put("expires", System.currentTimeMillis() + 2000);
template.convertAndSendToUser("john", "/reply", "2", headers);
template.convertAndSendToUser("john", "/reply", "3");
break;
}
return super.preSend(message, channel);
}
});
}
}
public static class TomcatWebSocketTestServer {
private static final ApplicationListener WS_APPLICATION_LISTENER =
new ApplicationListener(WsContextListener.class.getName(), false);
private final Tomcat tomcatServer;
private final int port;
private Context context;
public TomcatWebSocketTestServer(int port) {
this.port = port;
Connector connector = new Connector(Http11NioProtocol.class.getName());
connector.setPort(this.port);
File baseDir = createTempDir("tomcat");
String baseDirPath = baseDir.getAbsolutePath();
this.tomcatServer = new Tomcat();
this.tomcatServer.setBaseDir(baseDirPath);
this.tomcatServer.setPort(this.port);
this.tomcatServer.getService().addConnector(connector);
this.tomcatServer.setConnector(connector);
}
private File createTempDir(String prefix) {
try {
File tempFolder = File.createTempFile(prefix + '.', "." + getPort());
tempFolder.delete();
tempFolder.mkdir();
tempFolder.deleteOnExit();
return tempFolder;
} catch (IOException ex) {
throw new RuntimeException("Unable to create temp directory", ex);
}
}
public int getPort() {
return this.port;
}
#SafeVarargs
public final void deployConfig(Class<? extends WebApplicationInitializer>... initializers) {
this.context = this.tomcatServer.addContext("", System.getProperty("java.io.tmpdir"));
// Add Tomcat's DefaultServlet
Wrapper defaultServlet = this.context.createWrapper();
defaultServlet.setName("default");
defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet");
this.context.addChild(defaultServlet);
// Ensure WebSocket support
this.context.addApplicationListener(WS_APPLICATION_LISTENER);
this.context.addServletContainerInitializer(
new SpringServletContainerInitializer(), new HashSet<>(Arrays.asList(initializers)));
}
public void start() throws Exception {
this.tomcatServer.start();
}
public void stop() throws Exception {
this.tomcatServer.stop();
}
}
}
"stompClient.subscribe('/user/Johon/reply' --> '/user/Johon/reply' is a topic and not a queue.
If your Stomp client is not connected to the topic '/user/Johon/reply' he will loose every message sent to that topic.
So your solutions are :
convert your topic '/user/Johon/reply' to a queue, so the message remains on the queue indefinitely or until the server end-processes the message.
use Retroactive Consumer & Subscription Recovery Policy
A retroactive consumer is just a regular JMS Topic consumer who
indicates that at the start of a subscription every attempt should be
used to go back in time and send any old messages (or the last message
sent on that topic) that the consumer may have missed.
http://activemq.apache.org/retroactive-consumer.html
The subscription recovery policy allows you to go back in time when
you subscribe to a topic.
http://activemq.apache.org/subscription-recovery-policy.html
Use Durable Subscribers
Durable topic subscribers that are offline for a long period of time
are usually not desired in the system. The reason for that is that
broker needs to keep all the messages sent to those topics for the
said subscribers. And this message piling can over time exhaust broker
store limits for example and lead to the overall slowdown of the
system.
http://activemq.apache.org/manage-durable-subscribers.html
Durable Subscribers with Stomp :
http://activemq.apache.org/stomp.html#Stomp-ActiveMQExtensionstoSTOMP
CONNECT client-id string Specifies the JMS clientID which is used in
combination with the activemq.subcriptionName to denote a durable
subscriber.
some explanations about TTL
A client can specify a time-to-live value in milliseconds for each
message it sends. This value defines a message expiration time that is
the sum of the message's time-to-live and the GMT when it is sent (for
transacted sends, this is the time the client sends the message, not
the time the transaction is committed).
the default time to live is 0, so the message remains on the queue
indefinitely or until the server end-processes the message
UPDATE
if you want to use external ActiveMQ Broker
remove #EnableWebSocketMessageBroker and add to your activemq.xml below connector and restart the broker.
<transportConnector name="stomp" uri="stomp://localhost:61613"/>
if you want to embedd ActiveMQ Broker, add bean to you WebSocketConfig :
#Bean(initMethod = "start", destroyMethod = "stop")
public BrokerService broker() throws Exception {
final BrokerService broker = new BrokerService();
broker.addConnector("stomp://localhost:61613");
return broker;
}
and required dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-stomp</artifactId>
</dependency>
full examples
Spring Boot WebSocket with embedded ActiveMQ Broker
http://www.devglan.com/spring-boot/spring-boot-websocket-integration-example

Spring NoClassDefFoundError on org/springframework/transaction/support/TransactionSynchronization

Spring JMS is working against me, as the title say I get a NoClassDefFoundError, below you find the code.
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.Session;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.JmsTemplate102;
import org.springframework.jms.core.MessageCreator;
public class JMSQueueSender {
private JmsTemplate jmsTemplate;
private Queue queue;
public void setConnectionFactory(ConnectionFactory cf) {
this.jmsTemplate = new JmsTemplate102(cf, false);
}
public void setQueue(Queue queue) {
this.queue = queue;
}
public void simpleSend() throws Exception {
this.jmsTemplate.send(this.queue, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("hello queue world");
}
});
}
}
This is also the example code, I have a working code with IBM, but I'm trying to change it for a Spring JMS, but it isn't working. Can someone help me.
I guess you need to add spring-tx.jar (or org.springframework.transaction-*.jar with new style of names) to the classpath.
Try checking your JAVA_HOME and CLASSPATH settings.

Resources