Skip to content

Commit 3504f74

Browse files
committed
Fix failing Inline Caching with Apache Cassandra Integration Tests.
1 parent d1618a9 commit 3504f74

12 files changed

+301
-89
lines changed

spring-geode/spring-geode.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ dependencies {
2727
testCompile "org.mockito:mockito-core"
2828
testCompile "org.projectlombok:lombok"
2929
testCompile "org.testcontainers:testcontainers"
30+
testCompile "org.testcontainers:cassandra"
3031
testCompile "edu.umd.cs.mtc:multithreadedtc"
3132

3233
testCompile("org.springframework.boot:spring-boot-starter-test") {

spring-geode/src/test/java/example/app/crm/config/TestCassandraConfiguration.java

Lines changed: 157 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -15,96 +15,198 @@
1515
*/
1616
package example.app.crm.config;
1717

18-
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException;
18+
import static org.assertj.core.api.Assertions.assertThat;
1919

20-
import java.io.BufferedReader;
21-
import java.io.IOException;
22-
import java.io.InputStreamReader;
23-
import java.util.ArrayList;
24-
import java.util.List;
25-
import java.util.stream.Collectors;
20+
import java.util.Optional;
21+
import java.util.function.Consumer;
2622

23+
import com.datastax.oss.driver.api.core.CqlIdentifier;
24+
import com.datastax.oss.driver.api.core.session.Session;
25+
26+
import org.springframework.beans.BeansException;
27+
import org.springframework.beans.factory.config.BeanPostProcessor;
28+
import org.springframework.context.ApplicationListener;
29+
import org.springframework.context.annotation.Bean;
30+
import org.springframework.context.annotation.Profile;
31+
import org.springframework.context.event.ContextRefreshedEvent;
2732
import org.springframework.core.io.ClassPathResource;
2833
import org.springframework.core.io.Resource;
29-
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
34+
import org.springframework.data.cassandra.SessionFactory;
3035
import org.springframework.data.cassandra.config.CqlSessionFactoryBean;
31-
import org.springframework.data.gemfire.tests.util.IOUtils;
36+
import org.springframework.data.cassandra.core.CassandraTemplate;
37+
import org.springframework.data.cassandra.core.cql.CqlTemplate;
38+
import org.springframework.data.cassandra.core.cql.RowMapper;
39+
import org.springframework.data.cassandra.core.cql.session.init.KeyspacePopulator;
40+
import org.springframework.data.cassandra.core.cql.session.init.ResourceKeyspacePopulator;
41+
import org.springframework.data.cassandra.core.cql.session.init.SessionFactoryInitializer;
3242
import org.springframework.lang.NonNull;
33-
import org.springframework.lang.Nullable;
34-
import org.springframework.util.StringUtils;
43+
44+
import example.app.crm.model.Customer;
45+
import example.app.crm.repo.CustomerRepository;
3546

3647
/**
3748
* Base test configuration used to configure and bootstrap an Apache Cassandra database with a schema and data.
3849
*
3950
* @author John Blum
51+
* @see com.datastax.oss.driver.api.core.session.Session
52+
* @see org.springframework.beans.factory.config.BeanPostProcessor
53+
* @see org.springframework.context.annotation.Bean
4054
* @see org.springframework.core.io.Resource
41-
* @see org.springframework.data.cassandra.config.AbstractCassandraConfiguration
55+
* @see org.springframework.data.cassandra.SessionFactory
4256
* @see org.springframework.data.cassandra.config.CqlSessionFactoryBean
57+
* @see org.springframework.data.cassandra.core.CassandraTemplate
58+
* @see org.springframework.data.cassandra.core.cql.CqlTemplate
59+
* @see org.springframework.data.cassandra.core.cql.session.init.KeyspacePopulator
60+
* @see org.springframework.data.cassandra.core.cql.session.init.SessionFactoryInitializer
4361
* @since 1.1.0
4462
*/
45-
public abstract class TestCassandraConfiguration extends AbstractCassandraConfiguration {
63+
@SuppressWarnings("unused")
64+
public abstract class TestCassandraConfiguration {
65+
66+
private static final boolean CONTINUE_ON_ERROR = false;
67+
private static final boolean IGNORE_FAILED_DROPS = true;
68+
69+
private static final Customer pieDoe = Customer.newCustomer(16L, "Pie Doe");
70+
71+
private static final String CQL_SCRIPT_ENCODING = null;
4672

4773
protected static final int CASSANDRA_DEFAULT_PORT = CqlSessionFactoryBean.DEFAULT_PORT;
4874

49-
private static final String CASSANDRA_DATA_CQL = "cassandra-data.cql";
50-
private static final String CASSANDRA_SCHEMA_CQL = "cassandra-schema.cql";
51-
private static final String LOCAL_DATA_CENTER = "datacenter1";
52-
private static final String KEYSPACE_NAME = "CustomerService";
53-
private static final String SESSION_NAME = "CustomerServiceCluster";
75+
protected static final String CASSANDRA_DATA_CQL = "cassandra-data.cql";
76+
protected static final String CASSANDRA_INIT_CQL = "cassandra-init.cql";
77+
protected static final String CASSANDRA_SCHEMA_CQL = "cassandra-schema.cql";
78+
protected static final String DEBUGGING_PROFILE = "debugging";
79+
protected static final String KEYSPACE_NAME = "CustomerService";
80+
protected static final String TABLE_NAME = "Customers";
81+
82+
protected @NonNull Resource newCassandraDataCqlScriptResource() {
83+
return new ClassPathResource(CASSANDRA_DATA_CQL);
84+
}
85+
86+
protected @NonNull Resource newCassandraInitCqlScriptResource() {
87+
return new ClassPathResource(CASSANDRA_INIT_CQL);
88+
}
89+
90+
protected @NonNull Resource newCassandraSchemaCqlScriptResource() {
91+
return new ClassPathResource(CASSANDRA_SCHEMA_CQL);
92+
}
93+
94+
@Bean
95+
SessionFactoryInitializer sessionFactoryInitializer(SessionFactory sessionFactory) {
96+
97+
SessionFactoryInitializer sessionFactoryInitializer = new SessionFactoryInitializer();
98+
99+
KeyspacePopulator keyspacePopulator = newKeyspacePopulator(newCassandraDataCqlScriptResource());
100+
101+
sessionFactoryInitializer.setKeyspacePopulator(keyspacePopulator);
102+
sessionFactoryInitializer.setSessionFactory(sessionFactory);
103+
104+
return sessionFactoryInitializer;
105+
}
106+
107+
protected @NonNull KeyspacePopulator newKeyspacePopulator(Resource... cqlScripts) {
108+
return new ResourceKeyspacePopulator(CONTINUE_ON_ERROR, IGNORE_FAILED_DROPS, CQL_SCRIPT_ENCODING, cqlScripts);
109+
}
110+
111+
@Bean
112+
@Profile(DEBUGGING_PROFILE)
113+
BeanPostProcessor cassandraTemplatePostProcessor() {
114+
115+
return new BeanPostProcessor() {
116+
117+
@Override
118+
public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
119+
120+
if (bean instanceof CassandraTemplate) {
121+
122+
CassandraTemplate cassandraTemplate = (CassandraTemplate) bean;
123+
124+
Consumer<CassandraTemplate> cassandraTemplateConsumer = noopCassandraTemplateConsumer()
125+
.andThen(insertEntityObjectCassandraTemplateConsumer())
126+
.andThen(assertEntityCountCassandraTemplateConsumer())
127+
.andThen(assertEntityObjectCassandraTemplateConsumer())
128+
.andThen(assertKeyspaceNameCassandraTemplateConsumer())
129+
.andThen(assertTableNameCassandraTemplateConsumer());
130+
131+
cassandraTemplateConsumer.accept(cassandraTemplate);
132+
}
133+
134+
return bean;
135+
}
136+
};
137+
}
54138

55-
@NonNull
56-
@Override
57-
protected String getKeyspaceName() {
58-
return KEYSPACE_NAME;
139+
private Consumer<CassandraTemplate> noopCassandraTemplateConsumer() {
140+
return cassandraTemplate -> {};
59141
}
60142

61-
@Override
62-
protected String getLocalDataCenter() {
63-
return LOCAL_DATA_CENTER;
143+
private Consumer<CassandraTemplate> assertEntityCountCassandraTemplateConsumer() {
144+
return cassandraTemplate -> assertThat(cassandraTemplate.count(Customer.class)).isOne();
64145
}
65146

66-
@Nullable
67-
@Override
68-
protected String getSessionName() {
69-
return SESSION_NAME;
147+
private Consumer<CassandraTemplate> assertEntityObjectCassandraTemplateConsumer() {
148+
149+
return cassandraTemplate -> {
150+
151+
String cql = "SELECT id, name FROM \"Customers\"";
152+
153+
RowMapper<Customer> customerRowMapper = (row, rowNumber) ->
154+
Customer.newCustomer(row.getLong("id"), row.getString("name"));
155+
156+
Customer actualCustomer = cassandraTemplate.getCqlOperations().queryForObject(cql, customerRowMapper);
157+
158+
assertThat(actualCustomer).isEqualTo(pieDoe);
159+
assertThat(cassandraTemplate.selectOneById(16L, Customer.class)).isEqualTo(pieDoe);
160+
assertThat(cassandraTemplate.query(Customer.class).stream().findFirst().orElse(null)).isEqualTo(pieDoe);
161+
};
70162
}
71163

72-
/*
73-
@Nullable @Override
74-
protected KeyspacePopulator keyspacePopulator() {
75-
return cqlSession -> loadCassandraCqlScripts().forEach(cqlSession::execute);
164+
private Consumer<CassandraTemplate> insertEntityObjectCassandraTemplateConsumer() {
165+
return cassandraTemplate -> cassandraTemplate.insert(pieDoe);
76166
}
77-
*/
78167

79-
// TODO: Remove use of deprecation after Spring Data for Apache Cassandra issues are resolved!
80-
@Override
81-
protected List<String> getStartupScripts() {
168+
private Consumer<CassandraTemplate> assertKeyspaceNameCassandraTemplateConsumer() {
82169

83-
List<String> startupScripts = new ArrayList<>(super.getStartupScripts());
170+
return cassandraTemplate -> {
84171

85-
startupScripts.addAll(readLines(new ClassPathResource(CASSANDRA_SCHEMA_CQL)));
86-
startupScripts.addAll(readLines(new ClassPathResource(CASSANDRA_DATA_CQL)));
172+
String resolvedKeyspaceName = Optional.of(cassandraTemplate.getCqlOperations())
173+
.filter(CqlTemplate.class::isInstance)
174+
.map(CqlTemplate.class::cast)
175+
.map(CqlTemplate::getSessionFactory)
176+
.map(SessionFactory::getSession)
177+
.flatMap(Session::getKeyspace)
178+
.map(CqlIdentifier::toString)
179+
.orElse(null);
87180

88-
return startupScripts;
181+
assertThat(resolvedKeyspaceName).isEqualToIgnoringCase(KEYSPACE_NAME);
182+
};
89183
}
90184

91-
private @NonNull List<String> readLines(@NonNull Resource resource) {
185+
private Consumer<CassandraTemplate> assertTableNameCassandraTemplateConsumer() {
186+
187+
return cassandraTemplate -> {
92188

93-
BufferedReader resourceReader = null;
189+
String entityTableName = cassandraTemplate.getTableName(Customer.class).toString();
94190

95-
try {
191+
assertThat(entityTableName).endsWith(TABLE_NAME);
192+
193+
Optional.of(cassandraTemplate.getCqlOperations())
194+
.filter(CqlTemplate.class::isInstance)
195+
.map(CqlTemplate.class::cast)
196+
.map(CqlTemplate::getSessionFactory)
197+
.map(SessionFactory::getSession)
198+
.map(Session::getMetadata)
199+
.flatMap(metadata -> metadata.getKeyspace(KEYSPACE_NAME))
200+
.map(keyspaceMetadata -> keyspaceMetadata.getTable(entityTableName))
201+
.orElseThrow(() -> new IllegalStateException(String.format("Table [%s] not found", entityTableName)));
202+
};
203+
}
96204

97-
resourceReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
205+
@Bean
206+
@Profile(DEBUGGING_PROFILE)
207+
ApplicationListener<ContextRefreshedEvent> populateCassandraDatabaseUsingRepository(
208+
CustomerRepository customerRepository) {
98209

99-
return resourceReader.lines()
100-
.filter(StringUtils::hasText)
101-
.collect(Collectors.toList());
102-
}
103-
catch (IOException cause) {
104-
throw newRuntimeException(cause, "Failed to read from Resource [%s]", resource);
105-
}
106-
finally {
107-
IOUtils.close(resourceReader);
108-
}
210+
return event -> customerRepository.save(pieDoe);
109211
}
110212
}

0 commit comments

Comments
 (0)