Spring Integration for SimpMessagingTemplate - spring-integration

I have an MVC project that do the below
#Autowired
SimpMessagingTemplate messagingTemplate;
private void sendAlarmUpdate(AlarmNotify alarmNotify) {
messagingTemplate.convertAndSend("/topic/notify/alarm",alarmNotify);
}
I am trying to convert this into Spring Integration using int-stomp:outbound-channel-adapter but i am getting exception that the message payload should be array of bytes , i tried converting my object into JSON but still the same , what is the correct way to send a STOMP JSON message from spring-integration
#Bean
public Reactor2TcpStompClient stompClient() {
Reactor2TcpStompClient stompClient = new Reactor2TcpStompClient("192.168.70.XXX", 61613);
//stompClient.setMessageConverter(new PassThruMessageConverter());
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.afterPropertiesSet();
stompClient.setTaskScheduler(taskScheduler);
stompClient.setReceiptTimeLimit(5000);
return stompClient;
}
#Bean
public StompSessionManager stompSessionManager() {
Reactor2TcpStompSessionManager stompSessionManager = new Reactor2TcpStompSessionManager(stompClient());
stompSessionManager.setAutoReceipt(true);
return stompSessionManager;
}
<int:chain input-channel="stompChannel">
<!--<int:object-to-json-transformer />-->
<int-stomp:outbound-channel-adapter stomp-session-manager="stompSessionManager" destination="/topic/notify/alarm1" id="stompAdapter" />
</int:chain>

The problem with STOMP protocol on the wire that it really requires byte[] as message body.
So, you don't have choice unless convert your JSON into byte[] manually, or provide StringMessageConverter into your stompClient instead of that commented PassThruMessageConverter.

Related

How to manual ack on AMQP-Backed Channel

I'm using an AMQP-Backed channel in my workflow and I would like to handle ACK manually.
I though this could be done as in the AMQP Inbound Channel where you get reference of the AMQP Client Channel in the Message Header but I do not find the Header AmqpHeaders.CHANNEL in the message. Here is how I've setup my AmqpChannelFactoryBean :
#Bean(name = AMQP_BACKED_CHANNEL)
public AmqpChannelFactoryBean pubSub(ConnectionFactory connectionFactory) {
AmqpChannelFactoryBean factoryBean = new AmqpChannelFactoryBean();
factoryBean.setConnectionFactory(connectionFactory);
factoryBean.setQueueName(AMQP_BACKED_CHANNEL);
factoryBean.setAcknowledgeMode(AcknowledgeMode.MANUAL);
factoryBean.setPubSub(false);
factoryBean.setExtractPayload(true);
return factoryBean;
}
My feeling is that I should not use the same approach as in AMQP Inbound Channel but cannot find documentation out there. Anyone can help, please?
Little update: Not sure if this is the "proper" way to do it but inspired by the comment of Artem Bilam in this post Spring AMQP Integration - Consumer Manual Acknowledgement I've resolved using a MethodBeforeAdvice. Basically my MethodBeforeAdvice is applied to the invokeListener() method of the SimpleMessageListenerContainer created by the AmqpChannelFactoryBean therefore I can get hold of the Amqp Client Channel and amqp headers and the game is done! Below is my code modified (just a bit concise for the sake of a clearer reading):
#Bean(name = AMQP_BACKED_CHANNEL)
public AmqpChannelFactoryBean amqpBackedChannel(ConnectionFactory connectionFactory) {
AmqpChannelFactoryBean factoryBean = new AmqpChannelFactoryBean();
factoryBean.setConnectionFactory(connectionFactory);
factoryBean.setQueueName(AMQP_BACKED_CHANNEL);
factoryBean.setAcknowledgeMode(AcknowledgeMode.MANUAL);
factoryBean.setPubSub(false);
factoryBean.setExtractPayload(true);
factoryBean.setConcurrentConsumers(5);
MethodBeforeAdvice methodBeforeAdvice = new MethodBeforeAdvice() {
#Override public void before(Method method, Object[] args, Object target)
throws Throwable {
Channel amqpClientChannel = (Channel) args[0];
Message amqpCoreMessage = (Message) args[1];
Map<String, Object>
amqpCoreMessageHeaders = amqpCoreMessage.getMessageProperties().getHeaders();
amqpCoreMessageHeaders.put(AmqpHeaders.CHANNEL,amqpClientChannel);
}
};
factoryBean.setAdviceChain(new Advice[]{methodBeforeAdvice});
return factoryBean;
}

Sending messages to different topics using spring integration gateway

