diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractServerConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractServerConnectionFactory.java index 05b8f35c5d..c0d1b6621a 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractServerConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractServerConnectionFactory.java @@ -18,6 +18,7 @@ package org.springframework.integration.ip.tcp.connection; import java.net.ServerSocket; import java.net.Socket; +import java.net.SocketAddress; import java.net.SocketException; import org.springframework.integration.context.OrderlyShutdownCapable; @@ -32,8 +33,8 @@ import org.springframework.util.Assert; * @author Gary Russell * @since 2.0 */ -public abstract class AbstractServerConnectionFactory - extends AbstractConnectionFactory implements SchedulingAwareRunnable, OrderlyShutdownCapable { +public abstract class AbstractServerConnectionFactory extends AbstractConnectionFactory + implements TcpServerConnectionFactory, SchedulingAwareRunnable, OrderlyShutdownCapable { private static final int DEFAULT_BACKLOG = 5; @@ -60,6 +61,11 @@ public abstract class AbstractServerConnectionFactory return true; } + @Override + public SocketAddress getServerSocketAddress() { + return null; + } + @Override public void start() { synchronized (this.lifecycleMonitor) { diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetServerConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetServerConnectionFactory.java index b35779acc6..b7f40fc853 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetServerConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetServerConnectionFactory.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; +import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketTimeoutException; @@ -54,6 +55,25 @@ public class TcpNetServerConnectionFactory extends AbstractServerConnectionFacto return "tcp-net-server-connection-factory"; } + @Override + public int getPort() { + int port = super.getPort(); + if (port == 0 && this.serverSocket != null) { + return this.serverSocket.getLocalPort(); + } + return port; + } + + @Override + public SocketAddress getServerSocketAddress() { + if (this.serverSocket != null) { + return this.serverSocket.getLocalSocketAddress(); + } + else { + return null; + } + } + /** * If no listener registers, exits. * Accepts incoming connections and creates TcpConnections for each new connection. @@ -70,11 +90,11 @@ public class TcpNetServerConnectionFactory extends AbstractServerConnectionFacto } try { if (getLocalAddress() == null) { - theServerSocket = createServerSocket(getPort(), getBacklog(), null); + theServerSocket = createServerSocket(super.getPort(), getBacklog(), null); } else { InetAddress whichNic = InetAddress.getByName(getLocalAddress()); - theServerSocket = createServerSocket(getPort(), getBacklog(), whichNic); + theServerSocket = createServerSocket(super.getPort(), getBacklog(), whichNic); } getTcpSocketSupport().postProcessServerSocket(theServerSocket); this.serverSocket = theServerSocket; diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioServerConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioServerConnectionFactory.java index a4069c96a1..2a8ef46898 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioServerConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioServerConnectionFactory.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; +import java.net.SocketAddress; import java.nio.channels.CancelledKeyException; import java.nio.channels.ClosedSelectorException; import java.nio.channels.SelectionKey; @@ -66,6 +67,34 @@ public class TcpNioServerConnectionFactory extends AbstractServerConnectionFacto return "tcp-nio-server-connection-factory"; } + @Override + public int getPort() { + int port = super.getPort(); + if (port == 0 && this.serverChannel != null) { + try { + SocketAddress address = this.serverChannel.getLocalAddress(); + if (address instanceof InetSocketAddress) { + port = ((InetSocketAddress) address).getPort(); + } + } + catch (IOException e) { + } + } + return port; + } + + @Override + public SocketAddress getServerSocketAddress() { + if (this.serverChannel != null) { + try { + return this.serverChannel.getLocalAddress(); + } + catch (IOException e) { + } + } + return null; + } + /** * If no listener registers, exits. * Accepts incoming connections and creates TcpConnections for each new connection. @@ -81,11 +110,8 @@ public class TcpNioServerConnectionFactory extends AbstractServerConnectionFacto } try { this.serverChannel = ServerSocketChannel.open(); - int port = getPort(); + int port = super.getPort(); getTcpSocketSupport().postProcessServerSocket(this.serverChannel.socket()); - if (logger.isInfoEnabled()) { - logger.info("Listening on port " + port); - } this.serverChannel.configureBlocking(false); if (getLocalAddress() == null) { this.serverChannel.socket().bind(new InetSocketAddress(port), Math.abs(getBacklog())); @@ -94,6 +120,9 @@ public class TcpNioServerConnectionFactory extends AbstractServerConnectionFacto InetAddress whichNic = InetAddress.getByName(getLocalAddress()); this.serverChannel.socket().bind(new InetSocketAddress(whichNic, port), Math.abs(getBacklog())); } + if (logger.isInfoEnabled()) { + logger.info("Listening on port " + getPort()); + } final Selector selector = Selector.open(); this.serverChannel.register(selector, SelectionKey.OP_ACCEPT); setListening(true); diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpServerConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpServerConnectionFactory.java new file mode 100644 index 0000000000..6432d29c60 --- /dev/null +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpServerConnectionFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.ip.tcp.connection; + +import java.net.SocketAddress; + +/** + * Connection factories that act as TCP servers, listening for incoming connections. + * @author Gary Russell + * @since 4.2 + * + */ +public interface TcpServerConnectionFactory { + + /** + * Return the port this server is listening on. + * If the factory is configured to listen on a random port (0), this + * will return the actual port after the factory is started. It may + * return the previous value if the factory is stopped. + * @return the port. + */ + int getPort(); + + /** + * Return the {@link SocketAddress} that the underlying {@code ServerSocket} + * is bound to. + * @return the socket address. + */ + SocketAddress getServerSocketAddress(); + +} diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/TcpOutboundGatewayTests.java b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/TcpOutboundGatewayTests.java index d522c8af86..1cd5a401d1 100644 --- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/TcpOutboundGatewayTests.java +++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/TcpOutboundGatewayTests.java @@ -52,7 +52,9 @@ import javax.net.ServerSocketFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.log4j.Level; import org.hamcrest.Matchers; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; @@ -69,6 +71,7 @@ import org.springframework.integration.ip.tcp.connection.TcpConnectionSupport; import org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory; import org.springframework.integration.ip.tcp.connection.TcpNioClientConnectionFactory; import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.test.rule.Log4jLevelAdjuster; import org.springframework.integration.test.util.SocketUtils; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; @@ -81,6 +84,10 @@ import org.springframework.messaging.support.GenericMessage; */ public class TcpOutboundGatewayTests { + @Rule + public Log4jLevelAdjuster adjuster = new Log4jLevelAdjuster(Level.TRACE, + "org.springframework.integration.ip.tcp"); + private final Log logger = LogFactory.getLog(this.getClass()); @Test diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/ConnectionFactoryTests.java b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/ConnectionFactoryTests.java index 169ed2f460..a2d7bc46c9 100644 --- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/ConnectionFactoryTests.java +++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/ConnectionFactoryTests.java @@ -24,6 +24,8 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -36,10 +38,9 @@ import org.mockito.stubbing.Answer; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.messaging.Message; import org.springframework.integration.ip.tcp.TcpReceivingChannelAdapter; import org.springframework.integration.ip.util.TestingUtilities; -import org.springframework.integration.test.util.SocketUtils; +import org.springframework.messaging.Message; /** * @author Gary Russell @@ -50,7 +51,18 @@ import org.springframework.integration.test.util.SocketUtils; public class ConnectionFactoryTests { @Test - public void testObtainConnectionIds() throws Exception { + public void testObtainConnectionIdsNet() throws Exception { + TcpNetServerConnectionFactory serverFactory = new TcpNetServerConnectionFactory(0); + testObtainConnectionIds(serverFactory); + } + + @Test + public void testObtainConnectionIdsNio() throws Exception { + TcpNioServerConnectionFactory serverFactory = new TcpNioServerConnectionFactory(0); + testObtainConnectionIds(serverFactory); + } + + public void testObtainConnectionIds(AbstractServerConnectionFactory serverFactory) throws Exception { final List events = Collections.synchronizedList(new ArrayList()); ApplicationEventPublisher publisher = new ApplicationEventPublisher() { @@ -62,17 +74,16 @@ public class ConnectionFactoryTests { @Override public void publishEvent(Object event) { - + } - + }; - int port = SocketUtils.findAvailableServerSocket(); - TcpNetServerConnectionFactory serverFactory = new TcpNetServerConnectionFactory(port); serverFactory.setBeanName("serverFactory"); serverFactory.setApplicationEventPublisher(publisher); serverFactory = spy(serverFactory); final CountDownLatch serverConnectionInitLatch = new CountDownLatch(1); doAnswer(new Answer() { + @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object result = invocation.callRealMethod(); serverConnectionInitLatch.countDown(); @@ -83,8 +94,10 @@ public class ConnectionFactoryTests { adapter.setConnectionFactory(serverFactory); adapter.start(); TestingUtilities.waitListening(serverFactory, null); + int port = serverFactory.getPort(); TcpNetClientConnectionFactory clientFactory = new TcpNetClientConnectionFactory("localhost", port); clientFactory.registerListener(new TcpListener() { + @Override public boolean onMessage(Message message) { return false; } @@ -105,11 +118,13 @@ public class ConnectionFactoryTests { Thread.sleep(1000); clients = clientFactory.getOpenConnectionIds(); assertEquals(0, clients.size()); - assertEquals(6, events.size()); // OPEN, CLOSE, EXCEPTION for each side + int expected = serverFactory instanceof TcpNetServerConnectionFactory ? 6// OPEN, CLOSE, EXCEPTION for each side + : 4; //OPEN, CLOSE + assertEquals(expected, events.size()); FooEvent event = new FooEvent(client, "foo"); client.publishEvent(event); - assertEquals(7, events.size()); + assertEquals(expected + 1, events.size()); try { event = new FooEvent(mock(TcpConnectionSupport.class), "foo"); @@ -119,6 +134,13 @@ public class ConnectionFactoryTests { catch (IllegalArgumentException e) { assertTrue("Can only publish events with this as the source".equals(e.getMessage())); } + + SocketAddress address = serverFactory.getServerSocketAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress inetAddress = (InetSocketAddress) address; + assertEquals(port, inetAddress.getPort()); + } + serverFactory.stop(); } @SuppressWarnings("serial") diff --git a/src/reference/asciidoc/ip.adoc b/src/reference/asciidoc/ip.adoc index fe1d4ee2f9..22e17c6ba4 100644 --- a/src/reference/asciidoc/ip.adoc +++ b/src/reference/asciidoc/ip.adoc @@ -166,6 +166,11 @@ A simple server connection factory that uses `java.net.Socket` connections. A simple server connection factory that uses `java.nio.channel.SocketChannel` connections. +NOTE: Starting with Spring Integration _version 4.2_, if the server is configured to listen on a random port (0), +the actual port chosen by the OS can be obtained using `getPort()`. +Also, `getServerSocketAddress()` is available to get the complete `SocketAddress`. +See the javadocs for the `TcpServerConnectionFactory` interface for more information. + [source,xml] ---- > for more information. +[[x4.2-tcp-server-port]] +===== TCP Server Port + +If a TCP server socket factory is configured to listen on a random port, the actual port chosen by the OS can now +be obtained using `getPort()`. +`getServerSocketAddress()` is also available. + +See <> for more information. + [[x4.2-tcp-gw-rto]] ===== TCP Gateway Remote Timeout