Polish Inline Caching with Apache Cassandra Integration Tests configuration.

This commit is contained in:
John Blum
2022-07-11 18:40:23 -07:00
parent a410e25f05
commit 5a3c580a47
5 changed files with 90 additions and 79 deletions

View File

@@ -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<String> loadCassandraCqlScripts() {
List<String> cassandraCqlStatements = new ArrayList<>();
cassandraCqlStatements.addAll(loadCassandraSchemaCqlScript());
cassandraCqlStatements.addAll(loadCassandraDataCqlScript());
return cassandraCqlStatements;
protected Resource newCassandraDataCqlScriptResource() {
return new ClassPathResource(CASSANDRA_DATA_CQL);
}
protected List<String> loadCassandraDataCqlScript() {
return readLines(new ClassPathResource(CASSANDRA_DATA_CQL));
protected Resource newCassandraInitCqlScriptResource() {
return new ClassPathResource(CASSANDRA_INIT_CQL);
}
protected List<String> loadCassandraSchemaCqlScript() {
return readLines(new ClassPathResource(CASSANDRA_SCHEMA_CQL));
protected Resource newCassandraSchemaCqlScriptResource() {
return new ClassPathResource(CASSANDRA_SCHEMA_CQL);
}
private @NonNull List<String> 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<String> cqlPredicate() {
Predicate<String> 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<CassandraTemplate> cassandraTemplateConsumer = noopCassandraTemplateConsumer()
.andThen(entityObjectInsertingCassandraTemplateConsumer())
.andThen(entityObjectAssertingCassandraTemplateConsumer());
//.andThen(entityTableNameAssertingCassandraTemplateConsumer())
//.andThen(keyspaceNameAssertingCassandraTemplateConsumer());
Consumer<CassandraTemplate> cassandraTemplateConsumer = noopCassandraTemplateConsumer();
//.andThen(entityObjectInsertingCassandraTemplateConsumer())
//.andThen(entityObjectAssertingCassandraTemplateConsumer());
//.andThen(keyspaceNameAssertingCassandraTemplateConsumer())
//.andThen(tableNameAssertingCassandraTemplateConsumer());
cassandraTemplateConsumer.accept(cassandraTemplate);
}
@@ -164,11 +128,16 @@ public abstract class TestCassandraConfiguration {
};
}
private Consumer<CassandraTemplate> noopCassandraTemplateConsumer() {
return cassandraTemplate -> {};
}
private Consumer<CassandraTemplate> entityCountAssertingCassandraTemplateConsumer() {
return cassandraTemplate -> assertThat(cassandraTemplate.count(Customer.class)).isOne();
}
private Consumer<CassandraTemplate> 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<CassandraTemplate> entityTableNameAssertingCassandraTemplateConsumer() {
return cassandraTemplate ->
assertThat(cassandraTemplate.getTableName(Customer.class).toString()).endsWithIgnoringCase("Customers");
}
private Consumer<CassandraTemplate> 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<CassandraTemplate> noopCassandraTemplateConsumer() {
return cassandraTemplate -> {};
private Consumer<CassandraTemplate> 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<ContextRefreshedEvent> populateCassandraDatabaseUsingRepository(CustomerRepository customerRepository) {
return event -> customerRepository.save(Customer.newCustomer(16L, "Pie Doe"));
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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');

View File

@@ -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);