Skip to content

Commit f31c877

Browse files
christophstroblmp911de
authored andcommitted
Allow lenient infrastructure startup.
By avoiding interaction with a potential lazy proxy for a given CqlSession until it becomes mandatory we allow users to leverage lazy bean intialization for CqlSession during application startup. To fully enable this changes we need changes in the boot autoconfigration. Closes #380 Original pull request: #1485
1 parent 95f7080 commit f31c877

File tree

4 files changed

+171
-5
lines changed

4 files changed

+171
-5
lines changed

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/SessionFactoryFactoryBean.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ public void afterPropertiesSet() throws Exception {
145145

146146
super.afterPropertiesSet();
147147

148+
if(!shouldRunSchemaAction()) {
149+
return;
150+
}
151+
148152
Runnable schemaActionRunnable = () -> {
149153
if (this.keyspacePopulator != null) {
150154
this.keyspacePopulator.populate(this.session);
@@ -169,6 +173,10 @@ protected SessionFactory createInstance() {
169173
@SuppressWarnings("all")
170174
public void destroy() throws Exception {
171175

176+
if(!shouldRunSchemaAction()) {
177+
return;
178+
}
179+
172180
Runnable schemaActionRunnable = () -> {
173181
if (this.keyspaceCleaner != null) {
174182
this.keyspaceCleaner.populate(this.session);
@@ -230,6 +238,14 @@ protected void createTables(boolean drop, boolean dropUnused, boolean ifNotExist
230238
performSchemaActions(drop, dropUnused, ifNotExists);
231239
}
232240

241+
/**
242+
* @return {@literal true} if schema action or {@link KeyspacePopulator} defined.
243+
* @since 4.3
244+
*/
245+
protected boolean shouldRunSchemaAction() {
246+
return keyspacePopulator != null || SchemaAction.NONE != this.schemaAction;
247+
}
248+
233249
@SuppressWarnings("all")
234250
private void performSchemaActions(boolean drop, boolean dropUnused, boolean ifNotExists) {
235251

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Map.Entry;
2525
import java.util.function.Function;
2626
import java.util.function.Predicate;
27+
import java.util.function.Supplier;
2728

2829
import org.apache.commons.logging.Log;
2930
import org.apache.commons.logging.LogFactory;
@@ -61,6 +62,7 @@
6162
import org.springframework.data.projection.EntityProjection;
6263
import org.springframework.data.projection.ProjectionFactory;
6364
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
65+
import org.springframework.data.util.Lazy;
6466
import org.springframework.data.util.Predicates;
6567
import org.springframework.data.util.TypeInformation;
6668
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -98,7 +100,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
98100

99101
private final CassandraMappingContext mappingContext;
100102

101-
private CodecRegistry codecRegistry;
103+
private Supplier<CodecRegistry> codecRegistry;
102104

103105
private @Nullable UserTypeResolver userTypeResolver;
104106

@@ -234,8 +236,20 @@ public ProjectionFactory getProjectionFactory() {
234236
public void setCodecRegistry(CodecRegistry codecRegistry) {
235237

236238
Assert.notNull(codecRegistry, "CodecRegistry must not be null");
239+
setCodecRegistry(() -> codecRegistry);
240+
}
241+
242+
/**
243+
* Sets the {@link Supplier} used for obtaining the {@link CodecRegistry} to use.
244+
*
245+
* @param codecRegistry must not be {@literal null}.
246+
* @since 4.3
247+
*/
248+
public void setCodecRegistry(Supplier<CodecRegistry> codecRegistry) {
237249

238-
this.codecRegistry = codecRegistry;
250+
Assert.notNull(codecRegistry, "CodecRegistry provider must not be null");
251+
252+
this.codecRegistry = Lazy.of(codecRegistry);
239253
}
240254

241255
/**
@@ -245,8 +259,11 @@ public void setCodecRegistry(CodecRegistry codecRegistry) {
245259
* @since 3.0
246260
*/
247261
@Override
262+
@SuppressWarnings({ "deprecation" })
248263
public CodecRegistry getCodecRegistry() {
249-
return this.codecRegistry != null ? this.codecRegistry : getMappingContext().getCodecRegistry();
264+
265+
CodecRegistry registry = this.codecRegistry != null ? this.codecRegistry.get() : null;
266+
return registry != null ? registry : getMappingContext().getCodecRegistry();
250267
}
251268

252269
/**
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.cassandra.config;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import java.net.InetSocketAddress;
21+
import java.util.Collections;
22+
23+
import com.datastax.oss.driver.api.core.CqlIdentifier;
24+
import com.datastax.oss.driver.api.core.CqlSession;
25+
import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint;
26+
import org.junit.jupiter.api.Test;
27+
import org.springframework.beans.factory.BeanCreationException;
28+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
29+
import org.springframework.context.annotation.Bean;
30+
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.context.annotation.Lazy;
32+
import org.springframework.context.support.GenericApplicationContext;
33+
import org.springframework.data.cassandra.SessionFactory;
34+
import org.springframework.data.cassandra.core.CassandraTemplate;
35+
import org.springframework.data.cassandra.core.Person;
36+
import org.springframework.data.cassandra.core.convert.CassandraConverter;
37+
import org.springframework.data.cassandra.core.convert.CassandraCustomConversions;
38+
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
39+
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
40+
import org.springframework.data.cassandra.core.mapping.SimpleUserTypeResolver;
41+
42+
/**
43+
* @author Christoph Strobl
44+
*/
45+
class LazyStartupConfigurationTest {
46+
47+
@Test // GH-380
48+
void shouldDelayCqlSessionBeanInitializationTillFirstUsage() {
49+
50+
GenericApplicationContext ctx = new AnnotationConfigApplicationContext(LazyStartupConfig.class);
51+
52+
CassandraTemplate template = ctx.getBean(CassandraTemplate.class);
53+
assertThat(template).isNotNull();
54+
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() -> template.count(Person.class));
55+
}
56+
57+
@Configuration
58+
static class LazyStartupConfig {
59+
60+
@Lazy
61+
@Bean
62+
CqlSession cqlSession() {
63+
64+
return CqlSession.builder()
65+
.addContactEndPoint(new DefaultEndPoint(InetSocketAddress.createUnresolved("127.0.0.2", 9042)))
66+
.withKeyspace("system")
67+
.build();
68+
}
69+
70+
@Bean
71+
public SessionFactoryFactoryBean cassandraSessionFactory(CassandraConverter converter, @Lazy CqlSession cqlSession) {
72+
73+
SessionFactoryFactoryBean session = new SessionFactoryFactoryBean();
74+
session.setSession(cqlSession);
75+
session.setConverter(converter);
76+
session.setSchemaAction(SchemaAction.NONE);
77+
return session;
78+
}
79+
80+
@Bean
81+
public CassandraMappingContext cassandraMappingContext() {
82+
CassandraMappingContext context = new CassandraMappingContext();
83+
context.setSimpleTypeHolder(new CassandraCustomConversions(Collections.emptyList()).getSimpleTypeHolder());
84+
return context;
85+
}
86+
87+
@Bean
88+
public CassandraConverter cassandraConverter(CassandraMappingContext mappingContext, @Lazy CqlSession cqlSession) {
89+
90+
MappingCassandraConverter converter = new MappingCassandraConverter(mappingContext);
91+
converter.setCodecRegistry(() -> cqlSession.getContext().getCodecRegistry());
92+
converter.setCustomConversions(mappingContext.getCustomConversions());
93+
94+
CqlIdentifier keyspace = CqlIdentifier.fromCql("system");
95+
converter.setUserTypeResolver(new SimpleUserTypeResolver(cqlSession::getMetadata, keyspace));
96+
return converter;
97+
}
98+
99+
@Bean
100+
public CassandraTemplate cassandraTemplate(SessionFactory sessionFactory, CassandraConverter converter) {
101+
return new CassandraTemplate(sessionFactory, converter);
102+
}
103+
}
104+
}

spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/SchemaActionIntegrationTests.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
* @author Mark Paluch
5050
* @see AbstractKeyspaceCreatingIntegrationTests
5151
*/
52-
class SchemaActionIntegrationTests extends IntegrationTestsSupport {
52+
class SchemaActionIntegrationTests extends AbstractKeyspaceCreatingIntegrationTests {
5353

5454
private static final String CREATE_PERSON_TABLE_CQL = "CREATE TABLE IF NOT EXISTS person (id int, firstName text, lastName text, PRIMARY KEY(id));";
5555

@@ -66,7 +66,7 @@ ConfigurableApplicationContext newApplicationContext(Class<?>... annotatedClasse
6666
protected <T> T doInSessionWithConfiguration(Class<?> annotatedClass, SessionCallback<T> sessionCallback) {
6767

6868
try (ConfigurableApplicationContext applicationContext = newApplicationContext(annotatedClass)) {
69-
return sessionCallback.doInSession(applicationContext.getBean(CqlSession.class));
69+
return sessionCallback.doInSession(getSession());
7070
}
7171
}
7272

@@ -154,6 +154,21 @@ void recreateTableFromEntityDropsExistingTable() {
154154
});
155155
}
156156

157+
@Test // GH-380
158+
void shouldDoNotingIfSchemaActionIsNoneAndNoPopulateScriptPresent() {
159+
160+
doInSessionWithConfiguration(CreateWithoutSchemaAction.class, session -> {
161+
162+
KeyspaceMetadata keyspaceMetadata = session.refreshSchema().getKeyspace(session.getKeyspace().get()).orElse(null);
163+
164+
assertThat(keyspaceMetadata).isNotNull();
165+
assertThat(keyspaceMetadata.getTables()).isEmpty();
166+
167+
return null;
168+
});
169+
170+
}
171+
157172
@Configuration
158173
static class CreateWithNoExistingTableConfiguration extends IntegrationTestConfig {
159174

@@ -168,6 +183,20 @@ protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
168183
}
169184
}
170185

186+
@Configuration
187+
static class CreateWithoutSchemaAction extends IntegrationTestConfig {
188+
189+
@Override
190+
public SchemaAction getSchemaAction() {
191+
return SchemaAction.NONE;
192+
}
193+
194+
@Override
195+
protected KeyspacePopulator keyspacePopulator() {
196+
return null;
197+
}
198+
}
199+
171200
@Configuration
172201
static class CreateWithExistingTableConfiguration extends IntegrationTestConfig {
173202

0 commit comments

Comments
 (0)