What is the difference beetween JmsTemplate and #SendTo()? - java

I have two application, one is sending request, another is answering, i'm trying to implement it using #JmsListener.
This code works:
public JmsTemplate jmsTemplate (ConnectionFactory connectionFactory){
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(connectionFactory);
Destination destination = new ActiveMQQueue("replydestination");
jmsTemplate.setDefaultDestination(destination);
return jmsTemplate;
}
#JmsListener(destination = "somedestination",
containerFactory = "defaultJmsListenerContainerFactory")
public void receiveMessage (Message message) throws JMSException {
jmsTemplate.send(new ActiveMQTextMessage());
}
But when change to #SendTo("replydestination") it stops working:
#JmsListener(destination = "somedestination",
containerFactory = "defaultJmsListenerContainerFactory")
#SendTo("replydestination")
public Message receiveMessage (Message message) throws JMSException {
return new ActiveMQTextMessage();
}
Help me to understand why, and can i make this integration without using JmsTemplate.

JMS messages should be constructed with methods from javax.jms.Session or with a builder like this:
#JmsListener(destination = "somedestination",
containerFactory = "defaultJmsListenerContainerFactory")
#SendTo("replydestination")
public org.springframework.messaging.Message<String> listen(javax.jms.Message message) {
org.springframework.messaging.Message<String> reply = MessageBuilder
.withPayload("MyReply")
.build();
return reply;
}

This works too...
#SpringBootApplication
public class So65570932Application {
public static void main(String[] args) {
SpringApplication.run(So65570932Application.class, args);
}
#JmsListener(destination = "foo")
#SendTo("bar")
String listen(String in) {
System.out.println(in);
return in.toUpperCase();
}
#Bean
public ApplicationRunner runner(JmsTemplate template) {
return args -> {
template.convertAndSend("foo", "baz");
template.setReceiveTimeout(10_000);
System.out.println(template.receiveAndConvert("bar"));
};
}
}

Related

How can we have different handlers to subscribe messages from different topics using spring Integration?

