Spring Integration manually publish message to channel - java

I'm in the process of learning how to use the Java Spring Framework and started experimenting with Spring Integration. I'm trying to use Spring Integration to connect my application to an MQTT broker both to publish and subscribe to messages but I'm having trouble finding a way to manually publish messages to an outbound channel. If possible I want to build it using notations in the java code exclusively rather than xml files defining beans and other related configuration.
In every example I've seen the solution to manually publishing a message seems to be to use a MessagingGateway Interface and then use the SpringApplicationBuilder to get the ConfigurableApplicationContext to get a reference to the gateway interface in the main method. The reference is then used to publish a message. Would it be possible to use AutoWired for the interface instead? In my attempts I just get a NullPointer.
My aim is to build a game where I subscribe to a topic to get game messages and then whenever the user is ready to make the next move, publish a new message to the topic.
Update:
This is one of the examples I've been looking at of how to setup an outbound channel: https://docs.spring.io/spring-integration/reference/html/mqtt.html
Update 2 after answer from Gary Russel:
This is some example code I wrote after looking at examples which gets me a NullPointer when using #AutoWired for the Gateway when running gateway.sendToMqtt in Controller.java. What I want to achieve here is to send an mqtt message manually when a GET request is handled by the controller.
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
Controller.java
#RestController
#RequestMapping("/publishMessage")
public class Controller {
#Autowired
static Gateway gateway;
#RequestMapping(method = RequestMethod.GET)
public int request(){
gateway.sendToMqtt("Test Message!");
return 0;
}
}
MqttPublisher.java
#EnableIntegration
#Configuration
public class MqttPublisher {
#Bean
public MqttPahoClientFactory mqttClientFactory(){
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setServerURIs("tcp://localhost:1883");
return factory;
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound(){
MqttPahoMessageHandler messageHandler =
new MqttPahoMessageHandler("clientPublisher", mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("topic");
return messageHandler;
}
#Bean
public MessageChannel mqttOutboundChannel(){
return new DirectChannel();
}
#MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface Gateway {
void sendToMqtt(String data);
}
}
Update:
Not sure if this is the proper logging but it is what I get from adding:
logging.level.org.springframework.web=Debug
logging.level.org.hibernate=Error
to application.properties.
https://hastebin.com/cuvonufeco.hs

Use a Messaging Gateway or simply send a message to the channel.
EDIT
#SpringBootApplication
public class So47846492Application {
public static void main(String[] args) {
SpringApplication.run(So47846492Application.class, args).close();
}
#Bean
public ApplicationRunner runner(MyGate gate) {
return args -> {
gate.send("someTopic", "foo");
Thread.sleep(5_000);
};
}
#Bean
#ServiceActivator(inputChannel = "toMqtt")
public MqttPahoMessageHandler mqtt() {
MqttPahoMessageHandler handler = new MqttPahoMessageHandler("tcp://localhost:1883", "foo",
clientFactory());
handler.setDefaultTopic("myTopic");
handler.setQosExpressionString("1");
return handler;
}
#Bean
public MqttPahoClientFactory clientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setUserName("guest");
factory.setPassword("guest");
return factory;
}
#Bean
public MqttPahoMessageDrivenChannelAdapter mqttIn() {
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter("tcp://localhost:1883", "bar", "someTopic");
adapter.setOutputChannelName("fromMqtt");
return adapter;
}
#ServiceActivator(inputChannel = "fromMqtt")
public void in(String in) {
System.out.println(in);
}
#MessagingGateway(defaultRequestChannel = "toMqtt")
public interface MyGate {
void send(#Header(MqttHeaders.TOPIC) String topic, String out);
}
}

Related

Catching publish errors in GCP PubSub and Spring Boot

I have a Spring Boot application which needs to occasionally publish messages to GCP PubSub. I implemented it following the instructions on the spring boot page (https://spring.io/guides/gs/messaging-gcp-pubsub/) so I have implemented the following configuration file:
#Configuration
public class PubSubConfiguration {
#Value("${myprog.pubsub.sms-topic}")
private String topic;
#Bean
#ServiceActivator(inputChannel = "pubsubOutputChannel")
public MessageHandler messageSender(PubSubTemplate pubsubTemplate) {
return new PubSubMessageHandler(pubsubTemplate, this.topic);
}
#MessagingGateway(defaultRequestChannel = "pubsubOutputChannel")
public interface PubsubOutboundGateway {
void sendToPubsub(String text);
}
}
From my rest controller, I autowire the message gateway and call sendToPubsub:
#RequestMapping("/api/stuff")
#RestController
public class StuffController {
PubSubConfiguration.PubsubOutboundGateway messagingGateway;
#Autowired
public StuffController(#SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") PubSubConfiguration.PubsubOutboundGateway messagingGateway) {
this.messagingGateway = messagingGateway;
}
#RequestMapping(method = RequestMethod.POST, path = "/go")
public ResponseEntity<String> send() {
messagingGateway.sendToPubsub("TEST");
return new ResponseEntity<>("Ok!", HttpStatus.OK);
}
}
This works, however due to our particular use case I would like to respond with an error if publishing fails. If, for example, I configure a non-existent topic I would like to return a 500 error whereas it currently returns 200 and throws an exception asynchronously later. Is there any way I can access a future at the point of publishing?
Spring Cloud GCP PubSub implementation uses Spring Integration framework and rely on it. For this, your send to PubSub method have to throw an exception as described in the Spring integration documentation
#MessagingGateway(defaultRequestChannel = "pubsubOutputChannel")
public interface PubsubOutboundGateway {
void sendToPubsub(String text) throws MessagingException;
}

Netty as WebSocket server not starting in SpringBootTest

I'm trying to write a simple WebSocket server app based on on Spring and Netty.
My application looks like this
#SpringBootApplication
public class DemoReactiveWSApp {
public static void main(String[] args) {
SpringApplication.run(DemoReactiveWSApp.class, args);
}
}
with the following configuration
#Configuration
public class WebSocketConfig {
#Bean
public HandlerMapping handlerMapping() {
final Map<String, WebSocketHandler> handlerMap = new HashMap<>();
// will be populated later with routes and handlers
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(handlerMap);
mapping.setOrder(-1);
return mapping;
}
#Bean
public RequestUpgradeStrategy requestUpgradeStrategy() {
return new ReactorNettyRequestUpgradeStrategy();
}
}
When I run it, everything boots up, and I can attempt (for now) to establish WS connection.
However, when I want to start it in a test
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoReactiveWSAppTest {
#LocalServerPort
private String port;
#Test
public void givenContext_WhenStartingApplication_ThenItLoads() throws InterruptedException {
System.out.println("Port: " + port);
}
}
the server never seems to boot.
Am I forgetting something?
Stupid me, I forgot #RunWith(SpringRunner.class)

Asynchronous RPC using Spring Boot RabbitMQ

I have implemented a basic asynchronous RPC call using spring boot 1.4 and rabbit mq.
My intention is to use this example as a basis of communication
among micro services.For example, Publisher.java and Subscriber.java could be two micro services talking to each other.
The code shown works fine, but I am curious to know if there are any better ways
of doing this?
My queries as follows:
For subscriber to listen to request queue using #RabbitListener annotation , I did not had to declare directExchange() and binding() beans in configuration. But for asyncRabbitTemplate to read response from reply queue, I had to declare directExchange() and binding() beans in configuration.
Is there any way I can avoid it, because I feel it is code duplication as I am declaring these beans twice.
In real world application, there would be many such calls between micro services.And as per my understanding , I would need to declare similar rpcReplyMessageListenerContainer() and asyncRabbitTemplate() for each request-reply call.Is that correct?
Code as follows.
Link to Github
Config.java
#Configuration("asyncRPCConfig")
#Profile("async_rpc")
#EnableScheduling
#EnableRabbit
#ComponentScan(basePackages = {"in.rabbitmq.async_rpc"})
public class Config {
#Value("${queue.reply}")
private String replyQueue;
#Value("${exchange.direct}")
private String directExchange;
#Value("${routingKey.reply}")
private String replyRoutingKey;
#Bean
public Publisher publisher() {
return new Publisher();
}
#Bean
public SimpleRabbitListenerContainerFactory simpleMessageListenerContainerFactory(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(jsonMessageConverter());
return template;
}
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public Queue replyQueueRPC() {
return new Queue(replyQueue);
}
#Bean
public SimpleMessageListenerContainer rpcReplyMessageListenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory);
simpleMessageListenerContainer.setQueues(replyQueueRPC());
simpleMessageListenerContainer.setReceiveTimeout(2000);
simpleMessageListenerContainer.setTaskExecutor(Executors.newCachedThreadPool());
return simpleMessageListenerContainer;
}
#Bean
public AsyncRabbitTemplate asyncRabbitTemplate(ConnectionFactory connectionFactory) {
return new AsyncRabbitTemplate(rabbitTemplate(connectionFactory),
rpcReplyMessageListenerContainer(connectionFactory),
directExchange + "/" + replyRoutingKey);
}
#Bean
public DirectExchange directExchange() {
return new DirectExchange(directExchange);
}
#Bean
public Binding binding() {
return BindingBuilder.bind(replyQueueRPC()).to(directExchange()).with(replyRoutingKey);
}
#Bean
public Subscriber subscriber() {
return new Subscriber();
}
}
Publisher.java
public class Publisher {
#Value("${routingKey.request}")
private String requestRoutingKey;
#Autowired
private DirectExchange directExchange;
private static SecureRandom SECURE_RANDOM;
static {
try {
SECURE_RANDOM = SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
#Autowired
private AsyncRabbitTemplate asyncRabbitTemplate;
#Scheduled(fixedDelay = 100 * 1)
public void publishToDirectExchangeRPCStyle() {
Integer integer = SECURE_RANDOM.nextInt();
SampleRequestMessage sampleRequestMessage = new SampleRequestMessage(String.valueOf(integer));
System.out.println("Sending out message on direct directExchange:" + sampleRequestMessage);
AsyncRabbitTemplate.RabbitConverterFuture<SampleResponseMessage> sampleResponseMessageRabbitConverterFuture = asyncRabbitTemplate
.convertSendAndReceive(directExchange.getName(), requestRoutingKey, sampleRequestMessage);
sampleResponseMessageRabbitConverterFuture.addCallback(
sampleResponseMessage ->
System.out.println("Response for request message:" + sampleRequestMessage + " is:" + sampleResponseMessage)
, failure ->
System.out.println(failure.getMessage())
);
}
}
Subscriber.java
public class Subscriber {
#RabbitHandler
#RabbitListener(
bindings = {
#QueueBinding(value = #Queue("${queue.request}"),
key = "${routingKey.request}",
exchange = #Exchange(value = "${exchange.direct}", type = ExchangeTypes.DIRECT, durable = "true"))})
public SampleResponseMessage subscribeToRequestQueue(#Payload SampleRequestMessage sampleRequestMessage, Message message) {
System.out.println("Received message :" + message);
return new SampleResponseMessage(sampleRequestMessage.getMessage());
}
}
Your solution is fine.
It is not clear what you are asking...
I had to declare directExchange() and binding() beans in configuration.
Is there any way I can avoid it, because I feel it is code duplication as I am declaring these beans twice.
#QueueBinding is simply a convenience on #RabbitListener and an alternative to declaring the queue, exchange and binding as #Beans.
If you are using a common #Config class you can simply omit the bindings attribute on the listener and use queues = "${queue.reply}" to avoid the duplication.
I would need to declare similar rpcReplyMessageListenerContainer() and asyncRabbitTemplate() for each request-reply call.
Is that correct?
Yes; although with the upcoming 2.0 release, you can use a DirectReplyToMessageListenerContainer which avoids the need for a separate reply queue for each service; when you send a message.
See the documentation here and here.
Starting with version 2.0, the async template now supports Direct reply-to instead of a configured reply queue.
(Should read "as an alternative to " rather than "instead of").
So you can use the same template to talk to multiple services.

Multiple dynamic HTTP endpoints

I want to run multiple HTTP endpoints which should be creates based on list of paths.
Currently I'm able to create one endpoint:
#MessagingGateway(defaultRequestChannel = "requestChannel")
public interface Gateway {
String sendReceive(String in);
}
#Bean
public MessageChannel requestChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow flow() {
return IntegrationFlows.from("requestChannel").transform(new ObjectToStringTransformer())
.handle(new MyHandle())
.get();
}
#Bean
public HttpRequestHandlingMessagingGateway httpGate() {
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true);
RequestMapping mapping = new RequestMapping();
mapping.setMethods(HttpMethod.POST);
mapping.setPathPatterns("/path");
gateway.setRequestMapping(mapping);
gateway.setRequestChannel(requestChannel());
gateway.setRequestPayloadType(byte[].class);
return gateway;
}
but I want to do somthing like this:
#Autowired
List<String> paths;
#PostConstruct
public void createEndpoints() {
for (String path : paths) {
//code for dynamic endpoint creation
}
}
private class MyHandle extends AbstractReplyProducingMessageHandler {
#Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return this.getMessageBuilderFactory().withPayload("Your message: " + requestMessage.getPayload());
}
}
Can you tell me how can I do it?
Since Java DSL 1.2 there is a IntegrationFlowContextexactly for such a use-case to register IntegrationFlow and dependent beans dynamically.
https://spring.io/blog/2016/09/27/java-dsl-for-spring-integration-1-2-release-candidate-1-is-available
The GA release today.
You should just follow with the samples in those blog post and pay attention to the org.springframework.integration.dsl.http.Http factory.
But, indeed, do that as early as possible. The #PostConstruct is good phase for this use-case.
When it will be later, the HandlerMapping won't be able to detect an new mapping. Just because it does the scan in its afterPropertiesSet().

How to create multiple channels with different receivers? Spring Redis pub/sub

I'm using Redis Publish Subscribe through Spring Data, but I'm having problems to add more than 1 Channel.
Currently I'm following the typical examples where the MessageListenerContainer is configured by adding a MessageListenerAdapter which has associated a Receiver class, as following:
The previous works perfectly and I'm able to push and receive messages.
However one I try to add a second listener adapter for creating a "channel with a differente receiver and I'm getting a NullPointerException.
The error is attached below. Is there a different way to adding a new adapter? In general I would like to add channels dynamically.
It is possible to add multiple channels associated with one specific receiver by providing a PatternTopic list at addMessageListener method.
Thanks for your help
I believe there is an important bug with Spring Redis when adding MessageListenerAdapter.
If the Receiver class does not extend from MessageListener (and so, implements onMessage) the inner method MethodInvoker() from MessageListenerAdapter class specifically ask if the Receiver is instance of MessageListener (see last line of image below).
To solve this problem just extend from MessageListener and then you can add the additional adapters directly.
It's a shame that spring-data-redis team do not enable issues in their github page to publish this bug. https://github.com/spring-projects/spring-data-redis
If anyone is still looking, use below configuration for Spring Boot 1.5.X
RedisConfig class for multiple channels:
#Configuration
public class RedisConfig {
#Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
#Qualifier("notificationListenerAdapter") MessageListenerAdapter notificationListenerAdapter,
#Qualifier("countListenerAdapter") MessageListenerAdapter countListenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(notificationListenerAdapter, new PatternTopic("notification"));
container.addMessageListener(countListenerAdapter, new PatternTopic("count"));
return container;
}
#Bean("notificationListenerAdapter")
MessageListenerAdapter notificationListenerAdapter(RedisReceiver redisReceiver) {
return new MessageListenerAdapter(redisReceiver, "receiveNotificationMessage");
}
#Bean("countListenerAdapter")
MessageListenerAdapter countListenerAdapter(RedisReceiver redisReceiver) {
return new MessageListenerAdapter(redisReceiver, "receiveCountMessage");
}
#Bean
RedisReceiver receiver() {
return new RedisReceiver();
}
#Bean
StringRedisTemplate template(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}
RedisReceiver to receive messages from channels.
Note: Make sure the method name matches the method names defined above.
public class RedisReceiver {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisReceiver.class);
public void receiveNotificationMessage(String message) {
LOGGER.info("Message Received from notification channel: <" + message + ">");
}
public void receiveCountMessage(String message) {
LOGGER.info("Message Received from count channel: <" + message + ">");
}
}
Test the flow:
public class TestMessages {
private static final Logger LOG = LoggerFactory.getLogger(TestMessages.class);
private final StringRedisTemplate redisTemplate;
public TestMessages(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void sendNotification(String message) {
redisTemplate.convertAndSend("notification", message);
}
public void sendCount(String message) {
redisTemplate.convertAndSend("count", message);
}
}

Categories

Resources