spring integration tcp client send simple message - java

I've been developing a project using spring framework 4. I'm trying to create a simple TCP client via spring-integration-ip library. I've adjusted all configurations:
applicationContext.xml
...
<int:channel id="tcpChannel" />
<int-ip:tcp-outbound-channel-adapter id="outboundClient"
channel="tcpChannel"
connection-factory="tcpConnectionFactory"/>
...
bean configuration:
#Configuration
public class MyConfiguration{
#Bean
public AbstractClientConnectionFactory tcpConnectionFactory() {
return new TcpNetClientConnectionFactory("localhost", 2345);
}
}
I've read all documentations about spring tcp here.
I guess I must use tcp-outbound-channel-adapter or gateway to send messages. but I wonder how to use it; what method should I invoke. I'm not supposed to receive any messages from server.

I found the solution. I didn't need gateway. spring messaging gateways have been designed to implement the request-response scenario. So the only thing I need to do is that I send message vi channel. Perhaps there be some better solutions.
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;
public class MyOwnService{
#Inject
private MessageChannel channel;
public void someMethod(String message){
Message<String> m = MessageBuilder.withPayload(message).build();
channel.send(m);
}
}

Related

How to #Schedule a task according to a WebSocket Event in Spring Boot

I would like to create an application in Java to automate trades in my Binance account. Thankfully, joaopsilva has made it easy through an open source API which fetches candlesticks through REST Client or WebSocket. I would like to use WebSocket since it is lighter.
I searched in several sources and still I could not find an example project which uses the Spring Boot framework to build an event-driven application which interacts with a connected WebSocket.
If my line of reason is correct, I should define a bean for Spring to instantiate the WebSocket client:
#Configuration
public class WebSocketConfig {
#Bean
public BinanceApiWebSocketClient binanceApiWebSocketClient() {
return BinanceApiClientFactory.newInstance().newWebSocketClient();
}
}
To interact with the socket event, I created a #Scheduled response, in which I used an arbitrary rate of 1 per second just for testing. I did it like this:
#Configuration
#EnableScheduling
public class SocketListener {
#Autowired BinanceApiWebSocketClient client;
#Scheduled(fixedRate = 1000)
public void scheduleFixedDelayTask() {
client.onCandlestickEvent("ethbtc", CandlestickInterval.ONE_MINUTE, response ->
System.out.println(response));
}
}
It works. If I launch the Spring application, it successfuly configures the client Bean and it prints the candlestick events. However, every 1 second, what I receive is an enormous chunk of events. It looks like this:
So, I'm wondering. Am I doing this correctly? Would there be a way to Schedule the listener not to receive chunks of events, but to listen the socket exactly when an event happens (not setting delay = 1, which of course causes unnecessary performance issues).
If your question is about the correct place to start the event streaming through the websocket client defined as a bean, one of the options is an ApplicationRunner bean whose run() method will be executed once on application start:
#SpringBootApplication
public class BinanceClientApplication {
public static void main(String[] args) {
SpringApplication.run(BinanceClientApplication.class, args);
}
#Bean
public ApplicationRunner applicationRunner(BinanceApiWebSocketClient binanceApiWebSocketClient) {
return args -> {
binanceApiWebSocketClient.onCandlestickEvent("ethbtc",
CandlestickInterval.ONE_MINUTE,
response ->
System.out.println(response));
};
}
}
#Configuration
public class WebSocketConfig {
#Bean
public BinanceApiWebSocketClient binanceApiWebSocketClient() {
return BinanceApiClientFactory.newInstance().newWebSocketClient();
}
}
With the scheduled task you have a new event stream is started with every task execution and you end up having multiple streams of the same events.

Junit 5 functional testing the Micronaut Messaging-Driven Application