I am trying to use spring integration for send mqtt messages to a broker and I am trying to use the gateway interface.
#Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
//set the factory details
return factory:
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound() {
MqttPahoMessageHandler messageHandler =
new MqttPahoMessageHandler("randomString", mqttClientFactory());
//set handler details
messageHandler.setDefaultTopic(topic);
return messageHandler;
}
#Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
#MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
private interface MyGateway {
void sendToMqtt(String data);
}
My question is: If I want to use the gateway handler to send messages to different topics how would I do that without having to create an adapter for each topic ?
Thanks.
Hope I formulated my question clearly and the code is properly formatted.
You need to set the target topic in a message header.
Here is one way to do that...
void sendToMqtt(String data, #Header(MqttHeaders.TOPIC) String topic);
The gateway proxy will assemble the message with the header, which is then used by the outbound adapter.

#Transformer for ObjectToJson Not Working in Spring Integration

A POJO Message.java is to be Converted to JSON(JSON is to be sent to pubsub Topic,using Spring Integration MessageChannels.),using following:
#Bean
#Transformer(inputChannel = "pubsubOutputChannel", outputChannel = "handleOutChannel")
public ObjectToJsonTransformer transformOut() {
return new ObjectToJsonTransformer();
}
#MessagingGateway(defaultRequestChannel = "pubsubOutputChannel")
public interface PubsubOutboundGateway {
void sendToPubsub(Messages msg);
}
#Bean
#ServiceActivator(inputChannel = "handleOutChannel")
public MessageHandler messageSender(PubSubOperations pubsubTemplate) {
return new PubSubMessageHandler(pubsubTemplate, "TestTopic");
}
When i call sendToPubsub() with an instance of Message.java with required properties set,i get an error "Null".
Is serviceActivator not able to receive the required data?
Any suggestions to fix this?.
Yes, it can't do that because you just don't tell it to do that.
Your gateway is configured for this:
#MessagingGateway(defaultRequestChannel = "handleOutChannel")
But that is not an input channel for the ObjectToJsonTransformer. So, whatever you send over that gateway is going directly to the messageSender service activator.
Try to configure your gateway like this:
#MessagingGateway(defaultRequestChannel = "pubsubOutputChannel")

Why does a AmqpChannelFactoryBean with Jackson2JsonMessageConverter not store type?

I'm trying to use Spring integration with RabbitMQ, using RabbitMQ backed Spring integration channels. (Which seems almost not documented for some reason, is this new?).
To do this, it seems I can use AmqpChannelFactoryBean to create a channel.
To set up message conversion, I use a Jackson2JsonMessageConverter.
When I use a GenericMessage with a POJO payload, it refuses to de-serialize it from Java, basically because it doesn't know the type. I would have expected the type to be automagically be put on the header, but on the header there is only __TypeId__=org.springframework.messaging.support.GenericMessage.
In Spring boot my configuration class looks like this:
#Configuration
public class IntegrationConfiguration {
#Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public AmqpChannelFactoryBean myActivateOutChannel(CachingConnectionFactory connectionFactory,
MessageConverter messageConverter) {
AmqpChannelFactoryBean factoryBean = new AmqpChannelFactoryBean(true);
factoryBean.setConnectionFactory(connectionFactory);
factoryBean.setQueueName("myActivateOut");
factoryBean.setPubSub(false);
factoryBean.setAcknowledgeMode(AcknowledgeMode.AUTO);
factoryBean.setDefaultDeliveryMode(MessageDeliveryMode.PERSISTENT);
factoryBean.setMessageConverter(messageConverter);
return factoryBean;
}
#Bean
#ServiceActivator(inputChannel = "bsnkActivateOutChannel", autoStartup="true")
public MessageHandler mqttOutbound() {
return m -> System.out.println(m);
}
}
Sending is done like this:
private final MessageChannel myActivateOutChannel;
#Autowired
public MySender(MessageChannel myActivateOutChannel) {
this.myActivateOutChannel = myActivateOutChannel;
}
#Override
public void run(ApplicationArguments args) throws Exception {
MyPojo pojo = new MyPojo();
Message<MyPojo> msg = new GenericMessage<>(pojo);
myActivateOutChannel.send(msg);
}
If I set my own classmapper, things do work as they should. But I would have to use many MessageConverters if I set up things like that.
E.g.
converter.setClassMapper(new ClassMapper() {
#Override
public void fromClass(Class< ? > clazz, MessageProperties properties) {
}
#Override
public Class< ? > toClass(MessageProperties properties) {
return MyPojo.class;
}
});
Am I using this wrong? Am I missing some configuration? Any other suggestions?
Thanks!! :)
Note: Looking more at things, I'm guessing the 'Spring integration' way would be to add a Spring integration JSON transformer on each side, which means also adding two additional direct channels per RabbitMQ queue?
This feels wrong to me, since I've got triple the channels then (6! for in/out), but mayby that's how the framework is supposed to be used? Couple all the simple steps with direct channels? (Do I keep the persistence which the RabbitMQ channels offer in that case? Or do I need some transaction mechanism if I want that? Or is it inherent in how direct channels work?)
I've also noticed now there's both a Spring-integration MessageConverter, and a Spring-amqp MessageConverter. The latter being the one I've used. Would the other work the way I want it to? A quick glance at the code suggests it doesn't store the object type in the message header?
Prior to version 4.3, amqp-backed channels only supported serializable payloads; the work around was to use channel adapters instead (which support mapping).
INT-3975 introduced a new property extractPayload which causes the message headers to be mapped to rabbitmq headers and the message body is just the payload instead of a serialized GenericMessage.
Setting extractPayload to true should solve your problem.

