Remove SocketUtils usage

* Remove usage of non-stable `org.springframework.util.SocketUtils`
* Replace it with `0` for those tests where it is possible to select OS port
* Remove the mentioning of the `SocketUtils` from the `testing.adoc`
* Use `TransportConstants.DEFAULT_STOMP_PORT` for `StompServerIntegrationTests`.
We may disable this test in the future for CI if it is not going to be stable
* Introduce `Supplier<String> connectUrl` variants for `ZeroMqMessageHandler`
to let it defer connection evaluation until subscription to the socket `Mono`
in the `ZeroMqMessageHandler`.
* Move connection logic in the `ZeroMqMessageHandler` to `Lifecycle.start()`

Related to https://github.com/spring-projects/spring-framework/issues/28054

**Cherry-pick to `5.5.x`**

# Conflicts:
#	spring-integration-stomp/src/test/java/org/springframework/integration/stomp/client/StompServerIntegrationTests.java
This commit is contained in:
Artem Bilan
2022-02-15 16:11:12 -05:00
parent f8c22dd357
commit 9b27fbee89
12 changed files with 187 additions and 148 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -212,7 +212,7 @@ public class ZeroMqProxy implements InitializingBean, SmartLifecycle, BeanNameAw
/**
* Return the address an {@code inproc} control socket is bound or null if this proxy has not been started yet.
* @return the the address for control socket or null
* @return the address for control socket or null
*/
@Nullable
public String getControlAddress() {
@@ -222,7 +222,7 @@ public class ZeroMqProxy implements InitializingBean, SmartLifecycle, BeanNameAw
/**
* Return the address an {@code inproc} capture socket is bound or null if this proxy has not been started yet
* or {@link #captureAddress} is false.
* @return the the address for capture socket or null
* @return the address for capture socket or null
*/
@Nullable
public String getCaptureAddress() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.integration.zeromq.dsl;
import java.util.function.Supplier;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
@@ -53,6 +55,18 @@ public final class ZeroMq {
* @return the spec.
*/
public static ZeroMqMessageHandlerSpec outboundChannelAdapter(ZContext context, String connectUrl) {
return outboundChannelAdapter(context, () -> connectUrl);
}
/**
* Create an instance of {@link ZeroMqMessageHandlerSpec} for the provided {@link ZContext}
* and connection URL supplier.
* @param context the {@link ZContext} to use.
* @param connectUrl the supplier for URL to connect a ZeroMq socket to.
* @return the spec.
* @since 5.5.9
*/
public static ZeroMqMessageHandlerSpec outboundChannelAdapter(ZContext context, Supplier<String> connectUrl) {
return new ZeroMqMessageHandlerSpec(context, connectUrl);
}
@@ -70,6 +84,21 @@ public final class ZeroMq {
return new ZeroMqMessageHandlerSpec(context, connectUrl, socketType);
}
/**
* Create an instance of {@link ZeroMqMessageHandlerSpec} for the provided {@link ZContext},
* connection URL supplier and {@link SocketType}.
* @param context the {@link ZContext} to use.
* @param connectUrl the supplier for URL to connect a ZeroMq socket to.
* @param socketType the {@link SocketType} for ZeroMq socket.
* @return the spec.
* @since 5.5.9
*/
public static ZeroMqMessageHandlerSpec outboundChannelAdapter(ZContext context, Supplier<String> connectUrl,
SocketType socketType) {
return new ZeroMqMessageHandlerSpec(context, connectUrl, socketType);
}
/**
* Create an instance of {@link ZeroMqMessageProducerSpec} for the provided {@link ZContext}.
* @param context the {@link ZContext} to use.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.integration.zeromq.dsl;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
@@ -47,9 +48,20 @@ public class ZeroMqMessageHandlerSpec
* @param connectUrl the URL to connect the socket to.
*/
protected ZeroMqMessageHandlerSpec(ZContext context, String connectUrl) {
this(context, () -> connectUrl);
}
/**
* Create an instance based on the provided {@link ZContext} and connection string supplier.
* @param context the {@link ZContext} to use for creating sockets.
* @param connectUrl the supplier for URL to connect the socket to.
* @since 5.5.9
*/
protected ZeroMqMessageHandlerSpec(ZContext context, Supplier<String> connectUrl) {
super(new ZeroMqMessageHandler(context, connectUrl));
}
/**
* Create an instance based on the provided {@link ZContext}, connection string and {@link SocketType}.
* @param context the {@link ZContext} to use for creating sockets.
@@ -58,6 +70,17 @@ public class ZeroMqMessageHandlerSpec
* only {@link SocketType#PAIR}, {@link SocketType#PUB} and {@link SocketType#PUSH} are supported.
*/
protected ZeroMqMessageHandlerSpec(ZContext context, String connectUrl, SocketType socketType) {
this(context, () -> connectUrl, socketType);
}
/**
* Create an instance based on the provided {@link ZContext}, connection string supplier and {@link SocketType}.
* @param context the {@link ZContext} to use for creating sockets.
* @param connectUrl the supplier for URL to connect the socket to.
* @param socketType the {@link SocketType} to use;
* only {@link SocketType#PAIR}, {@link SocketType#PUB} and {@link SocketType#PUSH} are supported.
*/
protected ZeroMqMessageHandlerSpec(ZContext context, Supplier<String> connectUrl, SocketType socketType) {
super(new ZeroMqMessageHandler(context, connectUrl, socketType));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,9 @@ package org.springframework.integration.zeromq.outbound;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
@@ -36,10 +38,12 @@ import org.springframework.integration.handler.AbstractReactiveMessageHandler;
import org.springframework.integration.mapping.ConvertingBytesMessageMapper;
import org.springframework.integration.mapping.OutboundMessageMapper;
import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter;
import org.springframework.integration.support.management.ManageableLifecycle;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.util.Assert;
import reactor.core.Disposable;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
@@ -61,11 +65,14 @@ import zmq.socket.pubsub.Pub;
*
* @since 5.4
*/
public class ZeroMqMessageHandler extends AbstractReactiveMessageHandler {
public class ZeroMqMessageHandler extends AbstractReactiveMessageHandler
implements ManageableLifecycle {
private static final List<SocketType> VALID_SOCKET_TYPES =
Arrays.asList(SocketType.PAIR, SocketType.PUSH, SocketType.PUB);
private final AtomicBoolean running = new AtomicBoolean();
private final Scheduler publisherScheduler = Schedulers.newSingle("zeroMqMessageHandlerScheduler");
private final Mono<ZMQ.Socket> socketMono;
@@ -80,6 +87,8 @@ public class ZeroMqMessageHandler extends AbstractReactiveMessageHandler {
private volatile boolean initialized;
private volatile Disposable socketMonoSubscriber;
/**
* Create an instance based on the provided {@link ZContext} and connection string.
* @param context the {@link ZContext} to use for creating sockets.
@@ -89,6 +98,16 @@ public class ZeroMqMessageHandler extends AbstractReactiveMessageHandler {
this(context, connectUrl, SocketType.PAIR);
}
/**
* Create an instance based on the provided {@link ZContext} and connection string supplier.
* @param context the {@link ZContext} to use for creating sockets.
* @param connectUrl the supplier for URL to connect the socket to.
* @since 5.5.9
*/
public ZeroMqMessageHandler(ZContext context, Supplier<String> connectUrl) {
this(context, connectUrl, SocketType.PAIR);
}
/**
* Create an instance based on the provided {@link ZContext}, connection string and {@link SocketType}.
* @param context the {@link ZContext} to use for creating sockets.
@@ -97,15 +116,29 @@ public class ZeroMqMessageHandler extends AbstractReactiveMessageHandler {
* only {@link SocketType#PAIR}, {@link SocketType#PUB} and {@link SocketType#PUSH} are supported.
*/
public ZeroMqMessageHandler(ZContext context, String connectUrl, SocketType socketType) {
Assert.notNull(context, "'context' must not be null");
this(context, () -> connectUrl, socketType);
Assert.hasText(connectUrl, "'connectUrl' must not be empty");
}
/**
* Create an instance based on the provided {@link ZContext}, connection string supplier and {@link SocketType}.
* @param context the {@link ZContext} to use for creating sockets.
* @param connectUrl the supplier for URL to connect the socket to.
* @param socketType the {@link SocketType} to use;
* only {@link SocketType#PAIR}, {@link SocketType#PUB} and {@link SocketType#PUSH} are supported.
* @since 5.5.9
*/
public ZeroMqMessageHandler(ZContext context, Supplier<String> connectUrl, SocketType socketType) {
Assert.notNull(context, "'context' must not be null");
Assert.notNull(connectUrl, "'connectUrl' must not be null");
Assert.state(VALID_SOCKET_TYPES.contains(socketType),
() -> "'socketType' can only be one of the: " + VALID_SOCKET_TYPES);
this.socketMono =
Mono.just(context.createSocket(socketType))
.publishOn(this.publisherScheduler)
.doOnNext((socket) -> this.socketConfigurer.accept(socket))
.doOnNext((socket) -> socket.connect(connectUrl))
.doOnNext((socket) -> socket.connect(connectUrl.get()))
.cache()
.publishOn(this.publisherScheduler);
}
@@ -176,10 +209,28 @@ public class ZeroMqMessageHandler extends AbstractReactiveMessageHandler {
messageConverter.afterPropertiesSet();
this.messageMapper = new ConvertingBytesMessageMapper(messageConverter);
}
this.socketMono.subscribe();
this.initialized = true;
}
@Override
public void start() {
if (!this.running.getAndSet(true)) {
this.socketMonoSubscriber = this.socketMono.subscribe();
}
}
@Override
public void stop() {
if (this.running.getAndSet(false)) {
this.socketMonoSubscriber.dispose();
}
}
@Override
public boolean isRunning() {
return this.running.get();
}
@Override
protected Mono<Void> handleMessageInternal(Message<?> message) {
Assert.state(this.initialized, "the message handler is not initialized yet or already destroyed");
@@ -209,6 +260,7 @@ public class ZeroMqMessageHandler extends AbstractReactiveMessageHandler {
this.initialized = false;
super.destroy();
this.socketMono.doOnNext(ZMQ.Socket::close).block();
this.socketMonoSubscriber.dispose();
this.publisherScheduler.dispose();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,7 +44,6 @@ import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.util.SocketUtils;
/**
* @author Artem Bilan
@@ -55,8 +54,6 @@ import org.springframework.util.SocketUtils;
@DirtiesContext
public class ZeroMqDslTests {
private static final int PROXY_PUB_PORT = SocketUtils.findAvailableTcpPort();
@Autowired
ZContext context;
@@ -82,10 +79,10 @@ public class ZeroMqDslTests {
for (int i = 0; i < 2; i++) {
IntegrationFlow consumerFlow =
IntegrationFlows.from(
ZeroMq.inboundChannelAdapter(this.context, SocketType.SUB)
.connectUrl("tcp://localhost:" + this.subPubZeroMqProxy.getBackendPort())
.topics("someTopic")
.consumeDelay(Duration.ofMillis(100)))
ZeroMq.inboundChannelAdapter(this.context, SocketType.SUB)
.connectUrl("tcp://localhost:" + this.subPubZeroMqProxy.getBackendPort())
.topics("someTopic")
.consumeDelay(Duration.ofMillis(100)))
.channel(ZeroMq.zeroMqChannel(this.context).zeroMqProxy(this.pullPushZeroMqProxy))
.transform(Transformers.objectToString())
.handle(results::offer)
@@ -130,9 +127,7 @@ public class ZeroMqDslTests {
@Bean
ZeroMqProxy subPubZeroMqProxy() {
ZeroMqProxy zeroMqProxy = new ZeroMqProxy(context(), ZeroMqProxy.Type.SUB_PUB);
zeroMqProxy.setFrontendPort(PROXY_PUB_PORT);
return zeroMqProxy;
return new ZeroMqProxy(context(), ZeroMqProxy.Type.SUB_PUB);
}
@Bean
@@ -141,10 +136,14 @@ public class ZeroMqDslTests {
}
@Bean
IntegrationFlow publishToZeroMqPubSubFlow() {
IntegrationFlow publishToZeroMqPubSubFlow(ZeroMqProxy subPubZeroMqProxy) {
return flow ->
flow.handle(ZeroMq.outboundChannelAdapter(context(), "tcp://localhost:" + PROXY_PUB_PORT,
SocketType.PUB)
flow.handle(ZeroMq.outboundChannelAdapter(context(),
() -> {
await().until(() -> subPubZeroMqProxy.getFrontendPort() > 0);
return "tcp://localhost:" + subPubZeroMqProxy.getFrontendPort();
},
SocketType.PUB)
.topic("someTopic"));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -71,6 +71,7 @@ public class ZeroMqMessageHandlerTests {
Mono<ZMQ.Socket> socketMono = TestUtils.getPropertyValue(messageHandler, "socketMono", Mono.class);
ZMQ.Socket socketInUse = socketMono.block(Duration.ofSeconds(10));
assertThat(socketInUse.getZapDomain()).isEqualTo("global");
messageHandler.start();
Message<?> testMessage = new GenericMessage<>("test");
messageHandler.handleMessage(testMessage).subscribe();
@@ -99,6 +100,7 @@ public class ZeroMqMessageHandlerTests {
new FunctionExpression<Message<?>>((message) -> message.getHeaders().get("topic")));
messageHandler.setMessageMapper(new EmbeddedJsonHeadersMessageMapper());
messageHandler.afterPropertiesSet();
messageHandler.start();
Message<?> testMessage = MessageBuilder.withPayload("test").setHeader("topic", "testTopic").build();
@@ -137,6 +139,7 @@ public class ZeroMqMessageHandlerTests {
messageHandler.setBeanFactory(mock(BeanFactory.class));
messageHandler.setMessageConverter(new ByteArrayMessageConverter());
messageHandler.afterPropertiesSet();
messageHandler.start();
Message<?> testMessage = new GenericMessage<>("test".getBytes());
messageHandler.handleMessage(testMessage).subscribe();