I have a Rabbit MQ Micronaut Messaging-Driven application. The application only contains the Consumer side, Producer side is on another REST API application.
Now I want to perform JUnit 5 testing with the consumer side only. Trying to get the best idea to test the Messaging-Driven application that contains only the Rabbit MQ Listener
#RabbitListener
public record CategoryListener(IRepository repository) {
#Queue(ConstantValues.ADD_CATEGORY)
public CategoryViewModel Create(CategoryViewModel model) {
LOG.info(String.format("Listener --> Adding the product to the product collection"));
Category category = new Category(model.name(), model.description());
return Single.fromPublisher(this.repository.getCollection(ConstantValues.PRODUCT_CATEGORY_COLLECTION_NAME, Category.class)
.insertOne(category)).map(success->{
return new CategoryViewModel(
success.getInsertedId().asObjectId().getValue().toString(),
category.getName(),
category.getDescription());
}).blockingGet();
}
}
After some research, I found that we can use Testcontainers for integration testing, In my case, the Producer and receiver are on a different server. So do I need to create RabbitClient for each RabbitListener in the test environment or is there any way to mock RabbitClient
#MicronautTest
#Testcontainers
public class CategoryListenerTest {
#Container
private static final RabbitMQContainer RABBIT_MQ_CONTAINER = new RabbitMQContainer("rabbitmq")
.withExposedPorts(5672, 15672);
#Test
#DisplayName("Rabbit MQ container should be running")
void rabbitMqContainerShouldBeRunning() {
Assertions.assertTrue(RABBIT_MQ_CONTAINER.isRunning());
}
}
What is the best way to perform functional tests of Micronaut Messaging-Driven Application? In this question, I have a PRODUCER on another application. So I can't inject a PRODUCER client. How can I test this function on the LISTENER side?
Create producers with #RabbitClient or use the java api directly

How can I get a server port at runtime in Spring Webflux?

I have a Spring Webflux application with Spring Boot ver 2.3.5
How can I get a server port of running Netty container at runtime? (Not in tests)
Note: nor #Value("${server.port}") neither #Value("${local.server.port}") do not work if the port is not specified in the configuration.
After I looking into the spring boot library, I found that there is a ReactiveWebServerApplicationContext. It can give you the port.
#Autowired
private ReactiveWebServerApplicationContext server;
#GetMapping
public Flux<YourObject> getSomething() {
var port = server.getWebServer().getPort());
...
}
One way I found to do is from org.springframework.boot.web.embedded.netty.NettyWebServer#start and looks like a listener to a certain event:
#Component
#Slf4j
public class ServerStartListener implements ApplicationListener<WebServerInitializedEvent> {
#Override
public void onApplicationEvent(WebServerInitializedEvent event) {
// This is to exclude management port
if (!"management".equals(event.getApplicationContext().getServerNamespace())) {
log.info("Application started on port {}", event.getWebServer().getPort());
}
}
}
However, I find this not very elegant and wonder if there are better ways.

Spring boot migration of apache activemq to artemis

