diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CqlSessionFactoryBean.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CqlSessionFactoryBean.java index 8d36521e3..b4451b341 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CqlSessionFactoryBean.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CqlSessionFactoryBean.java @@ -24,7 +24,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.IntFunction; -import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -73,17 +72,16 @@ import com.datastax.oss.driver.api.core.CqlSessionBuilder; public class CqlSessionFactoryBean implements FactoryBean, InitializingBean, DisposableBean, PersistenceExceptionTranslator { + public static final String CASSANDRA_SYSTEM_SESSION = "system"; + public static final String DEFAULT_CONTACT_POINTS = "localhost"; + public static final int DEFAULT_PORT = 9042; + private static final boolean DEFAULT_CREATE_IF_NOT_EXISTS = false; private static final boolean DEFAULT_DROP_TABLES = false; private static final boolean DEFAULT_DROP_UNUSED_TABLES = false; - public static final int DEFAULT_PORT = 9042; - private static final CassandraExceptionTranslator EXCEPTION_TRANSLATOR = new CassandraExceptionTranslator(); - public static final String CASSANDRA_SYSTEM_SESSION = "system"; - public static final String DEFAULT_CONTACT_POINTS = "localhost"; - private int port = DEFAULT_PORT; private @Nullable CassandraConverter converter; @@ -118,15 +116,6 @@ public class CqlSessionFactoryBean private @Nullable String localDatacenter; private @Nullable String password; private @Nullable String username; - private Supplier setCqlSessionBuilderSupplier; - - public CqlSessionFactoryBean() { - this(CqlSession::builder); - } - - CqlSessionFactoryBean(Supplier setCqlSessionBuilderSupplier) { - this.setCqlSessionBuilderSupplier = setCqlSessionBuilderSupplier; - } /** * Null-safe operation to determine whether the Cassandra {@link CqlSession} is connected or not. @@ -144,11 +133,15 @@ public class CqlSessionFactoryBean /** * Set a comma-delimited string of the contact points (hosts) to connect to. Default is {@code localhost}; see - * {@link #DEFAULT_CONTACT_POINTS}. It can be in the form 'host:port', or a simple 'host' to use the configured port. + * {@link #DEFAULT_CONTACT_POINTS}. Contact points may use the form {@code host:port}, or a simple {@code host} to use + * the configured {@link #setPort(int) port}. * - * @param contactPoints the contact points used by the new cluster. + * @param contactPoints the contact points used by the new cluster, must not be {@literal null}. */ public void setContactPoints(String contactPoints) { + + Assert.hasText(contactPoints, "Contact points must not be empty"); + this.contactPoints = port -> createInetSocketAddresses(contactPoints, port); } @@ -156,13 +149,18 @@ public class CqlSessionFactoryBean * Set a collection of the contact points (hosts) to connect to. Default is {@code localhost}; see * {@link #DEFAULT_CONTACT_POINTS}. * - * @param contactPoints the contact points used by the new cluster. + * @param contactPoints the contact points used by the new cluster, must not be {@literal null}. Use + * {@link InetSocketAddress#createUnresolved(String, int) unresolved addresses} to delegate hostname + * resolution to the driver. + * @since 3.1 */ public void setContactPoints(Collection contactPoints) { + + Assert.notNull(contactPoints, "Contact points must not be null"); + this.contactPoints = unusedPort -> contactPoints; } - /** * Sets the name of the local datacenter. * @@ -204,8 +202,8 @@ public class CqlSessionFactoryBean * {@link CassandraMappingContext} inside {@code converter}. * * @param converter must not be {@literal null}. - * @deprecated Use {@link CassandraSessionFactoryBean} with - * {@link CassandraSessionFactoryBean#setConverter(CassandraConverter)} instead. + * @deprecated Use {@link SessionFactoryFactoryBean} with + * {@link SessionFactoryFactoryBean#setConverter(CassandraConverter)} instead. */ @Deprecated public void setConverter(CassandraConverter converter) { @@ -464,10 +462,11 @@ public class CqlSessionFactoryBean } protected CqlSessionBuilder buildBuilder() { + Collection addresses = contactPoints.apply(this.port); Assert.notEmpty(addresses, "At least one server is required"); - CqlSessionBuilder sessionBuilder = setCqlSessionBuilderSupplier.get(); + CqlSessionBuilder sessionBuilder = createBuilder(); addresses.forEach(sessionBuilder::addContactPoint); @@ -479,48 +478,12 @@ public class CqlSessionFactoryBean sessionBuilder.withLocalDatacenter(this.localDatacenter); } - return this.sessionBuilderConfigurer != null - ? this.sessionBuilderConfigurer.configure(sessionBuilder) - : sessionBuilder; + return this.sessionBuilderConfigurer != null ? this.sessionBuilderConfigurer.configure(sessionBuilder) + : sessionBuilder; } - private Collection createInetSocketAddresses(String contactPoints, int port) { - return StringUtils.commaDelimitedListToSet(contactPoints).stream().map(candidate -> toHostAndPort(candidate, port)) - .map(hostAndPort -> InetSocketAddress.createUnresolved(hostAndPort.host, hostAndPort.port)) - .collect(Collectors.toList()); - } - - private static class HostAndPort { - private final String host; - private final int port; - - private HostAndPort(String host, int port) { - this.host = host; - this.port = port; - } - } - - private HostAndPort toHostAndPort(String candidate, int port) { - int i = candidate.lastIndexOf(':'); - if (i == -1 || !isPort(() -> candidate.substring(i + 1))) { - return new HostAndPort(candidate, port); - } else { - String[] hostAndPort = candidate.split(":"); - if (hostAndPort.length != 2) { - throw new IllegalArgumentException( - String.format("The provided contact point: %s has wrong format.", candidate)); - } - return new HostAndPort(hostAndPort[0], Integer.parseInt(hostAndPort[1])); - } - } - - private boolean isPort(Supplier value) { - try { - int i = Integer.parseInt(value.get()); - return i > 0 && i < 65535; - } catch (Exception ex) { - return false; - } + CqlSessionBuilder createBuilder() { + return CqlSession.builder(); } /** @@ -536,8 +499,8 @@ public class CqlSessionFactoryBean } /** - * Build a {@link CqlSession Session} to the user-defined {@literal Keyspace} or the default {@literal Keyspace} - * if the user did not specify a {@literal Keyspace} by {@link String name}. + * Build a {@link CqlSession Session} to the user-defined {@literal Keyspace} or the default {@literal Keyspace} if + * the user did not specify a {@literal Keyspace} by {@link String name}. * * @param sessionBuilder {@link CqlSessionBuilder} used to a build a Cassandra {@link CqlSession}. * @return the built {@link CqlSession} to the user-defined {@literal Keyspace}. @@ -557,8 +520,8 @@ public class CqlSessionFactoryBean generateSpecificationsFromFactoryBeanDeclarations(); - List keyspaceStartupSpecifications = - new ArrayList<>(this.keyspaceCreations.size() + this.keyspaceAlterations.size()); + List keyspaceStartupSpecifications = new ArrayList<>( + this.keyspaceCreations.size() + this.keyspaceAlterations.size()); keyspaceStartupSpecifications.addAll(this.keyspaceCreations); keyspaceStartupSpecifications.addAll(this.keyspaceAlterations); @@ -567,8 +530,8 @@ public class CqlSessionFactoryBean } /** - * Evaluates the contents of all the {@link KeyspaceActionSpecificationFactoryBean}s - * and generates the proper {@link KeyspaceActionSpecification}s from them. + * Evaluates the contents of all the {@link KeyspaceActionSpecificationFactoryBean}s and generates the proper + * {@link KeyspaceActionSpecification}s from them. */ private void generateSpecificationsFromFactoryBeanDeclarations() { @@ -582,11 +545,9 @@ public class CqlSessionFactoryBean if (specification instanceof AlterKeyspaceSpecification) { this.keyspaceAlterations.add((AlterKeyspaceSpecification) specification); - } - else if (specification instanceof CreateKeyspaceSpecification) { + } else if (specification instanceof CreateKeyspaceSpecification) { this.keyspaceCreations.add((CreateKeyspaceSpecification) specification); - } - else if (specification instanceof DropKeyspaceSpecification) { + } else if (specification instanceof DropKeyspaceSpecification) { this.keyspaceDrops.add((DropKeyspaceSpecification) specification); } }); @@ -625,10 +586,10 @@ public class CqlSessionFactoryBean * Perform schema actions. * * @param drop {@literal true} to drop types/tables. - * @param dropUnused {@literal true} to drop unused types/tables (i.e. types/tables not known to be used by - * the {@link CassandraMappingContext}). - * @param ifNotExists {@literal true} to perform fail-safe creations by adding {@code IF NOT EXISTS} - * to each creation statement. + * @param dropUnused {@literal true} to drop unused types/tables (i.e. types/tables not known to be used by the + * {@link CassandraMappingContext}). + * @param ifNotExists {@literal true} to perform fail-safe creations by adding {@code IF NOT EXISTS} to each creation + * statement. */ protected void createTables(boolean drop, boolean dropUnused, boolean ifNotExists) { @@ -640,13 +601,13 @@ public class CqlSessionFactoryBean private void performSchemaActions(boolean drop, boolean dropUnused, boolean ifNotExists, CassandraAdminOperations adminOperations) { - CassandraPersistentEntitySchemaCreator schemaCreator = - new CassandraPersistentEntitySchemaCreator(getMappingContext(), adminOperations); + CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator( + getMappingContext(), adminOperations); if (drop) { - CassandraPersistentEntitySchemaDropper schemaDropper = - new CassandraPersistentEntitySchemaDropper(getMappingContext(), adminOperations); + CassandraPersistentEntitySchemaDropper schemaDropper = new CassandraPersistentEntitySchemaDropper( + getMappingContext(), adminOperations); schemaDropper.dropTables(dropUnused); schemaDropper.dropUserTypes(dropUnused); @@ -657,6 +618,34 @@ public class CqlSessionFactoryBean schemaCreator.createIndexes(ifNotExists); } + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#getObject() + */ + @Override + public CqlSession getObject() { + return this.session; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + @Override + public Class getObjectType() { + return CqlSession.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException) + */ + @Nullable + @Override + public DataAccessException translateExceptionIfPossible(RuntimeException e) { + return EXCEPTION_TRANSLATOR.translateExceptionIfPossible(e); + } + /* * (non-Javadoc) * @see org.springframework.beans.factory.DisposableBean#destroy() @@ -686,37 +675,8 @@ public class CqlSessionFactoryBean this.systemSession.close(); } - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.FactoryBean#getObject() - */ - @Override - public CqlSession getObject() { - return this.session; - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.FactoryBean#getObjectType() - */ - @Override - public Class getObjectType() { - return CqlSession.class; - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.FactoryBean#isSingleton() - */ - @Override - public boolean isSingleton() { - return true; - } - /** - * Executes the given, raw Cassandra CQL scripts. - * - * The {@link CqlSession} must be connected when this method is called. + * Executes the given, raw Cassandra CQL scripts. The {@link CqlSession} must be connected when this method is called. * * @see com.datastax.oss.driver.api.core.CqlSession#execute(String) */ @@ -733,7 +693,8 @@ public class CqlSessionFactoryBean if (!CollectionUtils.isEmpty(keyspaceActionSpecifications) || !CollectionUtils.isEmpty(keyspaceCqlScripts)) { - Stream keyspaceActionSpecificationsStream = keyspaceActionSpecifications.stream().map(this::toCql); + Stream keyspaceActionSpecificationsStream = keyspaceActionSpecifications.stream() + .map(CqlSessionFactoryBean::toCql); Stream keyspaceCqlScriptsStream = keyspaceCqlScripts.stream(); Stream cql = Stream.concat(keyspaceActionSpecificationsStream, keyspaceCqlScriptsStream); @@ -748,25 +709,110 @@ public class CqlSessionFactoryBean * @return a {@link String} containing the CQL for the given {@link KeyspaceActionSpecification}. * @see org.springframework.data.cassandra.core.cql.keyspace.KeyspaceActionSpecification */ - private String toCql(KeyspaceActionSpecification specification) { + private static String toCql(KeyspaceActionSpecification specification) { if (specification instanceof AlterKeyspaceSpecification) { return new AlterKeyspaceCqlGenerator((AlterKeyspaceSpecification) specification).toCql(); - } - else if (specification instanceof CreateKeyspaceSpecification) { + } else if (specification instanceof CreateKeyspaceSpecification) { return new CreateKeyspaceCqlGenerator((CreateKeyspaceSpecification) specification).toCql(); - } - else if (specification instanceof DropKeyspaceSpecification) { + } else if (specification instanceof DropKeyspaceSpecification) { return new DropKeyspaceCqlGenerator((DropKeyspaceSpecification) specification).toCql(); } - throw new IllegalArgumentException(String.format("Unsupported specification type: %s", - ClassUtils.getQualifiedName(specification.getClass()))); + throw new IllegalArgumentException( + String.format("Unsupported specification type: %s", ClassUtils.getQualifiedName(specification.getClass()))); } - @Nullable - @Override - public DataAccessException translateExceptionIfPossible(RuntimeException e) { - return EXCEPTION_TRANSLATOR.translateExceptionIfPossible(e); + private static Collection createInetSocketAddresses(String contactPoints, int defaultPort) { + + return StringUtils.commaDelimitedListToSet(contactPoints) // + .stream() // + .map(contactPoint -> HostAndPort.createWithDefaultPort(contactPoint, defaultPort)) // + .map(hostAndPort -> InetSocketAddress.createUnresolved(hostAndPort.getHost(), hostAndPort.getPort())) // + .collect(Collectors.toList()); + } + + /** + * Value object to encapsulate host and port. + */ + private static class HostAndPort { + + private final String host; + + private final int port; + + private HostAndPort(String host, int port) { + this.host = host; + this.port = port; + } + + /** + * Create a {@link HostAndPort} from a contact point. Contact points may contain a port or can be specified + * port-less. Contact points may be: + *
    + *
  • Plain IPv4 addresses ({@code 1.2.3.4})
  • + *
  • Hostnames ({@code foo.bar.baz})
  • + *
  • IPv6 without brackets {@code 1:2::3}
  • + *
  • IPv6 with brackets {@code [1:2::3]}
  • + *
  • IPv4 addresses with port ({@code 1.2.3.4:1234})
  • + *
  • Hostnames with port ({@code foo.bar.baz:1234})
  • + *
  • IPv6 with brackets and port {@code [1:2::3]:1234}
  • + *
+ * + * @param contactPoint must not be {@literal null}. + * @param defaultPort + * @return the host and port representation. + */ + static HostAndPort createWithDefaultPort(String contactPoint, int defaultPort) { + + int i = contactPoint.lastIndexOf(':'); + + if (i == -1 || !isValidPort(contactPoint.substring(i + 1))) { + return new HostAndPort(contactPoint, defaultPort); + } + + String[] hostAndPort = contactPoint.split(":"); + String host; + int port = defaultPort; + + if (hostAndPort.length != 2) { + + int bracketEnd = contactPoint.indexOf(']'); + if (contactPoint.startsWith("[") && bracketEnd != -1) { + + // IPv6 as resource identifier enclosed with brackets [ ] + host = contactPoint.substring(0, bracketEnd + 1); + String remainder = contactPoint.substring(bracketEnd + 1); + + if (remainder.startsWith(":")) { + port = Integer.parseInt(remainder.substring(1)); + } + } else { + // everything else + host = contactPoint; + } + } else { + host = hostAndPort[0]; + port = Integer.parseInt(hostAndPort[1]); + } + return new HostAndPort(host, port); + } + + private static boolean isValidPort(String value) { + try { + int i = Integer.parseInt(value); + return i > 0 && i < 65535; + } catch (NumberFormatException ex) { + return false; + } + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/CqlSessionFactoryBeanUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/CqlSessionFactoryBeanUnitTests.java index 5f5ae847f..a5446667d 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/CqlSessionFactoryBeanUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/CqlSessionFactoryBeanUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2020 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,118 +18,151 @@ package org.springframework.data.cassandra.config; import static org.mockito.Mockito.*; import java.net.InetSocketAddress; -import java.util.Arrays; - -import org.assertj.core.api.Assertions; +import java.util.Collections; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import com.datastax.oss.driver.api.core.CqlSessionBuilder; /** + * Unit tests for {@link CqlSessionFactoryBean}. + * * @author Tomasz Lelek + * @author Mark Paluch */ @ExtendWith(MockitoExtension.class) class CqlSessionFactoryBeanUnitTests { @Mock CqlSessionBuilder cqlSessionBuilder; - @Test - public void constructCqlSessionBuilderWithDefaultHostAndPort() { - new CqlSessionFactoryBean(() -> cqlSessionBuilder).buildBuilder(); + @Test // DATACASS-766 + void shouldConstructCqlSessionBuilderWithDefaultHostAndPort() { - verify(cqlSessionBuilder, times(1)).addContactPoint(InetSocketAddress + new MyCqlSessionFactoryBean(cqlSessionBuilder).buildBuilder(); + + verify(cqlSessionBuilder).addContactPoint(InetSocketAddress .createUnresolved(CqlSessionFactoryBean.DEFAULT_CONTACT_POINTS, CqlSessionFactoryBean.DEFAULT_PORT)); } - @Test - public void constructCqlSessionBuilderWithDefaultHostAndNonDefaultPort() { - CqlSessionFactoryBean cqlSessionFactoryBean = new CqlSessionFactoryBean(() -> cqlSessionBuilder); + @Test // DATACASS-766 + void shouldConstructCqlSessionBuilderWithDefaultHostAndNonDefaultPort() { + + CqlSessionFactoryBean cqlSessionFactoryBean = new MyCqlSessionFactoryBean(cqlSessionBuilder); cqlSessionFactoryBean.setPort(1000); cqlSessionFactoryBean.buildBuilder(); - verify(cqlSessionBuilder, times(1)) + verify(cqlSessionBuilder) .addContactPoint(InetSocketAddress.createUnresolved(CqlSessionFactoryBean.DEFAULT_CONTACT_POINTS, 1000)); } - @Test - public void constructCqlSessionBuilderWithMultipleContactPointsSamePort() { - CqlSessionFactoryBean cqlSessionFactoryBean = new CqlSessionFactoryBean(() -> cqlSessionBuilder); + @Test // DATACASS-766 + void shouldConstructCqlSessionBuilderWithMultipleContactPointsSamePort() { + + CqlSessionFactoryBean cqlSessionFactoryBean = new MyCqlSessionFactoryBean(cqlSessionBuilder); cqlSessionFactoryBean.setPort(1000); cqlSessionFactoryBean.setContactPoints("a,b,c"); cqlSessionFactoryBean.buildBuilder(); - verify(cqlSessionBuilder, times(1)).addContactPoint(InetSocketAddress.createUnresolved("a", 1000)); - verify(cqlSessionBuilder, times(1)).addContactPoint(InetSocketAddress.createUnresolved("b", 1000)); - verify(cqlSessionBuilder, times(1)).addContactPoint(InetSocketAddress.createUnresolved("c", 1000)); + + verify(cqlSessionBuilder).addContactPoint(InetSocketAddress.createUnresolved("a", 1000)); + verify(cqlSessionBuilder).addContactPoint(InetSocketAddress.createUnresolved("b", 1000)); + verify(cqlSessionBuilder).addContactPoint(InetSocketAddress.createUnresolved("c", 1000)); } - @Test - public void constructCqlSessionBuilderWithMultipleContactPointsDifferentPorts() { - CqlSessionFactoryBean cqlSessionFactoryBean = new CqlSessionFactoryBean(() -> cqlSessionBuilder); + @Test // DATACASS-766 + void constructCqlSessionBuilderWithMultipleContactPointsDifferentPorts() { + + CqlSessionFactoryBean cqlSessionFactoryBean = new MyCqlSessionFactoryBean(cqlSessionBuilder); cqlSessionFactoryBean.setContactPoints("a:1000,b:1001,c:1002"); cqlSessionFactoryBean.buildBuilder(); - verify(cqlSessionBuilder, times(1)).addContactPoint(InetSocketAddress.createUnresolved("a", 1000)); - verify(cqlSessionBuilder, times(1)).addContactPoint(InetSocketAddress.createUnresolved("b", 1001)); - verify(cqlSessionBuilder, times(1)).addContactPoint(InetSocketAddress.createUnresolved("c", 1002)); + + verify(cqlSessionBuilder).addContactPoint(InetSocketAddress.createUnresolved("a", 1000)); + verify(cqlSessionBuilder).addContactPoint(InetSocketAddress.createUnresolved("b", 1001)); + verify(cqlSessionBuilder).addContactPoint(InetSocketAddress.createUnresolved("c", 1002)); } + @Test // DATACASS-766 + void shouldConstructCqlSessionBuilderWithExplicitPortsAndDefaultPort() { - @Test - public void throwWhenContactPointsWithPortHasWrongFormat() { - CqlSessionFactoryBean cqlSessionFactoryBean = new CqlSessionFactoryBean(() -> cqlSessionBuilder); - cqlSessionFactoryBean.setContactPoints("a:1000:100"); - Assertions.assertThatThrownBy(cqlSessionFactoryBean::buildBuilder).isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("The provided contact point: a:1000:100 has wrong format."); - } - - @Test - public void constructCqlSessionBuilderWithExplictPortsAndDefaultPort() { - CqlSessionFactoryBean cqlSessionFactoryBean = new CqlSessionFactoryBean(() -> cqlSessionBuilder); + CqlSessionFactoryBean cqlSessionFactoryBean = new MyCqlSessionFactoryBean(cqlSessionBuilder); cqlSessionFactoryBean.setContactPoints("a:1000,b:2000,c"); cqlSessionFactoryBean.buildBuilder(); - verify(cqlSessionBuilder, times(1)).addContactPoint(InetSocketAddress.createUnresolved("a", 1000)); - verify(cqlSessionBuilder, times(1)).addContactPoint(InetSocketAddress.createUnresolved("b", 2000)); - verify(cqlSessionBuilder, times(1)) + + verify(cqlSessionBuilder).addContactPoint(InetSocketAddress.createUnresolved("a", 1000)); + verify(cqlSessionBuilder).addContactPoint(InetSocketAddress.createUnresolved("b", 2000)); + verify(cqlSessionBuilder) .addContactPoint(InetSocketAddress.createUnresolved("c", CqlSessionFactoryBean.DEFAULT_PORT)); } - @Test - public void constructCqlSessionBuilderWithIpv6ContactPoint() { - CqlSessionFactoryBean cqlSessionFactoryBean = new CqlSessionFactoryBean(() -> cqlSessionBuilder); + @Test // DATACASS-766 + void shouldConstructCqlSessionBuilderWithIpv6ContactPoint() { + + CqlSessionFactoryBean cqlSessionFactoryBean = new MyCqlSessionFactoryBean(cqlSessionBuilder); cqlSessionFactoryBean.setPort(1000); - cqlSessionFactoryBean.setContactPoints("[2001:db8:85a3:8d3:1319:8a2e:370:7348]"); + cqlSessionFactoryBean.setContactPoints("2001:db8:85a3:8d3:1319:8a2e:370:7348"); cqlSessionFactoryBean.buildBuilder(); - verify(cqlSessionBuilder, times(1)) - .addContactPoint(InetSocketAddress.createUnresolved("[2001:db8:85a3:8d3:1319:8a2e:370:7348]", 1000)); + + verify(cqlSessionBuilder) + .addContactPoint(InetSocketAddress.createUnresolved("2001:db8:85a3:8d3:1319:8a2e:370:7348", 1000)); } - @Test - public void constructCqlSessionBuilderWithContactPointsProvidedAsInetSocketAddress() { - CqlSessionFactoryBean cqlSessionFactoryBean = new CqlSessionFactoryBean(() -> cqlSessionBuilder); - cqlSessionFactoryBean.setContactPoints(Arrays.asList(InetSocketAddress.createUnresolved("a", 1000))); + @Test // DATACASS-766 + void shouldConstructCqlSessionBuilderWithIpv6ContactPointAndPort() { + + CqlSessionFactoryBean cqlSessionFactoryBean = new MyCqlSessionFactoryBean(cqlSessionBuilder); + cqlSessionFactoryBean.setPort(1000); + cqlSessionFactoryBean.setContactPoints("[2001:db8:85a3:8d3:1319:8a2e:370:7348]:1234"); cqlSessionFactoryBean.buildBuilder(); - verify(cqlSessionBuilder, times(1)).addContactPoint(InetSocketAddress.createUnresolved("a", 1000)); + + verify(cqlSessionBuilder) + .addContactPoint(InetSocketAddress.createUnresolved("[2001:db8:85a3:8d3:1319:8a2e:370:7348]", 1234)); } - @Test - public void constructCqlSessionBuilderLastSetContactPointsOverridePreviousInet() { - CqlSessionFactoryBean cqlSessionFactoryBean = new CqlSessionFactoryBean(() -> cqlSessionBuilder); - cqlSessionFactoryBean.setContactPoints(Arrays.asList(InetSocketAddress.createUnresolved("a", 1000))); + @Test // DATACASS-766 + void shouldConstructCqlSessionBuilderWithContactPointsProvidedAsInetSocketAddress() { + + CqlSessionFactoryBean cqlSessionFactoryBean = new MyCqlSessionFactoryBean(cqlSessionBuilder); + cqlSessionFactoryBean.setContactPoints(Collections.singletonList(InetSocketAddress.createUnresolved("a", 1000))); + cqlSessionFactoryBean.buildBuilder(); + + verify(cqlSessionBuilder).addContactPoint(InetSocketAddress.createUnresolved("a", 1000)); + } + + @Test // DATACASS-766 + void shouldConstructCqlSessionBuilderLastSetContactPointsOverridePreviousInet() { + + CqlSessionFactoryBean cqlSessionFactoryBean = new MyCqlSessionFactoryBean(cqlSessionBuilder); + cqlSessionFactoryBean.setContactPoints(Collections.singletonList(InetSocketAddress.createUnresolved("a", 1000))); cqlSessionFactoryBean.setContactPoints("b:1000"); cqlSessionFactoryBean.buildBuilder(); - verify(cqlSessionBuilder, times(1)).addContactPoint(InetSocketAddress.createUnresolved("b", 1000)); + + verify(cqlSessionBuilder).addContactPoint(InetSocketAddress.createUnresolved("b", 1000)); } - @Test - public void constructCqlSessionBuilderLastSetContactPointsOverridePreviousString() { - CqlSessionFactoryBean cqlSessionFactoryBean = new CqlSessionFactoryBean(() -> cqlSessionBuilder); + @Test // DATACASS-766 + void shouldConstructCqlSessionBuilderLastSetContactPointsOverridePreviousString() { + + CqlSessionFactoryBean cqlSessionFactoryBean = new MyCqlSessionFactoryBean(cqlSessionBuilder); cqlSessionFactoryBean.setContactPoints("b:1000"); - cqlSessionFactoryBean.setContactPoints(Arrays.asList(InetSocketAddress.createUnresolved("a", 1000))); + cqlSessionFactoryBean.setContactPoints(Collections.singletonList(InetSocketAddress.createUnresolved("a", 1000))); cqlSessionFactoryBean.buildBuilder(); - verify(cqlSessionBuilder, times(1)).addContactPoint(InetSocketAddress.createUnresolved("a", 1000)); + + verify(cqlSessionBuilder).addContactPoint(InetSocketAddress.createUnresolved("a", 1000)); + } + + static class MyCqlSessionFactoryBean extends CqlSessionFactoryBean { + + final CqlSessionBuilder cqlSessionBuilder; + + MyCqlSessionFactoryBean(CqlSessionBuilder cqlSessionBuilder) { + this.cqlSessionBuilder = cqlSessionBuilder; + } + + @Override + CqlSessionBuilder createBuilder() { + return cqlSessionBuilder; + } } } diff --git a/src/main/asciidoc/reference/cassandra.adoc b/src/main/asciidoc/reference/cassandra.adoc index 07c0d722b..74601b5e4 100644 --- a/src/main/asciidoc/reference/cassandra.adoc +++ b/src/main/asciidoc/reference/cassandra.adoc @@ -185,16 +185,14 @@ This section describes how to configure Spring Data Cassandra with XML. [[cassandra-connectors.xmlconfig.ext_properties]] ==== Externalizing Connection Properties -To externalize connection properties, you should first create a properties file that contains the information needed to connect to Cassandra. `contactpoints` and `keyspace` are the equired fields. -We added `port` for clarity. +To externalize connection properties, you should first create a properties file that contains the information needed to connect to Cassandra. `contactpoints` and `keyspace` are the required fields. The following example shows our properties file, called `cassandra.properties`: ==== [source] ---- -cassandra.contactpoints=10.1.55.80,10.1.55.81 -cassandra.port=9042 +cassandra.contactpoints=10.1.55.80:9042,10.1.55.81:9042 cassandra.keyspace=showcase ---- ==== @@ -256,8 +254,7 @@ in placeholders in the bean definitions --> - +