From 5a3c580a4745be984072ecc94ef2227dd6484c02 Mon Sep 17 00:00:00 2001 From: John Blum Date: Mon, 11 Jul 2022 18:40:23 -0700 Subject: [PATCH] Polish Inline Caching with Apache Cassandra Integration Tests configuration. --- .../config/TestCassandraConfiguration.java | 130 ++++++++---------- .../TestcontainersCassandraConfiguration.java | 32 ++++- ...eCachingWithCassandraIntegrationTests.java | 1 + .../src/test/resources/cassandra-init.cql | 4 + .../src/test/resources/cassandra-schema.cql | 2 +- 5 files changed, 90 insertions(+), 79 deletions(-) create mode 100644 spring-geode-project/spring-geode/src/test/resources/cassandra-init.cql 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 7168fde0..3233d2d5 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 @@ -16,24 +16,18 @@ package example.app.crm.config; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.stream.Collectors; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.session.Session; 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.event.ContextRefreshedEvent; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.data.cassandra.SessionFactory; @@ -42,12 +36,11 @@ import org.springframework.data.cassandra.core.CassandraTemplate; import org.springframework.data.cassandra.core.cql.CqlTemplate; 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 org.springframework.lang.Nullable; -import org.springframework.util.StringUtils; import example.app.crm.model.Customer; +import example.app.crm.repo.CustomerRepository; /** * Base test configuration used to configure and bootstrap an Apache Cassandra database with a schema and data. @@ -68,13 +61,16 @@ import example.app.crm.model.Customer; @SuppressWarnings("unused") public abstract class TestCassandraConfiguration { + private static final boolean CONTINUE_ON_ERROR = false; + private static final boolean IGNORE_FAILED_DROPS = true; + + private static final String CQL_SCRIPT_ENCODING = null; + protected static final int CASSANDRA_DEFAULT_PORT = CqlSessionFactoryBean.DEFAULT_PORT; 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"; - - private static final String COMMENT_LINE_PREFIX = "--"; - protected static final String LOCAL_DATA_CENTER = "datacenter1"; protected static final String KEYSPACE_NAME = "CustomerService"; @@ -83,9 +79,7 @@ public abstract class TestCassandraConfiguration { SessionFactoryInitializer sessionFactoryInitializer = new SessionFactoryInitializer(); - KeyspacePopulator keyspacePopulator = - // cqlSession -> loadCassandraCqlScripts().forEach(cqlSession::execute); - cqlSession -> loadCassandraDataCqlScript().forEach(cqlSession::execute); + KeyspacePopulator keyspacePopulator = newKeyspacePopulator(newCassandraDataCqlScriptResource()); sessionFactoryInitializer.setKeyspacePopulator(keyspacePopulator); sessionFactoryInitializer.setSessionFactory(sessionFactory); @@ -93,51 +87,21 @@ public abstract class TestCassandraConfiguration { return sessionFactoryInitializer; } - protected List loadCassandraCqlScripts() { - - List cassandraCqlStatements = new ArrayList<>(); - - cassandraCqlStatements.addAll(loadCassandraSchemaCqlScript()); - cassandraCqlStatements.addAll(loadCassandraDataCqlScript()); - - return cassandraCqlStatements; + protected Resource newCassandraDataCqlScriptResource() { + return new ClassPathResource(CASSANDRA_DATA_CQL); } - protected List loadCassandraDataCqlScript() { - return readLines(new ClassPathResource(CASSANDRA_DATA_CQL)); + protected Resource newCassandraInitCqlScriptResource() { + return new ClassPathResource(CASSANDRA_INIT_CQL); } - protected List loadCassandraSchemaCqlScript() { - return readLines(new ClassPathResource(CASSANDRA_SCHEMA_CQL)); + protected Resource newCassandraSchemaCqlScriptResource() { + return new ClassPathResource(CASSANDRA_SCHEMA_CQL); } - private @NonNull List readLines(@NonNull Resource resource) { - - try (BufferedReader resourceReader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) { - return resourceReader.lines() - .filter(cqlPredicate()) - .collect(Collectors.toList()); - } - catch (IOException cause) { - throw newRuntimeException(cause, "Failed to read from Resource [%s]", resource); - } - } - - private @NonNull Predicate cqlPredicate() { - - Predicate cqlPredicate = StringUtils::hasText; - - cqlPredicate.and(this::isNotCommentLine); - - return cqlPredicate; - } - - private boolean isCommentLine(@Nullable String line) { - return String.valueOf(line).trim().startsWith(COMMENT_LINE_PREFIX); - } - - private boolean isNotCommentLine(@Nullable String line) { - return !isCommentLine(line); + protected KeyspacePopulator newKeyspacePopulator(Resource... cqlScripts) { + return new ResourceKeyspacePopulator(CONTINUE_ON_ERROR, IGNORE_FAILED_DROPS, CQL_SCRIPT_ENCODING, cqlScripts); + //return cqlScript -> {}; } @Bean @@ -145,16 +109,16 @@ public abstract class TestCassandraConfiguration { return new BeanPostProcessor() { - @org.jetbrains.annotations.Nullable @Override + @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof CassandraTemplate cassandraTemplate) { - Consumer cassandraTemplateConsumer = noopCassandraTemplateConsumer() - .andThen(entityObjectInsertingCassandraTemplateConsumer()) - .andThen(entityObjectAssertingCassandraTemplateConsumer()); - //.andThen(entityTableNameAssertingCassandraTemplateConsumer()) - //.andThen(keyspaceNameAssertingCassandraTemplateConsumer()); + Consumer cassandraTemplateConsumer = noopCassandraTemplateConsumer(); + //.andThen(entityObjectInsertingCassandraTemplateConsumer()) + //.andThen(entityObjectAssertingCassandraTemplateConsumer()); + //.andThen(keyspaceNameAssertingCassandraTemplateConsumer()) + //.andThen(tableNameAssertingCassandraTemplateConsumer()); cassandraTemplateConsumer.accept(cassandraTemplate); } @@ -164,11 +128,16 @@ public abstract class TestCassandraConfiguration { }; } + private Consumer noopCassandraTemplateConsumer() { + return cassandraTemplate -> {}; + } + private Consumer entityCountAssertingCassandraTemplateConsumer() { return cassandraTemplate -> assertThat(cassandraTemplate.count(Customer.class)).isOne(); } private Consumer entityObjectAssertingCassandraTemplateConsumer() { + return cassandraTemplate -> { String cql = "SELECT id, name FROM Customers"; @@ -180,6 +149,9 @@ public abstract class TestCassandraConfiguration { 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); }; } @@ -188,18 +160,11 @@ public abstract class TestCassandraConfiguration { return cassandraTemplate -> cassandraTemplate.insert(Customer.newCustomer(16L, "Pie Doe")); } - private Consumer entityTableNameAssertingCassandraTemplateConsumer() { - - return cassandraTemplate -> - assertThat(cassandraTemplate.getTableName(Customer.class).toString()).endsWithIgnoringCase("Customers"); - } - private Consumer keyspaceNameAssertingCassandraTemplateConsumer() { return cassandraTemplate -> { - String resolvedKeyspaceName = Optional.of(cassandraTemplate) - .map(CassandraTemplate::getCqlOperations) + String resolvedKeyspaceName = Optional.ofNullable(cassandraTemplate.getCqlOperations()) .filter(CqlTemplate.class::isInstance) .map(CqlTemplate.class::cast) .map(CqlTemplate::getSessionFactory) @@ -212,7 +177,28 @@ public abstract class TestCassandraConfiguration { }; } - private Consumer noopCassandraTemplateConsumer() { - return cassandraTemplate -> {}; + private Consumer tableNameAssertingCassandraTemplateConsumer() { + + return cassandraTemplate -> { + + String entityTableName = cassandraTemplate.getTableName(Customer.class).toString(); + + assertThat(entityTableName).endsWithIgnoringCase("Customers"); + + Optional.ofNullable(cassandraTemplate.getCqlOperations()) + .filter(CqlTemplate.class::isInstance) + .map(CqlTemplate.class::cast) + .map(CqlTemplate::getSessionFactory) + .map(SessionFactory::getSession) + .map(Session::getMetadata) + .flatMap(metadata -> metadata.getKeyspace(KEYSPACE_NAME)) + .map(keyspaceMetadata -> keyspaceMetadata.getTable(entityTableName)) + .orElseThrow(() -> new IllegalStateException(String.format("Table [%s] not found", entityTableName))); + }; + } + + @Bean + ApplicationListener populateCassandraDatabaseUsingRepository(CustomerRepository customerRepository) { + return event -> customerRepository.save(Customer.newCustomer(16L, "Pie Doe")); } } 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 9fbadc30..43953417 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 @@ -27,6 +27,7 @@ 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.data.cassandra.core.CassandraTemplate; import org.springframework.lang.NonNull; import org.testcontainers.containers.CassandraContainer; @@ -38,7 +39,10 @@ import example.app.crm.model.Customer; * Spring {@link @Configuration} for Apache Cassandra using Testcontainers. * * @author John Blum + * @see java.net.InetSocketAddress * @see com.datastax.oss.driver.api.core.CqlSession + * @see org.springframework.boot.autoconfigure.cassandra.CqlSessionBuilderCustomizer + * @see org.springframework.boot.autoconfigure.domain.EntityScan * @see org.springframework.context.annotation.Bean * @see org.springframework.context.annotation.Configuration * @see org.springframework.context.annotation.Profile @@ -58,7 +62,7 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura @SuppressWarnings("rawtypes") GenericContainer cassandraContainer() { - GenericContainer cassandraContainer = newCustomCassandraContainer(); + GenericContainer cassandraContainer = newEnvironmentTunedCassandraContainer(); cassandraContainer.start(); @@ -69,12 +73,13 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura 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 newCustomCassandraContainer() { + private @NonNull GenericContainer newEnvironmentTunedCassandraContainer() { return newCassandraContainer() .withEnv("CASSANDRA_SNITCH", "GossipingPropertyFileSnitch") @@ -83,6 +88,10 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura .withEnv("JVM_OPTS", "-Dcassandra.skip_wait_for_gossip_to_settle=0 -Dcassandra.initial_token=0"); } + private @NonNull CassandraTemplate newCassandraTemplate(@NonNull CqlSession session) { + return new CassandraTemplate(session); + } + private @NonNull CqlSession newCqlSession(@NonNull GenericContainer cassandraContainer) { return CqlSession.builder() @@ -93,8 +102,8 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura private @NonNull GenericContainer withCassandraServer(@NonNull GenericContainer cassandraContainer) { - cassandraContainer = initializeCassandraServer(cassandraContainer); - //cassandraContainer = assertCassandraServerSetup(cassandraContainer); + //cassandraContainer = initializeCassandraServer(cassandraContainer); + cassandraContainer = assertCassandraServerSetup(cassandraContainer); return cassandraContainer; } @@ -102,8 +111,7 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura private GenericContainer initializeCassandraServer(GenericContainer cassandraContainer) { try (CqlSession session = newCqlSession(cassandraContainer)) { - //loadCassandraCqlScripts().forEach(session::execute); - loadCassandraSchemaCqlScript().forEach(session::execute); + newKeyspacePopulator(newCassandraSchemaCqlScriptResource()).populate(session); } return cassandraContainer; @@ -120,8 +128,11 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura keyspaceMetadata.getTable("Customers") .map(tableMetadata -> { + assertThat(tableMetadata.getName().toString()).isEqualToIgnoringCase("Customers"); assertThat(tableMetadata.getKeyspace().toString()).isEqualToIgnoringCase(KEYSPACE_NAME); + //assertCustomersTableHasSizeOne(session); + return tableMetadata; }) .orElseThrow(() -> new IllegalStateException("Table [Customers] not found")); @@ -134,6 +145,15 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura return cassandraContainer; } + private void assertCustomersTableHasSizeOne(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.count(Customer.class)).isOne(); // Table Customers not found; needs to use the Keyspace + } + @Bean CqlSessionBuilderCustomizer cqlSessionBuilderCustomizer( @Qualifier("CassandraContainer") GenericContainer cassandraContainer) { diff --git a/spring-geode-project/spring-geode/src/test/java/org/springframework/geode/cache/inline/cassandra/InlineCachingWithCassandraIntegrationTests.java b/spring-geode-project/spring-geode/src/test/java/org/springframework/geode/cache/inline/cassandra/InlineCachingWithCassandraIntegrationTests.java index d9ed1121..071b47e2 100644 --- a/spring-geode-project/spring-geode/src/test/java/org/springframework/geode/cache/inline/cassandra/InlineCachingWithCassandraIntegrationTests.java +++ b/spring-geode-project/spring-geode/src/test/java/org/springframework/geode/cache/inline/cassandra/InlineCachingWithCassandraIntegrationTests.java @@ -59,6 +59,7 @@ import example.app.crm.repo.CustomerRepository; * @see org.springframework.geode.cache.inline.AbstractInlineCachingWithExternalDataSourceIntegrationTests * @see org.springframework.test.context.ActiveProfiles * @see org.springframework.test.context.junit4.SpringRunner + * @see example.app.crm.config.TestcontainersCassandraConfiguration * @since 1.1.0 */ @SpringBootTest 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 new file mode 100644 index 00000000..8349aebb --- /dev/null +++ b/spring-geode-project/spring-geode/src/test/resources/cassandra-init.cql @@ -0,0 +1,4 @@ +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'); 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 f8dfd91c..5e5b3b6c 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 INDEX IF NOT EXISTS CustomerNameIdx ON Customers(name);