Skip to content

Commit 789be0d

Browse files
committed
Improve CassandraTemplate creation from Session.
CassandraTemplate and its reactive and asynchronous variants created with a bare Session now picks up UserTypeResolver and CodecRegistry configured at the Session level. ReactiveSession now also exposes the logged keyspace and metadata. Closes #1133
1 parent 1175496 commit 789be0d

File tree

11 files changed

+148
-25
lines changed

11 files changed

+148
-25
lines changed

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/ReactiveSession.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,17 @@
1919

2020
import java.io.Closeable;
2121
import java.util.Map;
22+
import java.util.Optional;
2223

24+
import com.datastax.oss.driver.api.core.CqlIdentifier;
25+
import com.datastax.oss.driver.api.core.CqlSessionBuilder;
2326
import com.datastax.oss.driver.api.core.context.DriverContext;
2427
import com.datastax.oss.driver.api.core.cql.BoundStatement;
2528
import com.datastax.oss.driver.api.core.cql.PreparedStatement;
2629
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
2730
import com.datastax.oss.driver.api.core.cql.Statement;
31+
import com.datastax.oss.driver.api.core.metadata.Metadata;
32+
import com.datastax.oss.driver.api.core.metadata.Node;
2833

2934
/**
3035
* A session holds connections to a Cassandra cluster, allowing it to be queried. {@link ReactiveSession} executes
@@ -49,6 +54,40 @@
4954
*/
5055
public interface ReactiveSession extends Closeable {
5156

57+
/**
58+
* Returns a snapshot of the Cassandra cluster's topology and schema metadata.
59+
* <p/>
60+
* In order to provide atomic updates, this method returns an immutable object: the node list, token map, and schema
61+
* contained in a given instance will always be consistent with each other (but note that {@link Node} itself is not
62+
* immutable: some of its properties will be updated dynamically, in particular {@link Node#getState()}).
63+
* <p/>
64+
* As a consequence of the above, you should call this method each time you need a fresh view of the metadata. <b>Do
65+
* not</b> call it once and store the result, because it is a frozen snapshot that will become stale over time.
66+
* <p>
67+
* If a metadata refresh triggers events (such as node added/removed, or schema events), then the new version of the
68+
* metadata is guaranteed to be visible by the time you receive these events.
69+
* <p>
70+
*
71+
* @return never {@code null}, but may be empty if metadata has been disabled in the configuration.
72+
* @since 3.2.2
73+
*/
74+
Metadata getMetadata();
75+
76+
/**
77+
* The keyspace that this session is currently connected to, or {@link Optional#empty()} if this session is not
78+
* connected to any keyspace.
79+
* <p/>
80+
* There are two ways that this can be set: before initializing the session (either with the {@code session-keyspace}
81+
* option in the configuration, or with {@link CqlSessionBuilder#withKeyspace(CqlIdentifier)}); or at runtime, if the
82+
* client issues a request that changes the keyspace (such as a CQL {@code USE} query). Note that this second method
83+
* is inherently unsafe, since other requests expecting the old keyspace might be executing concurrently. Therefore it
84+
* is highly discouraged, aside from trivial cases (such as a cqlsh-style program where requests are never
85+
* concurrent).
86+
*
87+
* @since 3.2.2
88+
*/
89+
Optional<CqlIdentifier> getKeyspace();
90+
5291
/**
5392
* Whether this Session instance has been closed.
5493
* <p/>

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,15 @@ public abstract class AbstractCassandraConfiguration extends AbstractSessionConf
6969
@Bean
7070
public CassandraConverter cassandraConverter() {
7171

72+
CqlSession cqlSession = getRequiredSession();
73+
7274
UserTypeResolver userTypeResolver =
73-
new SimpleUserTypeResolver(getRequiredSession(), CqlIdentifier.fromCql(getKeyspaceName()));
75+
new SimpleUserTypeResolver(cqlSession, CqlIdentifier.fromCql(getKeyspaceName()));
7476

7577
MappingCassandraConverter converter =
7678
new MappingCassandraConverter(requireBeanOfType(CassandraMappingContext.class));
7779

78-
converter.setCodecRegistry(getRequiredSession().getContext().getCodecRegistry());
80+
converter.setCodecRegistry(cqlSession.getContext().getCodecRegistry());
7981
converter.setUserTypeResolver(userTypeResolver);
8082
converter.setCustomConversions(requireBeanOfType(CassandraCustomConversions.class));
8183

@@ -92,8 +94,10 @@ public CassandraConverter cassandraConverter() {
9294
@Bean
9395
public CassandraMappingContext cassandraMapping() throws ClassNotFoundException {
9496

97+
CqlSession cqlSession = getRequiredSession();
98+
9599
UserTypeResolver userTypeResolver =
96-
new SimpleUserTypeResolver(getRequiredSession(), CqlIdentifier.fromCql(getKeyspaceName()));
100+
new SimpleUserTypeResolver(cqlSession, CqlIdentifier.fromCql(getKeyspaceName()));
97101

98102
CassandraMappingContext mappingContext =
99103
new CassandraMappingContext(userTypeResolver, SimpleTupleTypeFactory.DEFAULT);
@@ -102,7 +106,7 @@ public CassandraMappingContext cassandraMapping() throws ClassNotFoundException
102106

103107
getBeanClassLoader().ifPresent(mappingContext::setBeanClassLoader);
104108

105-
mappingContext.setCodecRegistry(getRequiredSession().getContext().getCodecRegistry());
109+
mappingContext.setCodecRegistry(cqlSession.getContext().getCodecRegistry());
106110
mappingContext.setCustomConversions(customConversions);
107111
mappingContext.setInitialEntitySet(getInitialEntitySet());
108112
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.data.cassandra.core.cql.util.CassandraFutureAdapter;
4343
import org.springframework.data.cassandra.core.cql.util.StatementBuilder;
4444
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
45+
import org.springframework.data.cassandra.core.mapping.SimpleUserTypeResolver;
4546
import org.springframework.data.cassandra.core.mapping.event.AfterConvertEvent;
4647
import org.springframework.data.cassandra.core.mapping.event.AfterDeleteEvent;
4748
import org.springframework.data.cassandra.core.mapping.event.AfterLoadEvent;
@@ -137,7 +138,7 @@ public class AsyncCassandraTemplate
137138
* @see Session
138139
*/
139140
public AsyncCassandraTemplate(CqlSession session) {
140-
this(session, newConverter());
141+
this(session, newConverter(session));
141142
}
142143

143144
/**
@@ -963,9 +964,11 @@ private Class<?> resolveTypeToRead(Class<?> entityType, Class<?> targetType) {
963964
return targetType.isInterface() || targetType.isAssignableFrom(entityType) ? entityType : targetType;
964965
}
965966

966-
private static MappingCassandraConverter newConverter() {
967+
private static MappingCassandraConverter newConverter(CqlSession session) {
967968

968969
MappingCassandraConverter converter = new MappingCassandraConverter();
970+
converter.setUserTypeResolver(new SimpleUserTypeResolver(session));
971+
converter.setCodecRegistry(session.getContext().getCodecRegistry());
969972

970973
converter.afterPropertiesSet();
971974

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.springframework.data.cassandra.core.cql.session.DefaultSessionFactory;
5151
import org.springframework.data.cassandra.core.cql.util.StatementBuilder;
5252
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
53+
import org.springframework.data.cassandra.core.mapping.SimpleUserTypeResolver;
5354
import org.springframework.data.cassandra.core.mapping.event.AfterConvertEvent;
5455
import org.springframework.data.cassandra.core.mapping.event.AfterDeleteEvent;
5556
import org.springframework.data.cassandra.core.mapping.event.AfterLoadEvent;
@@ -140,7 +141,7 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP
140141
* @see Session
141142
*/
142143
public CassandraTemplate(CqlSession session) {
143-
this(session, newConverter());
144+
this(session, newConverter(session));
144145
}
145146

146147
/**
@@ -1017,9 +1018,11 @@ private Class<?> resolveTypeToRead(Class<?> entityType, Class<?> targetType) {
10171018
return targetType.isInterface() || targetType.isAssignableFrom(entityType) ? entityType : targetType;
10181019
}
10191020

1020-
private static MappingCassandraConverter newConverter() {
1021+
private static MappingCassandraConverter newConverter(CqlSession session) {
10211022

10221023
MappingCassandraConverter converter = new MappingCassandraConverter();
1024+
converter.setUserTypeResolver(new SimpleUserTypeResolver(session));
1025+
converter.setCodecRegistry(session.getContext().getCodecRegistry());
10231026

10241027
converter.afterPropertiesSet();
10251028

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.springframework.data.cassandra.core.cql.session.DefaultReactiveSessionFactory;
4545
import org.springframework.data.cassandra.core.cql.util.StatementBuilder;
4646
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
47+
import org.springframework.data.cassandra.core.mapping.SimpleUserTypeResolver;
4748
import org.springframework.data.cassandra.core.mapping.event.AfterConvertEvent;
4849
import org.springframework.data.cassandra.core.mapping.event.AfterDeleteEvent;
4950
import org.springframework.data.cassandra.core.mapping.event.AfterLoadEvent;
@@ -136,7 +137,7 @@ public class ReactiveCassandraTemplate
136137
* @see Session
137138
*/
138139
public ReactiveCassandraTemplate(ReactiveSession session) {
139-
this(session, newConverter());
140+
this(session, newConverter(session));
140141
}
141142

142143
/**
@@ -969,9 +970,12 @@ private Class<?> resolveTypeToRead(Class<?> entityType, Class<?> targetType) {
969970
return targetType.isInterface() || targetType.isAssignableFrom(entityType) ? entityType : targetType;
970971
}
971972

972-
private static MappingCassandraConverter newConverter() {
973+
private static MappingCassandraConverter newConverter(ReactiveSession session) {
973974

974975
MappingCassandraConverter converter = new MappingCassandraConverter();
976+
converter.setUserTypeResolver(new SimpleUserTypeResolver(session::getMetadata,
977+
session.getKeyspace().orElse(CqlIdentifier.fromCql("system"))));
978+
converter.setCodecRegistry(session.getContext().getCodecRegistry());
975979

976980
converter.afterPropertiesSet();
977981

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
8787

8888
private CodecRegistry codecRegistry;
8989

90-
private UserTypeResolver userTypeResolver;
90+
private @Nullable UserTypeResolver userTypeResolver;
9191

9292
private @Nullable ClassLoader beanClassLoader;
9393

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/cql/session/DefaultBridgedReactiveSession.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Collections;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.Optional;
2627
import java.util.concurrent.CompletionStage;
2728

2829
import org.slf4j.Logger;
@@ -32,6 +33,7 @@
3233
import org.springframework.data.cassandra.ReactiveSession;
3334
import org.springframework.util.Assert;
3435

36+
import com.datastax.oss.driver.api.core.CqlIdentifier;
3537
import com.datastax.oss.driver.api.core.CqlSession;
3638
import com.datastax.oss.driver.api.core.context.DriverContext;
3739
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
@@ -44,6 +46,7 @@
4446
import com.datastax.oss.driver.api.core.cql.Row;
4547
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
4648
import com.datastax.oss.driver.api.core.cql.Statement;
49+
import com.datastax.oss.driver.api.core.metadata.Metadata;
4750

4851
/**
4952
* Default implementation of a {@link ReactiveSession}. This implementation bridges asynchronous {@link CqlSession}
@@ -87,6 +90,22 @@ public DefaultBridgedReactiveSession(CqlSession session) {
8790
this.session = session;
8891
}
8992

93+
/* (non-Javadoc)
94+
* @see org.springframework.data.cassandra.ReactiveSession#getMetadata()
95+
*/
96+
@Override
97+
public Metadata getMetadata() {
98+
return this.session.getMetadata();
99+
}
100+
101+
/* (non-Javadoc)
102+
* @see org.springframework.data.cassandra.ReactiveSession#getKeyspace()
103+
*/
104+
@Override
105+
public Optional<CqlIdentifier> getKeyspace() {
106+
return this.session.getKeyspace();
107+
}
108+
90109
/* (non-Javadoc)
91110
* @see org.springframework.data.cassandra.ReactiveSession#isClosed()
92111
*/

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/SimpleUserTypeResolver.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.cassandra.core.mapping;
1717

18+
import java.util.function.Supplier;
19+
1820
import org.springframework.lang.Nullable;
1921
import org.springframework.util.Assert;
2022

@@ -32,7 +34,7 @@
3234
*/
3335
public class SimpleUserTypeResolver implements UserTypeResolver {
3436

35-
private final CqlSession session;
37+
private final Supplier<Metadata> metadataSupplier;
3638

3739
private final CqlIdentifier keyspaceName;
3840

@@ -46,7 +48,7 @@ public SimpleUserTypeResolver(CqlSession session) {
4648

4749
Assert.notNull(session, "Session must not be null");
4850

49-
this.session = session;
51+
this.metadataSupplier = session::getMetadata;
5052
this.keyspaceName = session.getKeyspace().orElse(CqlIdentifier.fromCql("system"));
5153
}
5254

@@ -62,7 +64,23 @@ public SimpleUserTypeResolver(CqlSession session, CqlIdentifier keyspaceName) {
6264
Assert.notNull(session, "Session must not be null");
6365
Assert.notNull(keyspaceName, "Keyspace must not be null");
6466

65-
this.session = session;
67+
this.metadataSupplier = session::getMetadata;
68+
this.keyspaceName = keyspaceName;
69+
}
70+
71+
/**
72+
* Create a new {@link SimpleUserTypeResolver}.
73+
*
74+
* @param metadataSupplier must not be {@literal null}.
75+
* @param keyspaceName must not be {@literal null}.
76+
* @since 3.2.2
77+
*/
78+
public SimpleUserTypeResolver(Supplier<Metadata> metadataSupplier, CqlIdentifier keyspaceName) {
79+
80+
Assert.notNull(metadataSupplier, "Metadata supplier must not be null");
81+
Assert.notNull(keyspaceName, "Keyspace must not be null");
82+
83+
this.metadataSupplier = metadataSupplier;
6684
this.keyspaceName = keyspaceName;
6785
}
6886

@@ -72,7 +90,7 @@ public SimpleUserTypeResolver(CqlSession session, CqlIdentifier keyspaceName) {
7290
@Nullable
7391
@Override
7492
public UserDefinedType resolveType(CqlIdentifier typeName) {
75-
return session.getMetadata().getKeyspace(keyspaceName) //
93+
return metadataSupplier.get().getKeyspace(keyspaceName) //
7694
.flatMap(it -> it.getUserDefinedType(typeName)) //
7795
.orElse(null);
7896
}

spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateUnitTests.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import com.datastax.oss.driver.api.core.CqlIdentifier;
5252
import com.datastax.oss.driver.api.core.CqlSession;
5353
import com.datastax.oss.driver.api.core.NoNodeAvailableException;
54+
import com.datastax.oss.driver.api.core.context.DriverContext;
5455
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
5556
import com.datastax.oss.driver.api.core.cql.ColumnDefinition;
5657
import com.datastax.oss.driver.api.core.cql.ColumnDefinitions;
@@ -59,6 +60,7 @@
5960
import com.datastax.oss.driver.api.core.cql.Statement;
6061
import com.datastax.oss.driver.api.core.type.DataTypes;
6162
import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
63+
import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry;
6264

6365
/**
6466
* Unit tests for {@link AsyncCassandraTemplate}.
@@ -67,9 +69,11 @@
6769
*/
6870
@ExtendWith(MockitoExtension.class)
6971
@MockitoSettings(strictness = Strictness.LENIENT)
70-
public class AsyncCassandraTemplateUnitTests {
72+
class AsyncCassandraTemplateUnitTests {
7173

7274
@Mock CqlSession session;
75+
CodecRegistry codecRegistry = new DefaultCodecRegistry("foo");
76+
@Mock DriverContext driverContext;
7377
@Mock AsyncResultSet resultSet;
7478
@Mock Row row;
7579
@Mock ColumnDefinition columnDefinition;
@@ -86,8 +90,8 @@ public class AsyncCassandraTemplateUnitTests {
8690
@BeforeEach
8791
void setUp() {
8892

89-
template = new AsyncCassandraTemplate(session);
90-
template.setUsePreparedStatements(false);
93+
when(driverContext.getCodecRegistry()).thenReturn(codecRegistry);
94+
when(session.getContext()).thenReturn(driverContext);
9195

9296
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
9397
when(row.getColumnDefinitions()).thenReturn(columnDefinitions);
@@ -108,9 +112,17 @@ void setUp() {
108112
return entity;
109113
});
110114

115+
template = new AsyncCassandraTemplate(session);
116+
template.setUsePreparedStatements(false);
111117
template.setEntityCallbacks(callbacks);
112118
}
113119

120+
@Test // gh-1133
121+
void shouldConfigureConverterFromSession() {
122+
assertThat(template.getConverter().getCodecRegistry()).isEqualTo(session.getContext().getCodecRegistry());
123+
assertThat(template.getConverter()).extracting("userTypeResolver").isNotNull();
124+
}
125+
114126
@Test // DATACASS-292
115127
void selectUsingCqlShouldReturnMappedResults() {
116128

0 commit comments

Comments
 (0)