I wrote my first spring integration application which uses the mqtt broker to subscribe messages from different topics which are coming from a device. The device is publishing the messages and the client(Code) is accessing those messages using same topics.
I added a handler for accessing the messages coming from the broker and use it further in classes. Now, in my case, I want to have different handlers for different topics so that they can all be mapped to different VO classes and use it further in business logic.
As I know, I want to create only one connection to the broker, one channel but different topics can come and they should be handled in different handlers for the same connection. How Can I achieve that?
#SpringBootApplication
public class MqttJavaApplication {
public static void main(String[] args) {
// SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder(MqttJavaApplication.class);
SpringApplication.run(MqttJavaApplication.class,args);
}
#Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
#Bean
public MqttPahoMessageDrivenChannelAdapter inbound() {
String clientId = "uuid-" + UUID.randomUUID().toString();
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter("tcp://localhost:1883", clientId,"camera/status");
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1);
// adapter.setOutputChannelName("mqttInputChannel");
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
#Bean
public IntegrationFlow mqttInFlow() {
System.out.println(Arrays.stream(SubScribeMessages.class.getMethods()).findFirst());
return IntegrationFlows.from(inbound())
.transform(p -> p)
.handle("addTopics","handlHere")
.get();
}
#Component
public class MyService{
#Autowired
MqttPahoMessageDrivenChannelAdapter adapter;
#Bean
public String addTopics()
{
if(adapter.getTopic().length>0)
{
adapter.addTopic("camera/+/counts"); //new topic
adapter.addTopic("camera/+/live_counts"); //new topic
}
return "";
}
// topic "camera/+/counts" is handled here but other messages also come here, how do we handle other topics in separate handlers?
#ServiceActivator(inputChannel = "mqttInputChannel")
public void handleHere(#Payload Object mess) throws JsonProcessingException {
String[] topics = adapter.getTopic();
for(String topic:topics)
System.out.println(topic); // How can I get topic name which is using a wildcard?
ObjectMapper objectMapper = new ObjectMapper();
String json=mess.toString();
System.out.println(json);
CountVo countVo = objectMapper.readValue(json, CountVo.class);
if (!countVo.equals(null))
System.out.println(countVo.getIrisysCounts().get(0).getName());
}
}
}
Additional Question
How Can I get the full topic name when using a wildcard? The actual topic which was published but caught by wildcard.
Please help.
Add a router (.route(...)); you can route on the MqttHeaders.RECEIVED_TOPIC header (which contains the topic name) to different flows for each topic.
https://docs.spring.io/spring-integration/docs/current/reference/html/message-routing.html#messaging-routing-chapter
EDIT
The simplest router is to simply map the topic names to channel names. Here is an example:
#SpringBootApplication
public class So67391175Application {
public static void main(String[] args) {
SpringApplication.run(So67391175Application.class, args);
}
#Bean
public DefaultMqttPahoClientFactory pahoClientFactory() {
DefaultMqttPahoClientFactory pahoClientFactory = new DefaultMqttPahoClientFactory();
MqttConnectOptions connectionOptions = new MqttConnectOptions();
connectionOptions.setServerURIs(new String[] { "tcp://localhost:1883" });
pahoClientFactory.setConnectionOptions(connectionOptions);
return pahoClientFactory;
}
#Bean
public IntegrationFlow mqttInFlow(DefaultMqttPahoClientFactory pahoClientFactory) {
return IntegrationFlows.from(
new MqttPahoMessageDrivenChannelAdapter("testClient",
pahoClientFactory, "topic1", "topic2"))
.route("headers['" + MqttHeaders.RECEIVED_TOPIC + "']")
.get();
}
#Bean
public IntegrationFlow flow1() {
return IntegrationFlows.from("topic1")
.handle((payload, headers) -> {
System.out.println("message from topic1 " + payload + ": " + headers);
return null;
})
.get();
}
#Bean
public IntegrationFlow flow2() {
return IntegrationFlows.from("topic2")
.handle((payload, headers) -> {
System.out.println("message from topic2 " + payload + ": " + headers);
return null;
})
.get();
}
}
message from topic1 test: {mqtt_receivedRetained=false, mqtt_id=1, mqtt_duplicate=false, id=1d950bce-aa47-7e3b-1a0d-e4d01ed707de, mqtt_receivedTopic=topic1, mqtt_receivedQos=1, timestamp=1620250633090}
message from topic2 test: {mqtt_receivedRetained=false, mqtt_id=2, mqtt_duplicate=false, id=7e9c3f51-c148-2b18-3588-ed27e93dae19, mqtt_receivedTopic=topic2, mqtt_receivedQos=1, timestamp=1620250644602}
Thanks Gary! I think the answer you gave on routing can only take the defined topics, not on wildcards or for that matter any other regex. I could not understand how dynamic routing would help me.
Turns out, I can add wildcards when initializing bean and can handle using the service activator on the inputchannel using the adapter.
Like this:
#SpringBootApplication
public class MqttJavaApplication {
public static void main(String[] args) {
SpringApplication.run(MqttJavaApplication.class,args);
}
#Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
#Bean
public MqttPahoMessageDrivenChannelAdapter inbound() {
String clientId = "uuid-" + UUID.randomUUID().toString();
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter("tcp://localhost:1883", clientId, "irisys/V4D-20230143/status" );
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1);
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
#Component
public class MyService{
#Autowired
MqttPahoMessageDrivenChannelAdapter adapter;
#Bean
public String addTopics()
{
if(adapter.getTopic().length>0)
{
adapter.addTopic("camera/+/counts");
}
return "";
}
#Bean
#ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler handler() {
return new MessageHandler() {
#SneakyThrows
#Override
public void handleMessage(Message<?> message) throws MessagingException {
System.out.println(message.getPayload());
System.out.println(message.getHeaders());
if(message.getHeaders().get("mqtt_receivedTopic").toString().contains("counts"))
{
ObjectMapper objectMapper = new ObjectMapper();
String json=message.getPayload().toString();
System.out.println(json);
CountVo countVo = objectMapper.readValue(json, CountVo.class);
if (!countVo.equals(null))
System.out.println(countVo.getIrisysCounts().get(0).getName());
}
}
};
}
}
}
Do you think that there is better way than this? I couldn't think of anything other than this.

Handle the exceptions in RabbitMQ Spring Boot Application

