diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfiguration.java index 2a272e1a1b..825d9636ba 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfiguration.java @@ -21,6 +21,8 @@ import java.time.Duration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; import javax.net.ssl.SSLContext; @@ -114,7 +116,7 @@ public class CassandraAutoConfiguration { mapQueryOptions(properties, options); mapSocketOptions(properties, options); mapPoolingOptions(properties, options); - map.from(properties::getContactPoints) + map.from(mapContactPoints(properties)) .to((contactPoints) -> options.add(DefaultDriverOption.CONTACT_POINTS, contactPoints)); map.from(properties.getLocalDatacenter()).to( (localDatacenter) -> options.add(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, localDatacenter)); @@ -152,6 +154,29 @@ public class CassandraAutoConfiguration { .to((maxQueueSize) -> options.add(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE, maxQueueSize)); } + private List mapContactPoints(CassandraProperties properties) { + return properties.getContactPoints().stream() + .map((candidate) -> formatContactPoint(candidate, properties.getPort())).collect(Collectors.toList()); + } + + private String formatContactPoint(String candidate, int port) { + int i = candidate.lastIndexOf(':'); + if (i == -1 || !isPort(() -> candidate.substring(i + 1))) { + return String.format("%s:%s", candidate, port); + } + return candidate; + } + + private boolean isPort(Supplier value) { + try { + int i = Integer.parseInt(value.get()); + return i > 0 && i < 65535; + } + catch (Exception ex) { + return false; + } + } + private static class CassandraDriverOptions { private final Map options = new LinkedHashMap<>(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.java index c5fe7596b6..cbdfd86f4a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.java @@ -51,10 +51,16 @@ public class CassandraProperties { private String sessionName; /** - * Cluster node addresses in the form 'host:port'. + * Cluster node addresses in the form 'host:port', or a simple 'host' to use the + * configured port. */ private final List contactPoints = new ArrayList<>(Collections.singleton("127.0.0.1:9042")); + /** + * Port to use if a contact point does not specify one. + */ + private int port = 9042; + /** * Datacenter that is considered "local". Contact points should be from this * datacenter. @@ -147,6 +153,14 @@ public class CassandraProperties { return this.contactPoints; } + public int getPort() { + return this.port; + } + + public void setPort(int port) { + this.port = port; + } + public String getLocalDatacenter() { return this.localDatacenter; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationTests.java index 897cc9a89c..42b8e5fb02 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationTests.java @@ -21,9 +21,6 @@ import com.datastax.oss.driver.api.core.CqlSessionBuilder; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigParseOptions; -import com.typesafe.config.impl.Parseable; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -78,6 +75,38 @@ class CassandraAutoConfigurationTests { }); } + @Test + void driverConfigLoaderWithContactPointAndNoPort() { + this.contextRunner + .withPropertyValues("spring.data.cassandra.contact-points=cluster.example.com,another.example.com:9041", + "spring.data.cassandra.local-datacenter=cassandra-eu1") + .run((context) -> { + assertThat(context).hasSingleBean(DriverConfigLoader.class); + DriverExecutionProfile configuration = context.getBean(DriverConfigLoader.class).getInitialConfig() + .getDefaultProfile(); + assertThat(configuration.getStringList(DefaultDriverOption.CONTACT_POINTS)) + .containsOnly("cluster.example.com:9042", "another.example.com:9041"); + assertThat(configuration.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER)) + .isEqualTo("cassandra-eu1"); + }); + } + + @Test + void driverConfigLoaderWithContactPointAndNoPortAndCustomPort() { + this.contextRunner + .withPropertyValues("spring.data.cassandra.contact-points=cluster.example.com:9041,another.example.com", + "spring.data.cassandra.port=9043", "spring.data.cassandra.local-datacenter=cassandra-eu1") + .run((context) -> { + assertThat(context).hasSingleBean(DriverConfigLoader.class); + DriverExecutionProfile configuration = context.getBean(DriverConfigLoader.class).getInitialConfig() + .getDefaultProfile(); + assertThat(configuration.getStringList(DefaultDriverOption.CONTACT_POINTS)) + .containsOnly("cluster.example.com:9041", "another.example.com:9043"); + assertThat(configuration.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER)) + .isEqualTo("cassandra-eu1"); + }); + } + @Test void driverConfigLoaderWithCustomSessionName() { this.contextRunner.withPropertyValues("spring.data.cassandra.session-name=testcluster").run((context) -> { @@ -97,16 +126,6 @@ class CassandraAutoConfigurationTests { }); } - @Test - void driverConfigLoaderApplyConsistentDefaults() { - this.contextRunner.run((context) -> { - Config defaultConfig = defaultConfig(); - DriverExecutionProfile config = context.getBean(DriverConfigLoader.class).getInitialConfig() - .getDefaultProfile(); - // TODO - }); - } - @Test void driverConfigLoaderCustomizePoolOptions() { this.contextRunner.withPropertyValues("spring.data.cassandra.pool.idle-timeout=42", @@ -120,10 +139,6 @@ class CassandraAutoConfigurationTests { }); } - private static Config defaultConfig() { - return Parseable.newResources("reference.conf", ConfigParseOptions.defaults()).parse().toConfig(); - } - @Configuration(proxyBeanMethods = false) static class SimpleDriverConfigLoaderBuilderCustomizerConfig { diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc index 8b87e6feed..8d1a139221 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc @@ -4518,6 +4518,18 @@ Generally, you provide `keyspace-name` and `contact-points` as well the local da spring.data.cassandra.local-datacenter=datacenter1 ---- +If the port is the same for all your contact points you can use a shortcut and only specify the host names, as shown in the following example: + +[source,properties,indent=0,configprops] +---- + spring.data.cassandra.keyspace-name=mykeyspace + spring.data.cassandra.contact-points=cassandrahost1,cassandrahost2 + spring.data.cassandra.local-datacenter=datacenter1 +---- + +TIP: Those two examples are identical as the port default to `9042`. +If you need to configure the port, use `spring.data.cassandra.port`. + You can also register an arbitrary number of beans that implement `DriverConfigLoaderBuilderCustomizer` for more advanced driver customizations. The `CqlSession` can be customized with a bean of type `CqlSessionBuilderCustomizer`.