Set message header in rabbitmq while sending - jackson

I want to set message header while sending a message to rabbit.
I am using below code, but confused how to set message header in it.
public static <T> void sendMessage(String routingKey,final Object message,Class<T> type){
DefaultClassMapper typeMapper = new DefaultClassMapper();
typeMapper.setDefaultType(type);
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
converter.setClassMapper(typeMapper);
RabbitTemplate template = new RabbitTemplate(getConnectionFactory));
template.setMessageConverter(converter);
template.convertAndSend(routingKey, message);
}
In above method i am simply arguementing java POJO object and its type to send. I want to know where should i set message header here.
How to listen the message properties at listener end?

Java 8:
template.convertAndSend(routingKey, message, m -> {
m.getMessageProperties().getHeaders().put("foo", "bar");
m.getMessageProperties().setPriority(priority);
return m;
});
Java 6,7:
template.convertAndSend(routingKey, message, new MessagePostProcessor() {
#Override
public Message postProcessMessage(Message m) throws AmqpException {
m.getMessageProperties().getHeaders().put("foo", "bar");
m.getMessageProperties().setPriority(priority);
return m;
}
});

Related

Setting reply_to properties in RabbitListener

I'm a newbie in RabbitMQ. I use the following listener in my Spring configuration:
#RabbitListener(queues = "${rabbitmq.queue.a}")
#SendTo("${rabbitmq.queue.b}")
public ResponseMessage receive(RequestMessage request) {
ResponseMessage response = new ResponseMessage();
response.setRawPayload(jsonMapper.toJson(request));
return response;
}
And I would like that when sending a message to the rabbitmq.queue.b queue from this method, the "reply_to "property should be set to this message.
#Value("${rabbitmq.queue.b}")
private String responseQueue;
#Bean
Queue responseQueue() {
return QueueBuilder
.durable(responseQueue)
.build();
}
Thanks for any help.
Not sure what is that "should be set to this message.", but reply_to can be handled with something like this:
#RabbitListener(queues = "${rabbitmq.queue.a}")
#SendTo("${rabbitmq.queue.b}")
public Message<ResponseMessage> receive(RequestMessage request) {
ResponseMessage response = new ResponseMessage();
response.setRawPayload(jsonMapper.toJson(request));
return org.springframework.messaging.support.MessageBuilder.withPayload(response)
.setHeader(AmqpHeaders.REPLY_TO, "YOUR_QUEUE_NAME_FOR_REPLY")
.build();
}

spring amqp custom TTL and retry count

We are trying to implement Retry mechanism on client exceptions. We want to be able to set different routing key, ttl and retry count based on the content in each message. We want to keep the handler simple, i.e; for handleMessage to throw exception. How do we handle this exception and send the message to DLX with appropriate parameters. On retry if the failure happens again - message would be discarded (acknowledged) , or will be put back on DLX with incrementing the retry count. where would we implement this logic and how would be wired?
========================
With Gary's direction, I was able to implement. Here are excerpts ..
#Bean
public SimpleMessageListenerContainer listenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
jsonMessageHandler.setQueueName(queueName);
container.setQueueNames(queueName);
container.setMessageListener(jsonMessageListenerAdapter());
container.setAdviceChain(new Advice[]{retryOperationsInterceptor()});
return container;
}
#Bean
public MessageListenerAdapter messageListenerAdapter() {
return new MessageListenerAdapter(messageHandler,messageConverter);
}
#Bean
public MessageListenerAdapter jsonMessageListenerAdapter() {
return new MessageListenerAdapter(jsonMessageHandler);
}
#Bean
RetryOperationsInterceptor retryOperationsInterceptor() {
return RetryInterceptorBuilder.stateless().recoverer(republishMessageRecoverer).maxAttempts(1).build();
}
#Bean
RepublishMessageRecoverer republishMessageRecoverer() {
return new MyRepublishMessageRecoverer(rabbitTemplate());
}
==========
public class MyRepublishMessageRecoverer extends RepublishMessageRecoverer {
// - constructor
#Override
public void recover(Message message, Throwable cause) {
//Deal with headers
long currentCount = 0;
List xDeathList = (List)message.getMessageProperties().getHeaders().get("x-death");
if(xDeathList != null && xDeathList.size() > 0) {
currentCount = (Long)((Map)(xDeathList.get(0))).get("count");
}
if(currentCount < context.getRules().getNumberOfRetries()) {
//message sent to DLX
this.retryTemplate.send(handlerProperties.getSystem(), message);
} else {
//message ignored
}
throw new AmqpRejectAndDontRequeueException(cause);
}
}
You can't modify a rejected message, it is routed to the DLX/DLQ unchanged (except x-death headers are added by the broker).
You have to republish to the DLX/DLQ yourself if you want to change message properties.
You can use Spring Retry with a customized RepublishMessageRecoverer to do this.

Custom Client MessageInspector recording requests but not responses

