From e85cb2c4c5cc30a540d646fae2ca6c3800b59128 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 3 May 2016 16:44:02 +0200 Subject: [PATCH] Add schemaAction property to CassandraProperties Set schemaAction property in CassandraSessionFactoryBean. Use relaxed property resolver for enum lookup of the configured schemaAction. See gh-5855 --- .travis.yml | 1 + .../cassandra/CassandraProperties.java | 14 +++ .../CassandraDataAutoConfiguration.java | 14 ++- ...DataAutoConfigurationIntegrationTests.java | 8 ++ .../CassandraDataAutoConfigurationTests.java | 85 +++++++++++++++++++ ...draRepositoriesAutoConfigurationTests.java | 26 ++++++ .../data/cassandra/CassandraTestServer.java | 8 ++ .../data/cassandra/city/City.java | 4 + .../appendix-application-properties.adoc | 1 + 9 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraTestServer.java diff --git a/.travis.yml b/.travis.yml index 68e0c01fc74..4abb8a807d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ jdk: services: - mongodb - redis + - cassandra sudo: false install: true before_script: diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.java index a2d04ec74bf..eeab6ec4898 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.java @@ -32,6 +32,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * * @author Julien Dubois * @author Phillip Webb + * @author Mark Paluch * @since 1.3.0 */ @ConfigurationProperties(prefix = "spring.data.cassandra") @@ -112,6 +113,11 @@ public class CassandraProperties { */ private int readTimeoutMillis = SocketOptions.DEFAULT_READ_TIMEOUT_MILLIS; + /** + * Action to take at startup. + */ + private String schemaAction = "none"; + /** * Enable SSL support. */ @@ -247,4 +253,12 @@ public class CassandraProperties { this.ssl = ssl; } + public String getSchemaAction() { + return this.schemaAction; + } + + public void setSchemaAction(String schemaAction) { + this.schemaAction = schemaAction; + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.java index a086da5a020..280c8a7ffb1 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.java @@ -30,9 +30,12 @@ import org.springframework.boot.autoconfigure.cassandra.CassandraProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.domain.EntityScanPackages; +import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertyResolver; import org.springframework.data.cassandra.config.CassandraEntityClassScanner; import org.springframework.data.cassandra.config.CassandraSessionFactoryBean; import org.springframework.data.cassandra.config.SchemaAction; @@ -48,6 +51,7 @@ import org.springframework.data.cassandra.mapping.CassandraMappingContext; * * @author Julien Dubois * @author Eddú Meléndez + * @author Mark Paluch * @since 1.3.0 */ @Configuration @@ -62,11 +66,14 @@ public class CassandraDataAutoConfiguration { private final Cluster cluster; + private final PropertyResolver propertyResolver; + public CassandraDataAutoConfiguration(BeanFactory beanFactory, - CassandraProperties properties, Cluster cluster) { + CassandraProperties properties, Cluster cluster, Environment environment) { this.beanFactory = beanFactory; this.properties = properties; this.cluster = cluster; + this.propertyResolver = new RelaxedPropertyResolver(environment, "spring.data.cassandra."); } @Bean @@ -94,11 +101,14 @@ public class CassandraDataAutoConfiguration { @ConditionalOnMissingBean(Session.class) public CassandraSessionFactoryBean session(CassandraConverter converter) throws Exception { + CassandraSessionFactoryBean session = new CassandraSessionFactoryBean(); session.setCluster(this.cluster); session.setConverter(converter); session.setKeyspaceName(this.properties.getKeyspaceName()); - session.setSchemaAction(SchemaAction.NONE); + + SchemaAction schemaAction = propertyResolver.getProperty("schemaAction", SchemaAction.class, SchemaAction.NONE); + session.setSchemaAction(schemaAction); return session; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java new file mode 100644 index 00000000000..3b3b493de52 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java @@ -0,0 +1,8 @@ +package org.springframework.boot.autoconfigure.data.cassandra; + +/** + * + * @author Stephane Nicoll + */ +public class CassandraDataAutoConfigurationIntegrationTests { +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationTests.java index bd9b15fd7b6..5a274f773fe 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationTests.java @@ -18,19 +18,26 @@ package org.springframework.boot.autoconfigure.data.cassandra; import java.util.Set; +import com.datastax.driver.core.Cluster; import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.DriverException; + import org.junit.After; import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; import org.springframework.boot.autoconfigure.data.cassandra.city.City; import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; +import org.springframework.data.cassandra.config.CassandraSessionFactoryBean; +import org.springframework.data.cassandra.config.SchemaAction; import org.springframework.data.cassandra.core.CassandraTemplate; import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.test.util.ReflectionTestUtils; @@ -42,6 +49,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link CassandraDataAutoConfiguration} * * @author Eddú Meléndez + * @author Mark Paluch */ public class CassandraDataAutoConfigurationTests { @@ -65,6 +73,47 @@ public class CassandraDataAutoConfigurationTests { .isEqualTo(1); } + @Test + public void hasDefaultSchemaActionSet() { + + if (isCassandraAvailable()) { + + this.context = new AnnotationConfigApplicationContext(); + String cityPackage = City.class.getPackage().getName(); + AutoConfigurationPackages.register(this.context, cityPackage); + this.context.register(CassandraAutoConfiguration.class, + CassandraDataAutoConfiguration.class); + this.context.refresh(); + + CassandraSessionFactoryBean bean = this.context + .getBean(CassandraSessionFactoryBean.class); + assertThat(bean.getSchemaAction()).isEqualTo(SchemaAction.NONE); + } + } + + @Test + public void hasRecreateSchemaActionSet() { + + if (isCassandraAvailable()) { + createTestKeyspaceIfNotExists(); + + this.context = new AnnotationConfigApplicationContext(); + String cityPackage = City.class.getPackage().getName(); + AutoConfigurationPackages.register(this.context, cityPackage); + + EnvironmentTestUtils.addEnvironment(this.context, + "spring.data.cassandra.schemaAction:RECREATE_DROP_UNUSED", "spring.data.cassandra.keyspaceName:boot_test"); + + this.context.register(CassandraAutoConfiguration.class, + CassandraDataAutoConfiguration.class); + this.context.refresh(); + + CassandraSessionFactoryBean bean = this.context + .getBean(CassandraSessionFactoryBean.class); + assertThat(bean.getSchemaAction()).isEqualTo(SchemaAction.RECREATE_DROP_UNUSED); + } + } + @Test @SuppressWarnings("unchecked") public void entityScanShouldSetInitialEntitySet() throws Exception { @@ -80,6 +129,42 @@ public class CassandraDataAutoConfigurationTests { assertThat(initialEntitySet).containsOnly(City.class); } + /** + * @return {@literal true} if Cassandra is available + */ + private static boolean isCassandraAvailable() { + + Cluster cluster = newCluster(); + try { + cluster.connect().close(); + return true; + } + catch (DriverException exception) { + return false; + } + finally { + cluster.closeAsync(); + } + } + + private static void createTestKeyspaceIfNotExists() { + + Cluster cluster = newCluster(); + try { + Session session = cluster.connect(); + session.execute("CREATE KEYSPACE IF NOT EXISTS boot_test" + + " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"); + session.close(); + } + finally { + cluster.closeAsync(); + } + } + + private static Cluster newCluster() { + return Cluster.builder().addContactPoint("localhost").build(); + } + @Configuration @ComponentScan(excludeFilters = @ComponentScan.Filter(classes = { Session.class }, type = FilterType.ASSIGNABLE_TYPE)) diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraRepositoriesAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraRepositoriesAutoConfigurationTests.java index 8f21ed54211..ea005538688 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraRepositoriesAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraRepositoriesAutoConfigurationTests.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.data.cassandra; +import java.util.Set; + import com.datastax.driver.core.Cluster; import com.datastax.driver.core.Session; import org.junit.After; @@ -34,7 +36,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; +import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext; import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories; +import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -43,6 +47,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link CassandraRepositoriesAutoConfiguration}. * * @author Eddú Meléndez + * @author Mark Paluch */ public class CassandraRepositoriesAutoConfigurationTests { @@ -63,18 +68,39 @@ public class CassandraRepositoriesAutoConfigurationTests { addConfigurations(TestConfiguration.class); assertThat(this.context.getBean(CityRepository.class)).isNotNull(); assertThat(this.context.getBean(Cluster.class)).isNotNull(); + + BasicCassandraMappingContext mappingContext = this.context + .getBean(BasicCassandraMappingContext.class); + @SuppressWarnings("unchecked") + Set> entities = (Set>) ReflectionTestUtils + .getField(mappingContext, "initialEntitySet"); + assertThat(entities).hasSize(1); } @Test public void testNoRepositoryConfiguration() { addConfigurations(TestExcludeConfiguration.class, EmptyConfiguration.class); assertThat(this.context.getBean(Cluster.class)).isNotNull(); + + BasicCassandraMappingContext mappingContext = this.context + .getBean(BasicCassandraMappingContext.class); + @SuppressWarnings("unchecked") + Set> entities = (Set>) ReflectionTestUtils + .getField(mappingContext, "initialEntitySet"); + assertThat(entities).hasSize(1).containsOnly(City.class); } @Test public void doesNotTriggerDefaultRepositoryDetectionIfCustomized() { addConfigurations(TestExcludeConfiguration.class, CustomizedConfiguration.class); assertThat(this.context.getBean(CityCassandraRepository.class)).isNotNull(); + + BasicCassandraMappingContext mappingContext = this.context + .getBean(BasicCassandraMappingContext.class); + @SuppressWarnings("unchecked") + Set> entities = (Set>) ReflectionTestUtils + .getField(mappingContext, "initialEntitySet"); + assertThat(entities).hasSize(1).containsOnly(City.class); } private void addConfigurations(Class... configurations) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraTestServer.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraTestServer.java new file mode 100644 index 00000000000..1762f4a6a2e --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraTestServer.java @@ -0,0 +1,8 @@ +package org.springframework.boot.autoconfigure.data.cassandra; + +/** + * + * @author Stephane Nicoll + */ +public class CassandraTestServer { +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/city/City.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/city/City.java index b785a4ec012..fd9436bedb6 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/city/City.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/city/City.java @@ -16,6 +16,9 @@ package org.springframework.boot.autoconfigure.data.cassandra.city; +import com.datastax.driver.core.DataType.Name; + +import org.springframework.data.cassandra.mapping.CassandraType; import org.springframework.data.cassandra.mapping.Column; import org.springframework.data.cassandra.mapping.PrimaryKey; import org.springframework.data.cassandra.mapping.Table; @@ -24,6 +27,7 @@ import org.springframework.data.cassandra.mapping.Table; public class City { @PrimaryKey + @CassandraType(type = Name.BIGINT) private Long id; @Column diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 3341165842d..9cb3bc07a13 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -548,6 +548,7 @@ content into your application; rather pick only the properties that you need. spring.data.cassandra.reconnection-policy= # Reconnection policy class. spring.data.cassandra.retry-policy= # Class name of the retry policy. spring.data.cassandra.serial-consistency-level= # Queries serial consistency level. + spring.data.cassandra.schema-action= # Action to take at starup. Default: NONE. spring.data.cassandra.ssl=false # Enable SSL support. spring.data.cassandra.username= # Login user of the server.