diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraClusterFactoryBean.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraClusterFactoryBean.java index 8ef41bc48..34b3a70b3 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraClusterFactoryBean.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraClusterFactoryBean.java @@ -34,13 +34,16 @@ import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.cassandra.core.cql.CassandraExceptionTranslator; import org.springframework.data.cassandra.core.cql.CqlTemplate; +import org.springframework.data.cassandra.core.cql.generator.AlterKeyspaceCqlGenerator; import org.springframework.data.cassandra.core.cql.generator.CreateKeyspaceCqlGenerator; import org.springframework.data.cassandra.core.cql.generator.DropKeyspaceCqlGenerator; +import org.springframework.data.cassandra.core.cql.keyspace.AlterKeyspaceSpecification; import org.springframework.data.cassandra.core.cql.keyspace.CreateKeyspaceSpecification; import org.springframework.data.cassandra.core.cql.keyspace.DropKeyspaceSpecification; import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceActionSpecification; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -131,6 +134,7 @@ public class CassandraClusterFactoryBean private @Nullable LatencyTracker latencyTracker; private List keyspaceCreations = new ArrayList<>(); + private List keyspaceAlterations = new ArrayList<>(); private List keyspaceDrops = new ArrayList<>(); private Set keyspaceSpecifications = new HashSet<>(); private List keyspaceActions = new ArrayList<>(); @@ -219,7 +223,12 @@ public class CassandraClusterFactoryBean Optional.ofNullable(latencyTracker).ifPresent(cluster::register); generateSpecificationsFromFactoryBeans(); - executeSpecsAndScripts(keyspaceCreations, startupScripts, cluster); + + List startup = new ArrayList<>(keyspaceCreations.size() + keyspaceAlterations.size()); + startup.addAll(keyspaceCreations); + startup.addAll(keyspaceAlterations); + + executeSpecsAndScripts(startup, startupScripts, cluster); } /* @@ -307,6 +316,10 @@ public class CassandraClusterFactoryBean if (keyspaceActionSpecification instanceof DropKeyspaceSpecification) { keyspaceDrops.add((DropKeyspaceSpecification) keyspaceActionSpecification); } + + if (keyspaceActionSpecification instanceof AlterKeyspaceSpecification) { + keyspaceAlterations.add((AlterKeyspaceSpecification) keyspaceActionSpecification); + } }); } @@ -332,11 +345,22 @@ public class CassandraClusterFactoryBean } } - private String toCql(KeyspaceActionSpecification keyspaceActionSpecification) { + private String toCql(KeyspaceActionSpecification specification) { - return (keyspaceActionSpecification instanceof CreateKeyspaceSpecification - ? new CreateKeyspaceCqlGenerator((CreateKeyspaceSpecification) keyspaceActionSpecification).toCql() - : new DropKeyspaceCqlGenerator((DropKeyspaceSpecification) keyspaceActionSpecification).toCql()); + if (specification instanceof CreateKeyspaceSpecification) { + return new CreateKeyspaceCqlGenerator((CreateKeyspaceSpecification) specification).toCql(); + } + + if (specification instanceof DropKeyspaceSpecification) { + return new DropKeyspaceCqlGenerator((DropKeyspaceSpecification) specification).toCql(); + } + + if (specification instanceof AlterKeyspaceSpecification) { + return new AlterKeyspaceCqlGenerator((AlterKeyspaceSpecification) specification).toCql(); + } + + throw new IllegalArgumentException( + "Unsupported specification type: " + ClassUtils.getQualifiedName(specification.getClass())); } /* diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/KeyspaceActionSpecificationFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/KeyspaceActionSpecificationFactory.java index 8d9f6e4e3..b8fa77198 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/KeyspaceActionSpecificationFactory.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/KeyspaceActionSpecificationFactory.java @@ -23,10 +23,12 @@ import java.util.List; import java.util.Map; import org.springframework.data.cassandra.core.cql.KeyspaceIdentifier; +import org.springframework.data.cassandra.core.cql.keyspace.AlterKeyspaceSpecification; import org.springframework.data.cassandra.core.cql.keyspace.CreateKeyspaceSpecification; import org.springframework.data.cassandra.core.cql.keyspace.DataCenterReplication; import org.springframework.data.cassandra.core.cql.keyspace.DefaultOption; import org.springframework.data.cassandra.core.cql.keyspace.DropKeyspaceSpecification; +import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceActionSpecification; import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceOption; import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceOption.ReplicationStrategy; import org.springframework.data.cassandra.core.cql.keyspace.Option; @@ -83,25 +85,77 @@ class KeyspaceActionSpecificationFactory { CreateKeyspaceSpecification create = CreateKeyspaceSpecification.createKeyspace(name).ifNotExists(ifNotExists) .with(KeyspaceOption.DURABLE_WRITES, durableWrites); - Map replicationStrategyMap = new HashMap<>(); - replicationStrategyMap.put(new DefaultOption("class", String.class, true, false, true), - replicationStrategy.getValue()); + Map replication = getReplication(); - if (replicationStrategy == ReplicationStrategy.SIMPLE_STRATEGY) { - replicationStrategyMap.put(new DefaultOption("replication_factor", Long.class, true, false, false), - replicationFactor); + if (!replication.isEmpty()) { + create.with(KeyspaceOption.REPLICATION, replication); } - if (replicationStrategy == ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY) { - for (DataCenterReplication datacenter : replications) { - replicationStrategyMap.put(new DefaultOption(datacenter.getDataCenter(), Long.class, true, false, false), - datacenter.getReplicationFactor()); + return create; + } + + /** + * Generate a {@link AlterKeyspaceSpecification} for the keyspace. + * + * @return the {@link AlterKeyspaceSpecification}. + * @since 2.0.1 + */ + public KeyspaceActionSpecification alter() { + + AlterKeyspaceSpecification alter = AlterKeyspaceSpecification.alterKeyspace(name) + .with(KeyspaceOption.DURABLE_WRITES, durableWrites); + + Map replication = getReplication(); + + if (!replication.isEmpty()) { + alter.with(KeyspaceOption.REPLICATION, replication); + } + + return alter; + } + + /** + * Create replication options represented as {@link Map}. + * + * @return the replication options represented as {@link Map}. + * @since 2.0.1 + */ + protected Map getReplication() { + + Map replicationStrategyMap = new HashMap<>(); + + if (hasReplicationOptions()) { + + replicationStrategyMap.put(new DefaultOption("class", String.class, true, false, true), + replicationStrategy.getValue()); + + if (replicationStrategy == ReplicationStrategy.SIMPLE_STRATEGY) { + replicationStrategyMap.put(new DefaultOption("replication_factor", Long.class, true, false, false), + replicationFactor); + } + + if (replicationStrategy == ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY) { + for (DataCenterReplication datacenter : replications) { + replicationStrategyMap.put(new DefaultOption(datacenter.getDataCenter(), Long.class, true, false, false), + datacenter.getReplicationFactor()); + } } } - create.with(KeyspaceOption.REPLICATION, replicationStrategyMap); + return replicationStrategyMap; + } - return create; + private boolean hasReplicationOptions() { + + if (replicationStrategy == ReplicationStrategy.SIMPLE_STRATEGY && replicationFactor > 0) { + return true; + } + + if (replicationStrategy == ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY && !replications.isEmpty()) { + return true; + } + + return false; } /** diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/KeyspaceActionSpecificationFactoryBean.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/KeyspaceActionSpecificationFactoryBean.java index 8ae80654e..f6bc46405 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/KeyspaceActionSpecificationFactoryBean.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/KeyspaceActionSpecificationFactoryBean.java @@ -63,8 +63,8 @@ public class KeyspaceActionSpecificationFactoryBean implements FactoryBean actions = bean.getObject().getActions(); + + assertThat(actions).hasSize(1).hasAtLeastOneElementOfType(AlterKeyspaceSpecification.class); + + AlterKeyspaceSpecification alter = (AlterKeyspaceSpecification) actions.get(0); + + assertThat(alter.getName()).isEqualTo(KeyspaceIdentifier.of("my_keyspace")); + assertThat(alter.getOptions()).containsKeys("durable_writes", "replication"); + } + + @Test // DATACASS-502 + public void shouldAlterKeyspaceWithSimpleReplication() { + + bean.setAction(KeyspaceAction.ALTER); + bean.setDurableWrites(true); + bean.setName("my_keyspace"); + bean.setReplicationFactor(5); + + bean.afterPropertiesSet(); + + List actions = bean.getObject().getActions(); + + assertThat(actions).hasSize(1).hasAtLeastOneElementOfType(AlterKeyspaceSpecification.class); + + AlterKeyspaceSpecification alter = (AlterKeyspaceSpecification) actions.get(0); + + assertThat(alter.getName()).isEqualTo(KeyspaceIdentifier.of("my_keyspace")); + assertThat(alter.getOptions()).containsKeys("durable_writes", "replication"); + } + + @Test // DATACASS-502 + public void shouldAlterKeyspaceWithoutReplication() { + + bean.setAction(KeyspaceAction.ALTER); + bean.setDurableWrites(true); + bean.setName("my_keyspace"); + bean.afterPropertiesSet(); + + List actions = bean.getObject().getActions(); + + assertThat(actions).hasSize(1).hasAtLeastOneElementOfType(AlterKeyspaceSpecification.class); + + AlterKeyspaceSpecification alter = (AlterKeyspaceSpecification) actions.get(0); + + assertThat(alter.getName()).isEqualTo(KeyspaceIdentifier.of("my_keyspace")); + assertThat(alter.getOptions()).doesNotContainKeys("replication"); + } +} diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/FullySpecifiedKeyspaceCreatingXmlConfigIntegrationTests-context.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/FullySpecifiedKeyspaceCreatingXmlConfigIntegrationTests-context.xml index fca03cdc8..ce905b59f 100644 --- a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/FullySpecifiedKeyspaceCreatingXmlConfigIntegrationTests-context.xml +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/FullySpecifiedKeyspaceCreatingXmlConfigIntegrationTests-context.xml @@ -20,11 +20,15 @@ - - - - + + + + + + +