I have a Custom ClientMessageInspector that records requests but not replies to my service.
The code is:
namespace MessageListener.Instrumentation
{
public class MessageInspector : IClientMessageInspector
{
private Message TraceMessage(MessageBuffer buffer)
{
// Must use a buffer rather than the original message, because the Message's body can be processed only once.
Message msg = buffer.CreateMessage();
using (RREM_GilbaneEntities3 entities3 = new RREM_GilbaneEntities3())
{
SOAPMessage soapMessages = new SOAPMessage
{
SOAPMessage1 = msg.ToString(),
created = DateTime.Now,
source = "Interface12",
sourceIP = "Interface12"
};
entities3.SOAPMessages.Add(soapMessages);
entities3.SaveChanges();
}
//Return copy of origonal message with unalterd State
return buffer.CreateMessage();
}
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
reply = TraceMessage(reply.CreateBufferedCopy(int.MaxValue));
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
request = TraceMessage(request.CreateBufferedCopy(int.MaxValue));
return null;
}
}
}
What seems to be happening is both AfterRecievReply and BeforeSendRequest are being called. In AfterRecieveReply before I call TraceMessage, I can see the whole reply. Inside TraceMessage, when I do:
// Must use a buffer rather than the original message, because the Message's body can be processed only once.
Message msg = buffer.CreateMessage();
it turns the reply into junk:
msg {<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header />
<soap:Body>... stream ...</soap:Body>
</soap:Envelope>}
What's going on?
The reply isn't a junk message - it's just when you call ToString on it that it doesn't show the body of the message. Remember that a message can only be consumed once; once its body is read, it cannot be read again. Since many places (including the watch window of debuggers) will call ToString on an object, this method is implemented in a way that if it doesn't know for sure that a message body can be read multiple times, then it won't, which seems to be your case. If you want to really write out the message, try using this code:
public string MessageToString(Message message) {
using (MemoryStream ms = new MemoryStream()) {
XmlWriterSettings ws = new XmlWriterSettings();
ws.Encoding = new UTF8Encoding(false);
using (XmlWriter w = XmlWriter.Create(ms)) {
message.WriteMessage(w);
w.Flush();
return ws.Encoding.GetString(ms.ToArray());
}
}
}

Mule FunctionalTestComponent event callbacks - event is never received

I am trying to use the FunctionalTestComponent to catch messages in Mule so I can assert against certain Header properties mid flow. But events are never received and my test always passes where it should fail. How can I configure my test to catch the events? Here is my test method:
#Test
public void testCallback() throws Exception {
FunctionalTestComponent ftc = getFunctionalTestComponent("test");
ftc.setEventCallback(new EventCallback()
{
public void eventReceived(MuleEventContext context, Object component)
throws Exception
{
assertTrue(false);
System.out.println("Thanks for calling me back");
}
});
MuleClient client = muleContext.getClient();
MuleMessage reply = client.send("vm://test", TEST_MESSAGE, null, 5000);
}
The config is just 2 flows with a test:component in the second flow which is referenced by the vm://test flow.
The only way I could get it to work was by using a latch and an AtomicReference to save the MuleMessage so I can run assertions after the muleClient.send.
FunctionalTestComponent ftc = getFunctionalTestComponent("test");
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<MuleMessage> message = new AtomicReference<MuleMessage>();
EventCallback callback = new EventCallback() {
public void eventReceived(MuleEventContext context, Object component)
throws Exception {
if (1 == latch.getCount()) {
message.set(context.getMessage());
System.out.println("1111");
latch.countDown();
}
}
};
ftc.setEventCallback(callback);
MuleClient client = muleContext.getClient();
client.send("vm://test", TEST_MESSAGE, null);
latch.await(10, TimeUnit.SECONDS);
MuleMessage msg = (MuleMessage) message.get();

How to return the error message to WCF service generated in Message Inspector with Schema Validation

void validateMessage(ref System.ServiceModel.Channels.Message message)
{
XmlDocument bodyDoc = new XmlDocument();
var body = message.GetReaderAtBodyContents();
bodyDoc.Load(body);
XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas = schemas;
settings.ValidationType = ValidationType.Schema;
settings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
XmlReader reader = XmlReader.Create(new XmlNodeReader(bodyDoc), settings);
while (reader.Read()) ; // do nothing, just validate
// Create new message
Message newMsg = Message.CreateMessage(message.Version, null,
new XmlNodeReader(bodyDoc.DocumentElement));
newMsg.Headers.CopyHeadersFrom(message);
foreach (string propertyKey in message.Properties.Keys)
newMsg.Properties.Add(propertyKey, message.Properties[propertyKey]);
// Close the original message and return new message
message.Close();
message = newMsg;
}
private static void ValidationCallBack(object sender, ValidationEventArgs e)
{
Console.WriteLine("Validation Error: {0}", e.Message);
//throw new RequestValidationFault(e.Message);
throw new FaultException<string>(e.Message);
}
object IDispatchMessageInspector.AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
try
{
validateMessage(ref request);
}
catch (FaultException e)
{
throw new FaultException<string>(e.Message);
}
return null;
}
The above code screenshot help me validate message requested from client against the schema and throw the errors if some inconsistency happen. But I don't know how to pass error message back to the WCFservice since I also would like to return the error message to the client.
I tried to add the error message to request message and return to WCF service. But I have no idea on how to put the error message to its parent element in the body content which is easy for me to get it in the service operation. Thanks in advance!