I am using apache activemq with spring boot and I want to migrate to apache artemis to improve usage for cluster and nodes.
At the moment I am using mainly the concept of VirtualTopics and with JMS like
#JMSListener(destination = "Consumer.A.VirtualTopic.simple")
public void receiveMessage() {
...
}
...
public void send(JMSTemplate template) {
template.convertAndSend("VirtualTopic.simple", "Hello world!");
}
I have read, that artemis changed it's address model to addresses, queues and routing types instead of queues, topics and virtual topics like in activemq.
I have read a lot more, but I think I don't get it right, how I can migrate now. I tried it the same way like above, so I imported Artemis JMSClient from Maven and wanted to use it like before, but with FQQN (Fully Qualified Queue Name) or the VirtualTopic-Wildcard you can read on some sources. But somehow it does not work properly.
My Questions are:
- How can I migrate VirtualTopics? Did I get it right with FQQN and those VirtualTopics-Wildcards?
- How can I specify the routingtypes anycast and multicast for the code examples above? (In the online examples addresses and queues are hardcoded in the server broker.xml, but I want to create it on the fly of the application.)
- How can I use it with openwire protocol and how does the application know what it uses? Does it only depend on the port I am using of artemis? So 61616 for openwire?
Can anyone help in clarifying my thoughts?
UPDATE:
Some further questions.
1) I always read something like "a default 5.x consumer". Is it expected then to get mixed with artemis? Like you leave all of those naming conventions and just add the addresses to the VirtualTopic name to a FQQN, and just change dependecies to artemis?
2) I've already tried the "virtualTopicConsumerWildcards" with "import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;" and "import org.apache.activemq.ActiveMQConnectionFactory;", but only in the second case it made a difference.
3) I also tried to only use OpenWire as protocol in the acceptor, but in this case (and with "import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;") I get following error when starting my application: "2020-03-30 11:41:19,504 ERROR [org.apache.activemq.artemis.core.server] AMQ224096: Error setting up connection from /127.0.0.1:54201 to /127.0.0.1:61616; protocol CORE not found in map: [OPENWIRE]".
4) Do I put i.e. multicast:://VirtualTopic.simple this as destination name in template.convertAndSend(...)?
I tried template.setPubSubDomain(true) for multicast routing type and left it for anycast, this works. But is it a good way?
5) Do you maybe know, how I can "tell" my spring-boot-application with template.convertAndSend(...); to use Openwire?
UPDATE2:
Shared durable subscriptions
#JmsListener(destination = "VirtualTopic.test", id = "c1", subscription = "Consumer.A.VirtualTopic.test", containerFactory = "queueConnectionFactory")
public void receive1(String m) {
}
#JmsListener(destination = "VirtualTopic.test", id = "c2", subscription = "Consumer.B.VirtualTopic.test", containerFactory = "queueConnectionFactory")
public void receive2(String m) {
}
#Bean
public DefaultJmsListenerContainerFactory queueConnectionFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setClientId("brokerClientId");
factory.setSubscriptionDurable(true);
factory.setSubscriptionShared(true);
return factory;
}
Errors:
2020-04-17 11:23:44.485 WARN 7900 --- [enerContainer-3] o.s.j.l.DefaultMessageListenerContainer : Setup of JMS message listener invoker failed for destination 'VirtualTopic.test' - trying to recover. Cause: org.apache.activemq.ActiveMQSession.createSharedDurableConsumer(Ljavax/jms/Topic;Ljava/lang/String;Ljava/lang/String;)Ljavax/jms/MessageConsumer;
2020-04-17 11:23:44.514 ERROR 7900 --- [enerContainer-3] o.s.j.l.DefaultMessageListenerContainer : Could not refresh JMS Connection for destination 'VirtualTopic.test' - retrying using FixedBackOff{interval=5000, currentAttempts=0, maxAttempts=unlimited}. Cause: Broker: d1 - Client: brokerClientId already connected from /127.0.0.1:59979
What am I doing wrong here?
The idea behind virtual topics is that producers send to a topic in the usual JMS way and s consumer can consume from a physical queue for a logical topic subscription, allowing many consumers to be running on many machines & threads to load balance the load.
Artemis uses a queue per topic subscriber model internally and it is possibly to directly address the subscription queue using its Fully Qualified Queue name (FQQN).
For example, a default 5.x consumer destination for topic VirtualTopic.simple subscription A Consumer.A.VirtualTopic.simple would be replaced with an Artemis FQQN comprised of the address and queue VirtualTopic.simple::Consumer.A.VirtualTopic.simple.
However Artemis supports a virtual topic wildcard filter mechanism that will automatically convert the consumer destination into the corresponding FQQN. To enable filter mechanism the configuration string property
virtualTopicConsumerWildcards could be used. It has has two parts separated by a ;, ie the default 5.x virtual topic with consumer prefix of Consumer.*., would require a virtualTopicConsumerWildcards filter of Consumer.*.>;2.
Artemis is configured by default to auto-create destinations requested by clients. They can specify a special prefix when connecting to an address to indicate which kind of routing type to use. They can be enabled by adding the configuration string property anycastPrefix and multicastPrefix to an acceptor, you can find more details at Using Prefixes to Determine Routing Type. For example adding to the acceptor anycastPrefix=anycast://;multicastPrefix=multicast://, if the client needs to send a message to only one of the ANYCAST queues should use the destination anycast:://VirtualTopic.simple, if the client needs to send a message to the MULTICAST should use the destination multicast:://VirtualTopic.simple.
Artemis acceptors support using a single port for all protocols, they will automatically detect which protocol is being used CORE, AMQP, STOMP or OPENWIRE, but it is possible to limit which protocols are supported by using the protocols parameter.
The following acceptor enables the anycast prefix anycast://, the multicast prefix multicast:// and the virtual topic consumer wildcards, disabling all protocols except OPENWIRE on the endpoint localhost:61616.
<acceptor name="artemis">tcp://localhost:61616?anycastPrefix=anycast://;multicastPrefix=multicast://;virtualTopicConsumerWildcards=Consumer.*.%3E%3B2;protocols=OPENWIRE</acceptor>
UPDATE:
The following example application connects to an Artemis instance with the previous acceptor using the OpenWire protocol.
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.core.JmsTemplate;
#SpringBootApplication
#EnableJms
public class Application {
private final String BROKER_URL = "tcp://localhost:61616";
private final String BROKER_USERNAME = "admin";
private final String BROKER_PASSWORD = "admin";
public static void main(String[] args) throws Exception {
final ConfigurableApplicationContext context = SpringApplication.run(Application.class);
System.out.println("********************* Sending message...");
JmsTemplate jmsTemplate = context.getBean("jmsTemplate", JmsTemplate.class);
JmsTemplate jmsTemplateAnycast = context.getBean("jmsTemplateAnycast", JmsTemplate.class);
JmsTemplate jmsTemplateMulticast = context.getBean("jmsTemplateMulticast", JmsTemplate.class);
jmsTemplateAnycast.convertAndSend("VirtualTopic.simple", "Hello world anycast!");
jmsTemplate.convertAndSend("anycast://VirtualTopic.simple", "Hello world anycast using prefix!");
jmsTemplateMulticast.convertAndSend("VirtualTopic.simple", "Hello world multicast!");
jmsTemplate.convertAndSend("multicast://VirtualTopic.simple", "Hello world multicast using prefix!");
System.out.print("Press any key to close the context");
System.in.read();
context.close();
}
#Bean
public ActiveMQConnectionFactory connectionFactory(){
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(BROKER_URL);
connectionFactory.setUserName(BROKER_USERNAME);
connectionFactory.setPassword(BROKER_PASSWORD);
return connectionFactory;
}
#Bean
public JmsTemplate jmsTemplate(){
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(connectionFactory());
return template;
}
#Bean
public JmsTemplate jmsTemplateAnycast(){
JmsTemplate template = new JmsTemplate();
template.setPubSubDomain(false);
template.setConnectionFactory(connectionFactory());
return template;
}
#Bean
public JmsTemplate jmsTemplateMulticast(){
JmsTemplate template = new JmsTemplate();
template.setPubSubDomain(true);
template.setConnectionFactory(connectionFactory());
return template;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setConcurrency("1-1");
return factory;
}
#JmsListener(destination = "Consumer.A.VirtualTopic.simple")
public void receiveMessageFromA(String message) {
System.out.println("*********************** MESSAGE RECEIVED FROM A: " + message);
}
#JmsListener(destination = "Consumer.B.VirtualTopic.simple")
public void receiveMessageFromB(String message) {
System.out.println("*********************** MESSAGE RECEIVED FROM B: " + message);
}
#JmsListener(destination = "VirtualTopic.simple")
public void receiveMessageFromTopic(String message) {
System.out.println("*********************** MESSAGE RECEIVED FROM TOPIC: " + message);
}
}