I am using Spring Boot 1.4.1-RELEASE and RabbitMQ 3.2.3. My Application class looks like this -
#SpringBootApplication
#EnableAutoConfiguration
public class EventStoreMessageDeliveryApplication {
public final static String queueName = "customer.default.queue"; // spring-boot
#Bean
Queue queue() {
return new Queue(queueName, true);
}
#Bean
FanoutExchange exchange() {
return new FanoutExchange("customer.events.fanout.exchange", true, false); // spring-boot-exchange
}
#Bean
Binding binding() {
return new Binding(queueName, Binding.DestinationType.QUEUE, "customer.events.fanout.exchange", "*.*", null);
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
connectionFactory.setPublisherConfirms(true);
return connectionFactory;
}
#Bean
SimpleMessageListenerContainer container(MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.setQueueNames(queueName);
container.setMessageListener(listenerAdapter);
container.setRecoveryBackOff(new ExponentialBackOff(3000, 2));
return container;
}
#Bean
MessageListenerAdapter listenerAdapter(Receiver receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(EventStoreMessageDeliveryApplication.class, args);
}
}
And my listener class looks like -
#Component
public class Receiver {
private CountDownLatch latch = new CountDownLatch(1);
public void receiveMessage(String message) {
System.out.println("Received <" + message + ">");
// do something
latch.countDown();
}
public CountDownLatch getLatch() {
return latch;
}
}
I want to handle the exceptions like connection refused which may come when the broker is down. How can I handle such exceptions? I am not sure where I can get the handle for the exceptions.
You can create a SimpleRabbitListenerContainerFactory. This is basically a listener for events from RabbitConnectionFactory.
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setErrorHandler(rabbitErrorHandler());
return factory;
}
rabbitErrorHandler() can return a bean of implementation of org.springframework.util.ErrorHandler.
Reference docs
I have a suggestion and it could work out. Since you want to have an exception of connection refused against the RabbitMQ broker, it is up to the client to catch the exception.
In your example, which looks like the one from SpringIO docs, I would assume you could make the exception handling in the main (not recommended though):
#Component
public class Runner implements CommandLineRunner {
private final RabbitTemplate rabbitTemplate;
private final Receiver receiver;
public Runner(Receiver receiver, RabbitTemplate rabbitTemplate) {
this.receiver = receiver;
this.rabbitTemplate = rabbitTemplate;
}
#Override
public void run(String... args) throws Exception {
System.out.println("Sending message...");
try {
rabbitTemplate.convertAndSend(Application.topicExchangeName, "foo.bar.baz", "Hello from RabbitMQ!");
receiver.getLatch().await(10000, TimeUnit.MILLISECONDS);
}catch(AmqpException the_exception) {
System.out.printl("Connection refused. Problem thrown when trying to connecto the RabbitMQ");
}
}
}
The AmqpException comes from the docs of the convertAndSend() method, which is being thrown if something went bad. Here you can capture your own custom message.
I hope this is what you are looking for or atleast guides you the correct destination.
/A

Synchronize asynchronous communication