Spring Integration 4 asynchronous request/response

I am trying to write a simple message flow using Spring Integration v4's DSL APIs which would look like this:
-> in.ch -> Processing -> JmsGatewayOut -> JMS_OUT_QUEUE
Gateway
<- out.ch <- Processing <- JmsGatewayIn <- JMS_IN_QUEUE
With the request/response being asynchronous, when I inject a message via the initial Gateway, the message goes all the way to JMS_OUT_QUEUE. Beyond this message flow, a reply message is put back into JMS_IN_QUEUE which it is then picked up by JmsGatewayIn. At this point, the message is Processed and placed into out.ch (I know the response gets to out.ch because I have a logger interceptor there which logs the message being placed there) but, the Gateway never receives the response.
Instead of a response, the system outside of this message flow which picked up the message from JMS_OUT_QUEUE and placed the response in JMS_IN_QUEUE, receives a javax.jms.MessageFormatException: MQJMS1061: Unable to deserialize object on its own JmsOutboundgateway (I think it is failing to deserialize a jms reply object from looking at the logs).
I have clearly not got something configured correctly but I don't know exactly what. Does anyone know what I am missing?
Working with spring-integration-core-4.0.3.RELEASE, spring-integration-jms-4.0.3.RELEASE, spring-integration-java-dsl-1.0.0.M2, spring-jms-4.0.6.RELEASE.
My Gateway is configured as follows:
#MessagingGateway
public interface WsGateway {
#Gateway(requestChannel = "in.ch", replyChannel = "out.ch",
replyTimeout = 45000)
AResponse process(ARequest request);
}
My Integration flow is configured as follows:
#Configuration
#EnableIntegration
#IntegrationComponentScan
#ComponentScan
public class IntegrationConfig {
#Bean(name = "in.ch")
public DirectChannel inCh() {
return new DirectChannel();
}
#Bean(name = "out.ch")
public DirectChannel outCh() {
return new DirectChannel();
}
#Autowired
private MQQueueConnectionFactory mqConnectionFactory;
#Bean
public IntegrationFlow requestFlow() {
return IntegrationFlows.from("in.ch")
.handle("processor", "processARequest")
.handle(Jms.outboundGateway(mqConnectionFactory)
.requestDestination("JMS_OUT_QUEUE")
.correlationKey("JMSCorrelationID")
.get();
}
#Bean
public IntegrationFlow responseFlow() {
return IntegrationFlows.from(Jms.inboundGateway(mqConnectionFactory)
.destination("JMS_IN_QUEUE"))
.handle("processor", "processAResponse")
.channel("out.ch")
.get();
}
}
Thanks for any help on this,
PM.
First of all your configuration is bad:
Since you start the flow from WsGateway#process you really should wait reply there.
The gateway's request/reply capability is based on TemporaryReplyChannel, which is placed to the headers as non-serializable value.
As long as you wait rely on that gateway, actually there is no reason to provide the replyChannel, if you aren't going to do some publish-subscribe logic on the reply.
As you send message to the JMS queue, you should understand that consumer part might be a separete remote application. And the last one might know nothing about your out.ch.
The JMS request/reply capability is really based on JMSCorrelationID, but it isn't enough. The one more thing here is a ReplyTo JMS header. Hence, if you are going to send reply from the consumer you should really just rely on the JmsGatewayIn stuff.
So I'd change your code to this:
#MessagingGateway
public interface WsGateway {
#Gateway(requestChannel = "in.ch", replyTimeout = 45000)
AResponse process(ARequest request);
}
#Configuration
#EnableIntegration
#IntegrationComponentScan
#ComponentScan
public class IntegrationConfig {
#Bean(name = "in.ch")
public DirectChannel inCh() {
return new DirectChannel();
}
#Autowired
private MQQueueConnectionFactory mqConnectionFactory;
#Bean
public IntegrationFlow requestFlow() {
return IntegrationFlows.from("in.ch")
.handle("processor", "processARequest")
.handle(Jms.outboundGateway(mqConnectionFactory)
.requestDestination("JMS_OUT_QUEUE")
.replyDestination("JMS_IN_QUEUE"))
.handle("processor", "processAResponse")
.get();
}
}
Let me know, if it is appropriate for you or try to explian why you use two-way gateways for one one-way cases. Maybe Jms.outboundAdapter() and Jms.inboundAdapter() are more good for you?
UPDATE
How to use <header-channels-to-string> from Java DSL:
.enrichHeaders(e -> e.headerChannelsToString())

Resources