From f3b31fc467012e5f49d2a7d0036956f4ce9fbacf Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Wed, 11 Dec 2013 09:28:07 +0100 Subject: [PATCH] DATAMONGO-808 - Improve ServerAddressPropertyEditor to support IPv6 addresses. Improved parsing of ServerAddress to be able to handle IPv6 addresses correctly. We now use the actor ServerAddress(InetAddress) to be able to pass an IPv6 address. The constructor which takes a String as the hostname can't deal with IPv6 addresses directly because it tries to extract a port at the wrong location of such an address. This change should not change the behavior too much, since the constructor ServerAddress(String, int) already calls InetAddress.getByName(...) internally. Original pull request: #103. --- .../config/ServerAddressPropertyEditor.java | 52 +++++++-- .../config/MongoNamespaceReplicaSetTests.java | 9 +- .../ServerAddressPropertyEditorUnitTests.java | 107 +++++++++++++++++- 3 files changed, 157 insertions(+), 11 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java index 8e3dddbda..32383ba49 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java @@ -16,12 +16,14 @@ package org.springframework.data.mongodb.config; import java.beans.PropertyEditorSupport; +import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashSet; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import com.mongodb.ServerAddress; @@ -35,6 +37,11 @@ import com.mongodb.ServerAddress; */ public class ServerAddressPropertyEditor extends PropertyEditorSupport { + /** + * A port is a number without a leading 0 at the end of the address that is proceeded by just a single :. + */ + private static final String HOST_PORT_SPLIT_PATTERN = "(? 2) { - LOG.warn("Could not parse address source '{}'. Check your replica set configuration!", source); + if (hostAndPort.length > 2) { + LOG.warn(COULD_NOT_PARSE_ADDRESS_MESSAGE, "source", source); return null; } try { - return hostAndPort.length == 1 ? new ServerAddress(hostAndPort[0]) : new ServerAddress(hostAndPort[0], - Integer.parseInt(hostAndPort[1])); + InetAddress hostAddress = InetAddress.getByName(hostAndPort[0]); + Integer port = hostAndPort.length == 1 ? null : Integer.parseInt(hostAndPort[1]); + + return port == null ? new ServerAddress(hostAddress) : new ServerAddress(hostAddress, port); } catch (UnknownHostException e) { - LOG.warn("Could not parse host '{}'. Check your replica set configuration!", hostAndPort[0]); + LOG.warn(COULD_NOT_PARSE_ADDRESS_MESSAGE, "host", hostAndPort[0]); } catch (NumberFormatException e) { - LOG.warn("Could not parse port '{}'. Check your replica set configuration!", hostAndPort[1]); + LOG.warn(COULD_NOT_PARSE_ADDRESS_MESSAGE, "port", hostAndPort[1]); } return null; } + + /** + * Extract the host and port from the given {@link String}. + * + * @param addressAndPortSource must not be {@literal null}. + * @return + */ + private String[] extractHostAddressAndPort(String addressAndPortSource) { + + Assert.notNull(addressAndPortSource, "Address and port source must not be null!"); + + String[] hostAndPort = addressAndPortSource.split(HOST_PORT_SPLIT_PATTERN); + String hostAddress = hostAndPort[0]; + + if (isHostAddressInIPv6BracketNotation(hostAddress)) { + hostAndPort[0] = hostAddress.substring(1, hostAddress.length() - 1); + } + + return hostAndPort; + } + + private boolean isHostAddressInIPv6BracketNotation(String hostAddress) { + return hostAddress.startsWith("[") && hostAddress.endsWith("]"); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests.java index 975812f77..f3e15aec4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests.java @@ -19,6 +19,7 @@ package org.springframework.data.mongodb.config; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import java.net.InetAddress; import java.util.List; import org.junit.Ignore; @@ -40,8 +41,7 @@ import com.mongodb.ServerAddress; @ContextConfiguration public class MongoNamespaceReplicaSetTests { - @Autowired - private ApplicationContext ctx; + @Autowired private ApplicationContext ctx; @Test @SuppressWarnings("unchecked") @@ -53,7 +53,10 @@ public class MongoNamespaceReplicaSetTests { List replicaSetSeeds = (List) ReflectionTestUtils.getField(mfb, "replicaSetSeeds"); assertThat(replicaSetSeeds, is(notNullValue())); - assertThat(replicaSetSeeds, hasItems(new ServerAddress("127.0.0.1", 10001), new ServerAddress("localhost", 10002))); + assertThat( + replicaSetSeeds, + hasItems(new ServerAddress(InetAddress.getByName("127.0.0.1"), 10001), + new ServerAddress(InetAddress.getByName("localhost"), 10002))); } @Test diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java index baf688f86..2d7607ed6 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java @@ -18,12 +18,15 @@ package org.springframework.data.mongodb.config; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collection; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import com.mongodb.ServerAddress; @@ -35,6 +38,8 @@ import com.mongodb.ServerAddress; */ public class ServerAddressPropertyEditorUnitTests { + @Rule public ExpectedException expectedException = ExpectedException.none(); + ServerAddressPropertyEditor editor; @Before @@ -81,11 +86,111 @@ public class ServerAddressPropertyEditorUnitTests { assertNull(editor.getValue()); } + /** + * @see DATAMONGO-808 + */ + @Test + public void handleIPv6HostaddressLoopbackShort() throws UnknownHostException { + + String hostAddress = "::1"; + editor.setAsText(hostAddress); + + assertSingleAddressWithPort(hostAddress, null, editor.getValue()); + } + + /** + * @see DATAMONGO-808 + */ + @Test + public void handleIPv6HostaddressLoopbackShortWithPort() throws UnknownHostException { + + String hostAddress = "::1"; + int port = 27017; + editor.setAsText(hostAddress + ":" + port); + + assertSingleAddressWithPort(hostAddress, port, editor.getValue()); + } + + /** + * Here we detect no port since the last segment of the address contains leading zeros. + * + * @see DATAMONGO-808 + */ + @Test + public void handleIPv6HostaddressLoopbackLong() throws UnknownHostException { + + String hostAddress = "0000:0000:0000:0000:0000:0000:0000:0001"; + editor.setAsText(hostAddress); + + assertSingleAddressWithPort(hostAddress, null, editor.getValue()); + } + + /** + * @see DATAMONGO-808 + */ + @Test + public void handleIPv6HostaddressLoopbackLongWithBrackets() throws UnknownHostException { + + String hostAddress = "[0000:0000:0000:0000:0000:0000:0000:0001]"; + editor.setAsText(hostAddress); + + assertSingleAddressWithPort(hostAddress, null, editor.getValue()); + } + + /** + * We can't tell whether the last part of the hostAddress represents a port or not. + * + * @see DATAMONGO-808 + */ + @Test + public void shouldFailToHandleAmbiguousIPv6HostaddressLongWithoutPortAndWithoutBrackets() throws UnknownHostException { + + expectedException.expect(IllegalArgumentException.class); + + String hostAddress = "0000:0000:0000:0000:0000:0000:0000:128"; + editor.setAsText(hostAddress); + } + + /** + * @see DATAMONGO-808 + */ + @Test + public void handleIPv6HostaddressExampleAddressWithPort() throws UnknownHostException { + + String hostAddress = "0000:0000:0000:0000:0000:0000:0000:0001"; + int port = 27017; + editor.setAsText(hostAddress + ":" + port); + + assertSingleAddressWithPort(hostAddress, port, editor.getValue()); + } + + /** + * @see DATAMONGO-808 + */ + @Test + public void handleIPv6HostaddressExampleAddressInBracketsWithPort() throws UnknownHostException { + + String hostAddress = "[0000:0000:0000:0000:0000:0000:0000:0001]"; + int port = 27017; + editor.setAsText(hostAddress + ":" + port); + + assertSingleAddressWithPort(hostAddress, port, editor.getValue()); + } + private static void assertSingleAddressOfLocalhost(Object result) throws UnknownHostException { + assertSingleAddressWithPort("localhost", null, result); + } + + private static void assertSingleAddressWithPort(String hostAddress, Integer port, Object result) + throws UnknownHostException { assertThat(result, is(instanceOf(ServerAddress[].class))); Collection addresses = Arrays.asList((ServerAddress[]) result); assertThat(addresses, hasSize(1)); - assertThat(addresses, hasItem(new ServerAddress("localhost"))); + if (port == null) { + assertThat(addresses, hasItem(new ServerAddress(InetAddress.getByName(hostAddress)))); + } else { + assertThat(addresses, hasItem(new ServerAddress(InetAddress.getByName(hostAddress), port))); + } } }