I have a REST service which receives some data and check the data through an ansynchronous IBM MQ request.
REST controller:
#RestController
#RequestMapping("/request")
public class RequestController {
#RequestMapping(method = RequestMethod.POST)
public Response postRequest(#RequestBody Request request) {
String data = request.getData();
jmsSender.send(data);
// Now I need the response from MQ
// String mqResponse = ...
if (mqIsValid(mqResponse)) {
return createValidResponse();
}
return createNotValidResponse();
}
}
MQ sender:
#Service
public class JmsSender {
public void send(String data) {
jmsTemplate.convertAndSend("QUEUE.TO.MQ", data);
}
}
MQ receiver:
#Component
public class JmsReceiver {
#JmsListener(destination = "QUEUE.FROM.MQ, containerFactory = "DefaultJmsListenerContainerFactory")
public void receiveMessage(String message) {
// How to pass the message to the controller?
}
}
How can I wait for the right data from MQ to create the correct response in the controller?
Is it possible to use a BlockingQueue for this like described here? In my case I have to distinguish the data. I can't just take the first data from the blocking queue.
If for example there are two REST requests simultaneously (with data: abc and xyz). How can I make sure to response the right answer not just the first answer I get from MQ?
I also can't change the MQ interface.
Try using a CountDownLatch like below.
#RestController
#RequestMapping("/request")
public class RequestController {
#RequestMapping(method = RequestMethod.POST)
public Response postRequest(#RequestBody Request request) {
final CountDownLatch jmsLatch = new CountDownLatch (1);
String data = request.getData();
jmsSender.send(data, jmsLatch);
try {
latch.await(); // wait untill latch counted down to 0
} catch (InterruptedException e) {
return createNotValidResponse();
}
return createValidResponse();
}
}
Modify the send method to get the CountDownLatch from controller.
#Service
public class JmsSender {
public void send(String data, final CountDownLatch jmsLatch) {
jmsLatch.await();
jmsTemplate.convertAndSend("QUEUE.TO.MQ", data);
}
}
Modify the receive method to get the same CountDownLatch from controller.
#Component
public class JmsReceiver {
#JmsListener(destination = "QUEUE.FROM.MQ", containerFactory = "DefaultJmsListenerContainerFactory")
public void receiveMessage(String message, final CountDownLatch jmsLatch) {
// Pass the message to the controller
jmsLatch.countDown();
}
}
The trick here is you have to spread the same CountDownLatch instance from controller to the sender and receiver class and call the countDown method after you receive the message.
Since I couldn't find any suitable solution for me, I've created a simple waiting mechanism to get the data.
MqReceiver:
#Component
public class JmsReceiver {
private final Lock lock;
private final Condition containsKey;
private final Map<String, String> responses;
public JmsReceiver() {
this.lock = new ReentrantLock();
this.containsKey = lock.newCondition();
this.responses = new HashMap<>();
}
#JmsListener(destination = "QUEUE.FROM.MQ", containerFactory = "DefaultJmsListenerContainerFactory")
public void receiveMessage(String message) {
put(getKeyFromMessage(message), message);
}
public String get(String key) throws InterruptedException {
lock.lock();
try {
while (!responses.containsKey(key)) {
containsKey.await();
}
return responses.get(key);
} finally {
lock.unlock();
}
}
public void put(String key, String messagee) {
lock.lock();
try {
responses.put(key, messagee);
containsKey.signalAll();
} finally {
lock.unlock();
}
}
}
This can be used in the controller:
#RestController
#RequestMapping("/request")
public class RequestController {
#RequestMapping(method = RequestMethod.POST)
public Response postRequest(#RequestBody Request request) {
String data = request.getData();
jmsSender.send(data);
String key = getKeyFromData(data);
// waits until MQ sends the data
String mqResponse = jmsReceiver.get(key);
if (mqIsValid(mqResponse)) {
return createValidResponse();
}
return createNotValidResponse();
}
}
Solution for scenario sync-async implementing request-reply pattern with jms(activemq)
The idea of this example is working in two different services in a different jvm. The solution is tested concurrent with several instances services:
Service 1 (M1) - Rest api synchronous and in some point start an
async flow using activemq to call the second Service M2 implementing Integration Pattern Request-Reply. You don't need to stop or wait any thread, the jms pattern implements the ack Session.AUTO_ACKNOWLEDGE.
#PostMapping
public AnyDto sendMessage(final AnyDto anyDto) {
return routeService.send(anyDto);
}
public void flowOrchestation (final anyDto data) throws JMSException {
final ObjectMessage objectMessage = composeTemplateMessage(data);
final AnyDto responseDto = jmsMessagingTemplate.convertSendAndReceive(new ActiveMQQueue("queue.request"),
objectMessage, AnyDto.class);
}
private ObjectMessage composeTemplateMessage(final AnyDto data) throws JMSException {
jmsTemplate.setReceiveTimeout(10000L);
jmsMessagingTemplate.setJmsTemplate(jmsTemplate);
Session session = jmsMessagingTemplate.getConnectionFactory().createConnection()
.createSession(false, Session.AUTO_ACKNOWLEDGE);
final ObjectMessage objectMessage = session.createObjectMessage(data);
objectMessage.setJMSCorrelationID(UUID.randomUUID().toString());
objectMessage.setJMSReplyTo(new ActiveMQQueue("queue.response"));
objectMessage.setJMSExpiration(0);
objectMessage.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT);
return objectMessage;
}
Timeout and expiration can be modified depending your requeriments. 0 expiration means no time to expire.
Service 2 (M2): Just receive the message and make the response to JmsReplyTo setted on M1.
#Component
public class Consumer implements SessionAwareMessageListener<Message> {
#Override
#JmsListener(destination = "${queue.request}")
public void onMessage(Message message, Session session) throws JMSException {
AnyDto anyDto = (AnyDto) ((ActiveMQObjectMessage) message).getObject();
//do some stuff
final ObjectMessage responseMessage = new ActiveMQObjectMessage();
responseMessage.setJMSCorrelationID(message.getJMSCorrelationID());
responseMessage.setObject(dtoModified);
final MessageProducer producer = session.createProducer(message.getJMSReplyTo());
producer.send(responseMessage);
}}

