From 9cdeaf0325f1ee08a32af4f7c8c862f6e6bdf82f Mon Sep 17 00:00:00 2001 From: John Blum Date: Thu, 21 Jul 2022 14:50:51 -0700 Subject: [PATCH] Apply addiitional polishing to the Inline Caching with Apache Cassandra Integration Tests. The refactorings and code changes are based on feedback from Mark Paluch regarding Apache Cassandra and Spring Data for Apache Cassandra. Note: a developer must quote the CqlIdentifier (e.g. table names) when the case is mixed. --- .../config/TestCassandraConfiguration.java | 76 ++++++++++--------- .../TestcontainersCassandraConfiguration.java | 57 ++++++++------ .../java/example/app/crm/model/Customer.java | 7 +- .../src/test/resources/cassandra-data.cql | 2 +- .../src/test/resources/cassandra-init.cql | 7 +- .../src/test/resources/cassandra-schema.cql | 4 +- 6 files changed, 83 insertions(+), 70 deletions(-) diff --git a/spring-geode-project/spring-geode/src/test/java/example/app/crm/config/TestCassandraConfiguration.java b/spring-geode-project/spring-geode/src/test/java/example/app/crm/config/TestCassandraConfiguration.java index 3233d2d5..7335da18 100644 --- a/spring-geode-project/spring-geode/src/test/java/example/app/crm/config/TestCassandraConfiguration.java +++ b/spring-geode-project/spring-geode/src/test/java/example/app/crm/config/TestCassandraConfiguration.java @@ -27,6 +27,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -38,6 +39,7 @@ import org.springframework.data.cassandra.core.cql.RowMapper; import org.springframework.data.cassandra.core.cql.session.init.KeyspacePopulator; import org.springframework.data.cassandra.core.cql.session.init.ResourceKeyspacePopulator; import org.springframework.data.cassandra.core.cql.session.init.SessionFactoryInitializer; +import org.springframework.lang.NonNull; import example.app.crm.model.Customer; import example.app.crm.repo.CustomerRepository; @@ -64,6 +66,8 @@ public abstract class TestCassandraConfiguration { private static final boolean CONTINUE_ON_ERROR = false; private static final boolean IGNORE_FAILED_DROPS = true; + private static final Customer pieDoe = Customer.newCustomer(16L, "Pie Doe"); + private static final String CQL_SCRIPT_ENCODING = null; protected static final int CASSANDRA_DEFAULT_PORT = CqlSessionFactoryBean.DEFAULT_PORT; @@ -71,8 +75,21 @@ public abstract class TestCassandraConfiguration { protected static final String CASSANDRA_DATA_CQL = "cassandra-data.cql"; protected static final String CASSANDRA_INIT_CQL = "cassandra-init.cql"; protected static final String CASSANDRA_SCHEMA_CQL = "cassandra-schema.cql"; - protected static final String LOCAL_DATA_CENTER = "datacenter1"; + protected static final String DEBUGGING_PROFILE = "debugging"; protected static final String KEYSPACE_NAME = "CustomerService"; + protected static final String TABLE_NAME = "Customers"; + + protected @NonNull Resource newCassandraDataCqlScriptResource() { + return new ClassPathResource(CASSANDRA_DATA_CQL); + } + + protected @NonNull Resource newCassandraInitCqlScriptResource() { + return new ClassPathResource(CASSANDRA_INIT_CQL); + } + + protected @NonNull Resource newCassandraSchemaCqlScriptResource() { + return new ClassPathResource(CASSANDRA_SCHEMA_CQL); + } @Bean SessionFactoryInitializer sessionFactoryInitializer(SessionFactory sessionFactory) { @@ -87,24 +104,12 @@ public abstract class TestCassandraConfiguration { return sessionFactoryInitializer; } - protected Resource newCassandraDataCqlScriptResource() { - return new ClassPathResource(CASSANDRA_DATA_CQL); - } - - protected Resource newCassandraInitCqlScriptResource() { - return new ClassPathResource(CASSANDRA_INIT_CQL); - } - - protected Resource newCassandraSchemaCqlScriptResource() { - return new ClassPathResource(CASSANDRA_SCHEMA_CQL); - } - - protected KeyspacePopulator newKeyspacePopulator(Resource... cqlScripts) { + protected @NonNull KeyspacePopulator newKeyspacePopulator(Resource... cqlScripts) { return new ResourceKeyspacePopulator(CONTINUE_ON_ERROR, IGNORE_FAILED_DROPS, CQL_SCRIPT_ENCODING, cqlScripts); - //return cqlScript -> {}; } @Bean + @Profile(DEBUGGING_PROFILE) BeanPostProcessor cassandraTemplatePostProcessor() { return new BeanPostProcessor() { @@ -114,11 +119,12 @@ public abstract class TestCassandraConfiguration { if (bean instanceof CassandraTemplate cassandraTemplate) { - Consumer cassandraTemplateConsumer = noopCassandraTemplateConsumer(); - //.andThen(entityObjectInsertingCassandraTemplateConsumer()) - //.andThen(entityObjectAssertingCassandraTemplateConsumer()); - //.andThen(keyspaceNameAssertingCassandraTemplateConsumer()) - //.andThen(tableNameAssertingCassandraTemplateConsumer()); + Consumer cassandraTemplateConsumer = noopCassandraTemplateConsumer() + .andThen(insertEntityObjectCassandraTemplateConsumer()) + .andThen(assertEntityCountCassandraTemplateConsumer()) + .andThen(assertEntityObjectCassandraTemplateConsumer()) + .andThen(assertKeyspaceNameCassandraTemplateConsumer()) + .andThen(assertTableNameCassandraTemplateConsumer()); cassandraTemplateConsumer.accept(cassandraTemplate); } @@ -132,35 +138,32 @@ public abstract class TestCassandraConfiguration { return cassandraTemplate -> {}; } - private Consumer entityCountAssertingCassandraTemplateConsumer() { + private Consumer assertEntityCountCassandraTemplateConsumer() { return cassandraTemplate -> assertThat(cassandraTemplate.count(Customer.class)).isOne(); } - private Consumer entityObjectAssertingCassandraTemplateConsumer() { + private Consumer assertEntityObjectCassandraTemplateConsumer() { return cassandraTemplate -> { - String cql = "SELECT id, name FROM Customers"; + String cql = "SELECT id, name FROM \"Customers\""; RowMapper customerRowMapper = (row, rowNumber) -> Customer.newCustomer(row.getLong("id"), row.getString("name")); Customer actualCustomer = cassandraTemplate.getCqlOperations().queryForObject(cql, customerRowMapper); - Customer expectedCustomer = Customer.newCustomer(16L, "Pie Doe"); - assertThat(actualCustomer).isEqualTo(expectedCustomer); - //assertThat(cassandraTemplate.selectOneById(16L, Customer.class)).isEqualTo(expectedCustomer); - //assertThat(cassandraTemplate.query(Customer.class).stream().findFirst().orElse(null)) - // .isEqualTo(expectedCustomer); + assertThat(actualCustomer).isEqualTo(pieDoe); + assertThat(cassandraTemplate.selectOneById(16L, Customer.class)).isEqualTo(pieDoe); + assertThat(cassandraTemplate.query(Customer.class).stream().findFirst().orElse(null)).isEqualTo(pieDoe); }; } - // TODO: Why does this work and the CQL data script not work! - private Consumer entityObjectInsertingCassandraTemplateConsumer() { - return cassandraTemplate -> cassandraTemplate.insert(Customer.newCustomer(16L, "Pie Doe")); + private Consumer insertEntityObjectCassandraTemplateConsumer() { + return cassandraTemplate -> cassandraTemplate.insert(pieDoe); } - private Consumer keyspaceNameAssertingCassandraTemplateConsumer() { + private Consumer assertKeyspaceNameCassandraTemplateConsumer() { return cassandraTemplate -> { @@ -177,7 +180,7 @@ public abstract class TestCassandraConfiguration { }; } - private Consumer tableNameAssertingCassandraTemplateConsumer() { + private Consumer assertTableNameCassandraTemplateConsumer() { return cassandraTemplate -> { @@ -198,7 +201,10 @@ public abstract class TestCassandraConfiguration { } @Bean - ApplicationListener populateCassandraDatabaseUsingRepository(CustomerRepository customerRepository) { - return event -> customerRepository.save(Customer.newCustomer(16L, "Pie Doe")); + @Profile(DEBUGGING_PROFILE) + ApplicationListener populateCassandraDatabaseUsingRepository( + CustomerRepository customerRepository) { + + return event -> customerRepository.save(pieDoe); } } diff --git a/spring-geode-project/spring-geode/src/test/java/example/app/crm/config/TestcontainersCassandraConfiguration.java b/spring-geode-project/spring-geode/src/test/java/example/app/crm/config/TestcontainersCassandraConfiguration.java index 43953417..336acc75 100644 --- a/spring-geode-project/spring-geode/src/test/java/example/app/crm/config/TestcontainersCassandraConfiguration.java +++ b/spring-geode-project/spring-geode/src/test/java/example/app/crm/config/TestcontainersCassandraConfiguration.java @@ -18,15 +18,18 @@ package example.app.crm.config; import static org.assertj.core.api.Assertions.assertThat; import java.net.InetSocketAddress; +import java.util.Arrays; import com.datastax.oss.driver.api.core.CqlSession; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.cassandra.CassandraProperties; import org.springframework.boot.autoconfigure.cassandra.CqlSessionBuilderCustomizer; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; import org.springframework.data.cassandra.core.CassandraTemplate; import org.springframework.lang.NonNull; @@ -57,29 +60,29 @@ import example.app.crm.model.Customer; public class TestcontainersCassandraConfiguration extends TestCassandraConfiguration { private static final String CASSANDRA_DOCKER_IMAGE_NAME = "cassandra:latest"; + private static final String LOCAL_DATACENTER_NAME = "datacenter1"; @Bean("CassandraContainer") - @SuppressWarnings("rawtypes") - GenericContainer cassandraContainer() { + GenericContainer cassandraContainer(Environment environment) { - GenericContainer cassandraContainer = newEnvironmentTunedCassandraContainer(); + GenericContainer cassandraContainer = newEnvironmentOptimizedCassandraContainer(); cassandraContainer.start(); - return withCassandraServer(cassandraContainer); + return withCassandraServer(cassandraContainer, environment); } - @SuppressWarnings("rawtypes") - private @NonNull GenericContainer newCassandraContainer() { - return new CassandraContainer(CASSANDRA_DOCKER_IMAGE_NAME) + private @NonNull GenericContainer newCassandraContainer() { + + return new CassandraContainer<>(CASSANDRA_DOCKER_IMAGE_NAME) .withInitScript(CASSANDRA_SCHEMA_CQL) //.withInitScript(CASSANDRA_INIT_CQL) .withExposedPorts(CASSANDRA_DEFAULT_PORT) .withReuse(true); } - @SuppressWarnings("rawtypes") - private @NonNull GenericContainer newEnvironmentTunedCassandraContainer() { + // Information (feedback) received from Sergei Egorov. + private @NonNull GenericContainer newEnvironmentOptimizedCassandraContainer() { return newCassandraContainer() .withEnv("CASSANDRA_SNITCH", "GossipingPropertyFileSnitch") @@ -96,19 +99,22 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura return CqlSession.builder() .addContactPoint(resolveContactPoint(cassandraContainer)) - .withLocalDatacenter(LOCAL_DATA_CENTER) + .withLocalDatacenter(LOCAL_DATACENTER_NAME) .build(); } - private @NonNull GenericContainer withCassandraServer(@NonNull GenericContainer cassandraContainer) { + private @NonNull GenericContainer withCassandraServer(@NonNull GenericContainer cassandraContainer, + @NonNull Environment environment) { - //cassandraContainer = initializeCassandraServer(cassandraContainer); - cassandraContainer = assertCassandraServerSetup(cassandraContainer); + if (Arrays.asList(environment.getActiveProfiles()).contains(DEBUGGING_PROFILE)) { + cassandraContainer = initializeCassandraServer(cassandraContainer); + cassandraContainer = assertCassandraServerSetup(cassandraContainer); + } return cassandraContainer; } - private GenericContainer initializeCassandraServer(GenericContainer cassandraContainer) { + private @NonNull GenericContainer initializeCassandraServer(@NonNull GenericContainer cassandraContainer) { try (CqlSession session = newCqlSession(cassandraContainer)) { newKeyspacePopulator(newCassandraSchemaCqlScriptResource()).populate(session); @@ -117,7 +123,7 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura return cassandraContainer; } - private GenericContainer assertCassandraServerSetup(GenericContainer cassandraContainer) { + private @NonNull GenericContainer assertCassandraServerSetup(@NonNull GenericContainer cassandraContainer) { try (CqlSession session = newCqlSession(cassandraContainer)) { @@ -126,16 +132,16 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura assertThat(keyspaceMetadata.getName().toString()).isEqualToIgnoringCase(KEYSPACE_NAME); - keyspaceMetadata.getTable("Customers") + keyspaceMetadata.getTable(TABLE_NAME) .map(tableMetadata -> { - assertThat(tableMetadata.getName().toString()).isEqualToIgnoringCase("Customers"); + assertThat(tableMetadata.getName().toString()).isEqualTo(TABLE_NAME); assertThat(tableMetadata.getKeyspace().toString()).isEqualToIgnoringCase(KEYSPACE_NAME); //assertCustomersTableHasSizeOne(session); return tableMetadata; }) - .orElseThrow(() -> new IllegalStateException("Table [Customers] not found")); + .orElseThrow(() -> new IllegalStateException(String.format("Table [%s] not found", TABLE_NAME))); return keyspaceMetadata; }) @@ -145,25 +151,26 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura return cassandraContainer; } - private void assertCustomersTableHasSizeOne(CqlSession session) { + private void assertCustomersTableHasSizeOne(@NonNull CqlSession session) { CassandraTemplate template = newCassandraTemplate(session); assertThat(template.getCqlOperations().execute(String.format("USE %s;", KEYSPACE_NAME))).isTrue(); - assertThat(template.getCqlOperations().queryForObject("SELECT count(*) FROM Customers", Long.class)).isOne(); + assertThat(template.getCqlOperations().queryForObject("SELECT count(*) FROM \"Customers\"", Long.class)).isOne(); //assertThat(template.count(Customer.class)).isOne(); // Table Customers not found; needs to use the Keyspace } @Bean - CqlSessionBuilderCustomizer cqlSessionBuilderCustomizer( + CqlSessionBuilderCustomizer cqlSessionBuilderCustomizer(CassandraProperties properties, @Qualifier("CassandraContainer") GenericContainer cassandraContainer) { - return cqlSessionBuilder -> cqlSessionBuilder.addContactPoint(resolveContactPoint(cassandraContainer)) - .withLocalDatacenter(LOCAL_DATA_CENTER) - .withKeyspace(KEYSPACE_NAME); + return cqlSessionBuilder -> cqlSessionBuilder + .addContactPoint(resolveContactPoint(cassandraContainer)) + .withLocalDatacenter(properties.getLocalDatacenter()) + .withKeyspace(properties.getKeyspaceName()); } - private InetSocketAddress resolveContactPoint(GenericContainer cassandraContainer) { + private @NonNull InetSocketAddress resolveContactPoint(@NonNull GenericContainer cassandraContainer) { return new InetSocketAddress(cassandraContainer.getHost(), cassandraContainer.getMappedPort(CASSANDRA_DEFAULT_PORT)); } diff --git a/spring-geode-project/spring-geode/src/test/java/example/app/crm/model/Customer.java b/spring-geode-project/spring-geode/src/test/java/example/app/crm/model/Customer.java index 249bc0bc..36dcdd80 100644 --- a/spring-geode-project/spring-geode/src/test/java/example/app/crm/model/Customer.java +++ b/spring-geode-project/spring-geode/src/test/java/example/app/crm/model/Customer.java @@ -18,7 +18,6 @@ package example.app.crm.model; import jakarta.persistence.Entity; import jakarta.persistence.Table; -import org.springframework.data.annotation.Id; import org.springframework.data.cassandra.core.mapping.Indexed; import org.springframework.data.cassandra.core.mapping.PrimaryKey; import org.springframework.data.gemfire.mapping.annotation.Region; @@ -34,7 +33,8 @@ import lombok.NoArgsConstructor; * @author John Blum * @see jakarta.persistence.Entity * @see jakarta.persistence.Table - * @see org.springframework.data.annotation.Id + * @see org.springframework.data.cassandra.core.mapping.Indexed + * @see org.springframework.data.cassandra.core.mapping.PrimaryKey * @see org.springframework.data.cassandra.core.mapping.Table * @see org.springframework.data.gemfire.mapping.annotation.Region * @since 1.1.0 @@ -48,9 +48,8 @@ import lombok.NoArgsConstructor; @AllArgsConstructor(staticName = "newCustomer") public class Customer { - @Id - @jakarta.persistence.Id @PrimaryKey + @jakarta.persistence.Id private Long id; @Indexed diff --git a/spring-geode-project/spring-geode/src/test/resources/cassandra-data.cql b/spring-geode-project/spring-geode/src/test/resources/cassandra-data.cql index 732dfb2f..2b682ebb 100644 --- a/spring-geode-project/spring-geode/src/test/resources/cassandra-data.cql +++ b/spring-geode-project/spring-geode/src/test/resources/cassandra-data.cql @@ -1 +1 @@ -INSERT INTO Customers (id, name) VALUES (16, 'Pie Doe'); +INSERT INTO "Customers" (id, name) VALUES (16, 'Pie Doe'); diff --git a/spring-geode-project/spring-geode/src/test/resources/cassandra-init.cql b/spring-geode-project/spring-geode/src/test/resources/cassandra-init.cql index 8349aebb..c0f63a65 100644 --- a/spring-geode-project/spring-geode/src/test/resources/cassandra-init.cql +++ b/spring-geode-project/spring-geode/src/test/resources/cassandra-init.cql @@ -1,4 +1,5 @@ CREATE KEYSPACE IF NOT EXISTS CustomerService WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1 }; -CREATE TABLE IF NOT EXISTS CustomerService.Customers (id BIGINT PRIMARY KEY, name TEXT); -CREATE INDEX IF NOT EXISTS CustomerNameIdx ON CustomerService.Customers(name); -INSERT INTO CustomerService.Customers (id, name) VALUES (16, 'Pie Doe'); +USE CustomerService; +CREATE TABLE IF NOT EXISTS "Customers" (id BIGINT PRIMARY KEY, name TEXT); +CREATE INDEX IF NOT EXISTS CustomerNameIdx ON "Customers"(name); +INSERT INTO "Customers" (id, name) VALUES (16, 'Pie Doe'); diff --git a/spring-geode-project/spring-geode/src/test/resources/cassandra-schema.cql b/spring-geode-project/spring-geode/src/test/resources/cassandra-schema.cql index 5e5b3b6c..80d7a04e 100644 --- a/spring-geode-project/spring-geode/src/test/resources/cassandra-schema.cql +++ b/spring-geode-project/spring-geode/src/test/resources/cassandra-schema.cql @@ -1,4 +1,4 @@ CREATE KEYSPACE IF NOT EXISTS CustomerService WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1 }; USE CustomerService; -CREATE TABLE IF NOT EXISTS Customers (id BIGINT PRIMARY KEY, name TEXT); -CREATE INDEX IF NOT EXISTS CustomerNameIdx ON Customers(name); +CREATE TABLE IF NOT EXISTS "Customers" (id BIGINT PRIMARY KEY, name TEXT); +CREATE INDEX IF NOT EXISTS CustomerNameIdx ON "Customers"(name);