Spring integration ip - udp channel with java code only

I've been looking at the Spring integration ip module, I wanted to create UDP channel for receiving, but I found I can only do it with XML.
I was thinking that I could make something out if I looked inside the implementation code, but it creates bean definition itself, from parameters supplied in xml.
I can't use xml definitions in my code, is there a way to make it work with spring without xml?
alternatively, is there any better way in java to work with udp?
Starting with version 5.0 there is Java DSL on the matter already, so the code for UDP Channel Adapters may look like:
#Bean
public IntegrationFlow inUdpAdapter() {
return IntegrationFlows.from(Udp.inboundAdapter(0))
.channel(udpIn())
.get();
}
#Bean
public QueueChannel udpIn() {
return new QueueChannel();
}
#Bean
public IntegrationFlow outUdpAdapter() {
return f -> f.handle(Udp.outboundAdapter(m -> m.getHeaders().get("udp_dest")));
}
But with existing Spring Integration version you can simply configure UnicastReceivingChannelAdapter bean:
#Bean
public UnicastReceivingChannelAdapter udpInboundAdapter() {
UnicastReceivingChannelAdapter unicastReceivingChannelAdapter = new UnicastReceivingChannelAdapter(1111);
unicastReceivingChannelAdapter.setOutputChannel(udpChannel());
return unicastReceivingChannelAdapter;
}
In the Reference Manual you can find the Tips and Tricks chapter for some info how to write Spring Integration application with raw Java and annotation configuration.
I added JIRA to address Java sample in the Reference Manual.

Categories