I have three project:
Javascript SockJS STOMP client
Spring-boot STOMP endpoint and AMQP
Spring-boot AMQP (RabbitListener) client for testing
I am using RabbitMQ message broker (+Stomp plugin) and configured amqp and stomp endpoint normally..When I send message to queue with RabbitTemplate and third project (spring-boot amqp client for testing) normally subscribed this message , everything works fine !! But Javascript STOMP client didn't received this message..
P.S. When I send message with SimpMessagingTemplate , JS client receives message fine !
Javascript SockJS STOMP Client
var socket = new SockJS('http://localhost:8090/hello');
stompClient = Stomp.over(socket);
stompClient.connect('guest','guest', function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/testqueue', function(greeting){
showGreeting(JSON.parse(greeting.body).content);
});
});
spring-boot STOMP endpoint and AMQP
#Controller
public class SampleController {
Logger logger = Logger.getLogger(SampleController.class);
#Autowired
private RabbitTemplate rabbitTemplate;
private SimpMessagingTemplate messagingTemplate;
#Autowired
public SampleController(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
#GetMapping("/emit/{message}")
#ResponseBody
String queue1(#PathVariable("message") String message) throws Exception {
logger.info("Emit to testqueue");
rabbitTemplate.convertAndSend("/topic/testqueue", new Greeting("Salam olsun " + message));
Thread.sleep(60000); // simulated delay
return "Emit to testqueue";
}
}
spring-boot amqp client for testing
#Component
public class RabbitMqListener {
Logger logger = Logger.getLogger(RabbitMqListener.class);
#RabbitListener(queues = "/topic/testqueue")
public void processQueue1(String message) {
logger.info("Received from queue : " + message);
}
}
How I can mix amqp and stomp protocols in RabbitMQ ? I want to send message from another project with amqp protocol (RabbitTemplate) and receive this message from JS STOMP client (SockJS) .. Thanks.
I was changed rabbitTemplate.convertAndSend("/topic/testqueue", ...) to rabbitTemplate.convertAndSend("amq.topic","testqueue" ...) and everythink works fine ))) Especially thanks to Artem Bilan for support. Good Luck
Related
I am trying to use a RPC AMQP RabbitMQ queue to send and receive messages. The problem is that I have set a setReplyTimeout value. When that happens I get a "org.springframework.amqp.AmqpRejectAndDontRequeueException: Reply received after timeout". I have a DLQ set up on the incoming queue, but it appears that the exception is received when spring tries to return the message on its queue that is automatically created. Thus how can I handle exceptions when sending messages back to a producer? Ideally I would want any message that gets an exception while being sent to a producer sent to a DLQ.
I am using
#RabbitListener(queues = QueueConfig.QUEUE_ALL, containerFactory = "containerFactoryQueueAll")
It requires a SimpleRabbitListenerContainerFactory which does not have setQueues. Also rabbitTemplate does not have a rabbitTemplate.setReplyQueue
Thanks,
Brian
Instead of using the default built-in reply listener container with the direct reply-to pseudo queue, use a Reply Listener Container with a named queue that is configured to route undeliverable messages to a DLQ.
The RabbitTemplate is configured as the container's listener:
#Bean
public RabbitTemplate amqpTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
rabbitTemplate.setMessageConverter(msgConv());
rabbitTemplate.setReplyQueue(replyQueue());
rabbitTemplate.setReplyTimeout(60000);
rabbitTemplate.setUseDirectReplyToContainer(false);
return rabbitTemplate;
}
#Bean
public SimpleMessageListenerContainer replyListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.setQueues(replyQueue());
container.setMessageListener(amqpTemplate());
return container;
}
#Bean
public Queue replyQueue() {
return new Queue("my.reply.queue");
}
Note that the documentation needs to be updated, but you also need
rabbitTemplate.setUseDirectReplyToContainer(false);
IMPORTANT
If you have multiple instances of the client, each needs its own reply queue.
I have four exact replicas of a service that among other things catch messages from a certain queue using Apache Camel RabbitMQ endpoints. Each route looks like this:
//Start Process from RabbitMQ queue
from("rabbitmq://" +
System.getenv("ADVERTISE_ADDRESS") +
"/" +
System.getenv("RABBITMQ_EXCHANGE_NAME") +
"?routingKey=" +
System.getenv("RABBITMQ_ROUTING_KEY") +
"&autoAck=true")
.process(exchange -> exchange.getIn().setBody(exchange.getIn().getBody()))
.unmarshal().json(JsonLibrary.Jackson, TwitterBean.class)
.transform().method(ResponseTransformer.class, "transformtwitterBean")
.marshal().json(JsonLibrary.Jackson)
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("http4://" + System.getenv("ADVERTISE_ADDRESS") + ":" + System.getenv("CAMUNDA_PORT") + "/rest/process-definition/key/MainProcess/start")
.log("Response: ${body}");
Right now each endpoint processes the message.
Even though the "concurrent consumers"-option by default is one.
I assumed that maybe my messages weren't acknowledged,
so I set the autoAck option to true.
This didn't help, how can I make these services competing consumers?
EDIT:
A code snippet from the configuration of my publisher app:
#Configuration
public class RabbitMqConfig {
#Bean
Queue queue() {
return new Queue(System.getenv("RABBITMQ_QUEUE_NAME"), true);
}
#Bean
DirectExchange exchange() {
return new DirectExchange(System.getenv("RABBITMQ_EXCHANGE_NAME"), true, true);
}
#Bean
Binding binding(Queue queue, DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(System.getenv("RABBITMQ_ROUTING_KEY"));
}
#Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
public AmqpTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(jsonMessageConverter());
return rabbitTemplate;
}
}
The issue you have is that you're not naming your queue on the service side
Based on the camel apache rabbitmq documentation, this means that a random name is generated for the queue.
So:
you have a publisher that sends a message to an exchange
then each of your service creates a queue with a random name, and binds it to the exchange
Each service having it's own queue, bound to the same exchange, will get the same messages.
To avoid this you need to provide a queue name,
so that each service will connect to the same queue, which will mean they will share the message consumption with the other service instances.
Sounds like you don't have a Queue, but a Topic. See here for a comparison.
The message broker is responsible to give a queue message to only one consumer, no matter how much of them are present.
I have an issue in getting the message to spring-cloud-stream spring-boot app.
I am using rabbitMq as message engine.
Message producer is a non spring-boot app, which sends a message using Spring RestTemplate.
Queue Name: "audit.logging.rest"
The consumer application is setup to listen that queue. This app is spring-boot app(spring-cloud-stream).
Below is the consumer code
application.yml
cloud:
stream:
bindings:
restChannel:
binder: rabbit
destination: audit.logging
group: rest
AuditServiceApplication.java
#SpringBootApplication
public class AuditServiceApplication {
#Bean
public ByteArrayMessageConverter byteArrayMessageConverter() {
return new ByteArrayMessageConverter();
}
#Input
#StreamListener(AuditChannelProperties.REST_CHANNEL)
public void receive(AuditTestLogger logger) {
...
}
AuditTestLogger.java
public class AuditTestLogger {
private String applicationName;
public String getApplicationName() {
return applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
}
Below is the request being sent from the producer App in JSON format.
{"applicationName" : "AppOne" }
Found couple of issues:
Issue1:
What I noticed is the below method is getting triggered only when the method Parameter is mentioned as Object, as spring-cloud-stream is not able to parse the message into Java POJO object.
#Input
#StreamListener(AuditChannelProperties.REST_CHANNEL)
public void receive(AuditTestLogger logger) {
Issue2:
When I changed the method to receive object. I see the object is of type RMQTextMessage which cannot be parsed. However I see actual posted message within it against text property.
I had written a ByteArrayMessageConverter which even didn't help.
Is there any way to tell spring cloud stream to extract the message from RMQTextMessage using MessageConverter and get the actual message out of it.
Thanks in Advance..
RMQTextMessage? Looks like it is a part of rabbitmq-jms-client.
In case of RabbitMQ Binder you should rely only on the Spring AMQP.
Now let's figure out what your producer application is doing.
Since you get RMQTextMessage as value for the #StreamListener method that says me that the sender really uses rabbitmq-jms-client for producing, and therefore the real AMQP message in queue has that RMQTextMessage as a wrapper for real payload.
Why don't use Spring AMQP there as well?
It's a late reply but I have the exact problem and solved it by sending and receiving the messages in application/json format. use this in the spring cloud stream config.
content-type: application/json
I have a requirement to send message to MessageListener after certain duration , So is there any way to achieve using Spring AMQP.
Eg .
Producer produces the message and message goes to RabbitMQ Q , The message gets received Listener listening to that Q immediately, I want to delay that message to be received at consumer side say after some configuration parameter say 1000ms
The RabbitMQ provides for this purpose Delayed Exchange feature.
Starting with version 1.6 Spring AMQP also provides a high level API on the matter: http://docs.spring.io/spring-amqp/reference/html/_reference.html#delayed-message-exchange:
<rabbit:topic-exchange name="topic" delayed="true" />
MessageProperties properties = new MessageProperties();
properties.setDelay(15000);
template.send(exchange, routingKey,
MessageBuilder.withBody("foo".getBytes()).andProperties(properties).build());
UPDATE
Before Spring AMQP 1.6 you should do like this:
#Bean
CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-delayed-type", "direct");
return new CustomExchange("my-exchange", "x-delayed-message", true, false, args);
}
...
MessageProperties properties = new MessageProperties();
properties.setHeader("x-delay", 15000);
template.send(exchange, routingKey,
MessageBuilder.withBody("foo".getBytes()).andProperties(properties).build());
Also see this question and its answer: Scheduled/Delay messaging in Spring AMQP RabbitMq
If you use spring boot, it can be like this:
#Bean
Queue queue() {
return QueueBuilder.durable(queueName)
.withArgument("x-dead-letter-exchange", dlx)
.withArgument("x-dead-letter-routing-key", dlq)
.build();
}
#Bean
TopicExchange exchange() {
return (TopicExchange) ExchangeBuilder.topicExchange(topicExchangeName)
.delayed()
.build();
#Bean
Binding binding() {
return BindingBuilder.bind(queue()).to(exchange()).with(queueName);
}
I am new to JEE7 and have been working on some quick exercises but I've bumped into a problem. I have a sample Java SE application that sends a message to an ActiveMQ queue and I have an MDB deployed on Wildfly 8 that reads the messages as they come in. This all works fine and I can receive the messages using getText. However, when I use getBody to get the message body, I get an "Unknown Error". Can anyone let me know what I'm doing wrong?
Here's my code below;
/***CLIENT CODE****/
import javax.jms.*;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class SimpleMessageClient {
// URL of the JMS server. DEFAULT_BROKER_URL will just mean
// that JMS server is on localhost
private static String url = ActiveMQConnection.DEFAULT_BROKER_URL;
// Name of the queue we will be sending messages to
private static String subject = "MyQueue";
public static void main(String[] args) throws JMSException {
// Getting JMS connection from the server and starting it
ConnectionFactory connectionFactory =
new ActiveMQConnectionFactory(url);
Connection connection = connectionFactory.createConnection();
connection.start();
// JMS messages are sent and received using a Session. We will
// create here a non-transactional session object. If you want
// to use transactions you should set the first parameter to 'true'
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
// Destination represents here our queue 'TESTQUEUE' on the
// JMS server. You don't have to do anything special on the
// server to create it, it will be created automatically.
Destination destination = session.createQueue(subject);
// MessageProducer is used for sending messages (as opposed
// to MessageConsumer which is used for receiving them)
MessageProducer producer = session.createProducer(destination);
// We will send a small text message saying 'Hello' in Japanese
TextMessage message = session.createTextMessage("Jai Hind");
//Message someMsg=session.createMessage();
// someMsg.
// Here we are sending the message!
producer.send(message);
System.out.println("Sent message '" + message.getText() + "'");
connection.close();
}
}
And the consumer;
package javaeetutorial.simplemessage.ejb;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
#MessageDriven(activationConfig = {
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(propertyName = "destination", propertyValue = "MyQueue")
})
public class SimpleMessageBean implements MessageListener {
#Resource
private MessageDrivenContext mdc;
static final Logger logger = Logger.getLogger("SimpleMessageBean");
public SimpleMessageBean() {
}
#Override
public void onMessage(Message inMessage) {
try {
if (inMessage instanceof TextMessage) {
logger.log(Level.INFO,
"MESSAGE BEAN: Message received: {0}",
inMessage.getBody(String.class));
} else {
logger.log(Level.WARNING,
"Message of wrong type: {0}",
inMessage.getClass().getName());
}
} catch (JMSException e) {
e.printStackTrace();
logger.log(Level.SEVERE,
"SimpleMessageBean.onMessage: JMSException: {0}",
e.toString());
mdc.setRollbackOnly();
}
}
}
Part of the error I get is;
16:47:48,510 ERROR [org.jboss.as.ejb3] (default-threads - 32) javax.ejb.EJBTransactionRolledbackException: Unexpected Error
16:47:48,511 ERROR [org.jboss.as.ejb3.invocation] (default-threads - 32) JBAS014134: EJB Invocation failed on component SimpleMessageBean for method public void javaeetutorial.simplemessage.ejb.SimpleMessageBean.onMessage(javax.jms.Message): javax.ejb.EJBTransactionRolledbackException: Unexpected Error
at org.jboss.as.ejb3.tx.CMTTxInterceptor.handleInCallerTx(CMTTxInterceptor.java:157) [wildfly-ejb3-8.2.0.Final.jar:8.2.0.Final]
at org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInCallerTx(CMTTxInterceptor.java:253) [wildfly-ejb3-8.2.0.Final.jar:8.2.0.Final]
at org.jboss.as.ejb3.tx.CMTTxInterceptor.required(CMTTxInterceptor.java:342) [wildfly-ejb3-8.2.0.Final.jar:8.2.0.Final]
The method
<T> T Message.getBody(Class<T> c)
you refer to was an addition to JMS 2.0 (see also: http://www.oracle.com/technetwork/articles/java/jms20-1947669.html).
While WildFly 8 is fully compliant to Java EE 7 and therefore JMS 2.1, the current ActiveMQ (5.12.0) is still restricted to JMS 1.1.
Since you presumably import the JMS 2.1 API in your SimpleMessageBean, you reference a method simply not present in the ActiveMQ message.
When you try to call the getBody()-method on the message, it cannot be resolved in the message implementation and hence an AbstractMethodError is thrown. This results in the rollback of the transaction which gives you the EJBTransactionRolledbackException.
I see two immediate solutions for your problem:
If you want to keep using ActiveMQ, confine yourself to the JMS 1.1 API. The getText()-method you mentioned is part of JMS 1.1 and therefore works flawlessly. See here for the JMS 1.1 API (https://docs.oracle.com/javaee/6/api/javax/jms/package-summary.html) and here for the current ActiveMQ API documentation (http://activemq.apache.org/maven/5.12.0/apidocs/index.html).
Switch to a JMS 2.x compliant message broker. Since you are using WildFly, I recommend taking a look at HornetQ (http://hornetq.jboss.org/).