ERR Timed out after 1m0s: health check never passed

I am getting this exception after some 2-3 minutes of proper run of my app on cloud.I added timeout : 180 in manifest.yml file but the error still persists.
On STS console, I get the log as :
Execution failed for task ':cfPush'.> Application TestApp start timed out
Can anyone help me out in this please
Code:
#SpringBootApplication
public class SftpJavaApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(SftpJavaApplication.class)
.web(false)
.run(args);
}
#Bean
public SessionFactory<LsEntry> sftpSessionFactory() {
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
factory.setHost("ip");
factory.setPort(port);
factory.setUser("user");
factory.setPassword("pwd");
factory.setAllowUnknownKeys(true);
return new CachingSessionFactory<LsEntry>(factory);
}
#Bean
#Transformer(inputChannel = "stream",outputChannel="data")
public org.springframework.integration.transformer.Transformer transformer () {
return new org.springframework.integration.transformer.StreamTransformer("UTF-8");
}
#Bean
#InboundChannelAdapter(value = "stream", poller = #Poller(fixedDelay = "1000", maxMessagesPerPoll = "1"))
public MessageSource<InputStream> ftpMessageSource() {
SftpStreamingMessageSource messageSource = new SftpStreamingMessageSource(template(), null);
messageSource.setRemoteDirectory("/test1/test2/test3");
messageSource.setFilter(new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(),
"streaming"));
return messageSource;
}
#Bean
public SftpRemoteFileTemplate template() {
return new SftpRemoteFileTemplate(sftpSessionFactory());
}
#Bean
#ServiceActivator(inputChannel = "data" )
public MessageHandler handler() {
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
System.out.println(">>>>>>>>>>>>>"+message.getPayload()); //instead of a print the message is published to rabbit mq
}
};
}
}
There is another class which connects to a rabbit mq service on cloud and consumes the message

Spring and AMQP RabbitMQ topic exchange not working

I'm trying to set up topic exchange on my spring app.
Here's my context configuration:
#Configuration
public class IntegrationConfig {
public final static String queueName = "my-queue";
#Bean
AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
#Bean
Queue queue() {
return new Queue(queueName);
}
#Bean
TopicExchange exchange() {
return new TopicExchange("my-exchange", false, true);
}
#Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("ru.interosite.*");
}
#Bean
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(queueName);
container.setMessageListener(listenerAdapter);
return container;
}
#Bean
ImageUploadReceiver receiver() {
return new ImageUploadReceiver();
}
#Bean
MessageListenerAdapter listenerAdapter(ImageUploadReceiver receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
}
This is receiver class:
public class ImageUploadReceiver {
private CountDownLatch latch = new CountDownLatch(1);
public void receiveMessage(String message) {
System.out.println("Received ");
latch.countDown();
}
public CountDownLatch getLatch() {
return latch;
}
}
This is sender code:
#RequestMapping("/sendmessage")
#ResponseBody
public String sendMessage() {
rabbitTemplate.convertAndSend("ru.interosite.1", "ttt1233");
try {
imageUploadReceiver.getLatch().await(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Msg received";
}
So I'm sending message to topic exchange using binding key "ru.interosite.1" to the queue that was bound with pattern "ru.interosite.*". I used these key and pattern when tried sample from https://www.rabbitmq.com/tutorials/tutorial-five-java.html and they worked fine.
But inside String AMQP it does not work, i.e. receiver never gets called. It called only if binding key and pattern are completely the same as if I were using DirectExchange.
Am I missing something here?
You don't show the config for the RabbitTemplate, but I guess it is with default options.
To send a message to the my-exchange you must specify it directly:
rabbitTemplate.convertAndSend("my-exchange", "ru.interosite.1", "ttt1233");
You can also set the exchange in the rabbit template like this:
#Configuration
public class IntegrationConfig {
// ... as above
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setExchange("my-exchange");
return template;
}
}
So you can send the message with:
public class MyController {
#Autowired
RabbitTemplate rabbitTemplate;
#RequestMapping("/sendmessage")
#ResponseBody
public String sendMessage() {
rabbitTemplate.convertAndSend("ru.interosite.1", "ttt1233");
// ... as above
}
}

Categories

Resources