Skip to content

Commit 5866f00

Browse files
committed
Support AOT processing of Cassandra repositories.
We now ship runtime hints for AOT processing of the Spring Data infrastructure. Closes #1280
1 parent f416c2c commit 5866f00

File tree

18 files changed

+1259
-68
lines changed

18 files changed

+1259
-68
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2022 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;
17+
18+
import java.util.Arrays;
19+
import java.util.function.Consumer;
20+
21+
import org.springframework.data.domain.ManagedTypes;
22+
23+
/**
24+
* Cassandra-specific extension to {@link ManagedTypes}.
25+
*
26+
* @author Mark Paluch
27+
* @since 4.0
28+
*/
29+
public final class CassandraManagedTypes implements ManagedTypes {
30+
31+
private final ManagedTypes delegate;
32+
33+
private CassandraManagedTypes(ManagedTypes types) {
34+
this.delegate = types;
35+
}
36+
37+
/**
38+
* Wraps an existing {@link ManagedTypes} object with {@link CassandraManagedTypes}.
39+
*
40+
* @param managedTypes
41+
* @return
42+
*/
43+
public static CassandraManagedTypes from(ManagedTypes managedTypes) {
44+
return new CassandraManagedTypes(managedTypes);
45+
}
46+
47+
/**
48+
* Factory method used to construct {@link CassandraManagedTypes} from the given array of {@link Class types}.
49+
*
50+
* @param types array of {@link Class types} used to initialize the {@link ManagedTypes}; must not be {@literal null}.
51+
* @return new instance of {@link CassandraManagedTypes} initialized from {@link Class types}.
52+
*/
53+
public static CassandraManagedTypes from(Class<?>... types) {
54+
return fromIterable(Arrays.asList(types));
55+
}
56+
57+
/**
58+
* Factory method used to construct {@link CassandraManagedTypes} from the given, required {@link Iterable} of
59+
* {@link Class types}.
60+
*
61+
* @param types {@link Iterable} of {@link Class types} used to initialize the {@link ManagedTypes}; must not be
62+
* {@literal null}.
63+
* @return new instance of {@link CassandraManagedTypes} initialized the given, required {@link Iterable} of
64+
* {@link Class types}.
65+
*/
66+
public static CassandraManagedTypes fromIterable(Iterable<? extends Class<?>> types) {
67+
return from(ManagedTypes.fromIterable(types));
68+
}
69+
70+
/**
71+
* Factory method to return an empty {@link CassandraManagedTypes} object.
72+
*
73+
* @return an empty {@link CassandraManagedTypes} object.
74+
*/
75+
public static CassandraManagedTypes empty() {
76+
return from(ManagedTypes.empty());
77+
}
78+
79+
@Override
80+
public void forEach(Consumer<Class<?>> action) {
81+
delegate.forEach(action);
82+
}
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2022 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.aot;
17+
18+
import java.util.Arrays;
19+
20+
import org.springframework.aot.hint.MemberCategory;
21+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
22+
import org.springframework.aot.hint.TypeReference;
23+
import org.springframework.data.cassandra.core.mapping.event.BeforeConvertCallback;
24+
import org.springframework.data.cassandra.core.mapping.event.BeforeSaveCallback;
25+
import org.springframework.data.cassandra.core.mapping.event.ReactiveBeforeConvertCallback;
26+
import org.springframework.data.cassandra.core.mapping.event.ReactiveBeforeSaveCallback;
27+
import org.springframework.data.cassandra.repository.support.SimpleCassandraRepository;
28+
import org.springframework.data.cassandra.repository.support.SimpleReactiveCassandraRepository;
29+
import org.springframework.data.repository.util.ReactiveWrappers;
30+
import org.springframework.lang.Nullable;
31+
32+
/**
33+
* {@link RuntimeHintsRegistrar} for repository types and entity callbacks.
34+
*
35+
* @author Mark Paluch
36+
* @since 4.0
37+
*/
38+
class CassandraRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
39+
40+
private static final boolean PROJECT_REACTOR_PRESENT = ReactiveWrappers
41+
.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
42+
43+
@Override
44+
public void registerHints(org.springframework.aot.hint.RuntimeHints hints, @Nullable ClassLoader classLoader) {
45+
46+
hints.reflection().registerTypes(Arrays.asList(TypeReference.of(SimpleCassandraRepository.class), //
47+
TypeReference.of(BeforeConvertCallback.class), //
48+
TypeReference.of(BeforeSaveCallback.class)),
49+
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
50+
MemberCategory.INVOKE_PUBLIC_METHODS));
51+
52+
if (PROJECT_REACTOR_PRESENT) {
53+
54+
hints.reflection().registerTypes(Arrays.asList(TypeReference.of(SimpleReactiveCassandraRepository.class), //
55+
TypeReference.of(ReactiveBeforeConvertCallback.class), //
56+
TypeReference.of(ReactiveBeforeSaveCallback.class)),
57+
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
58+
MemberCategory.INVOKE_PUBLIC_METHODS));
59+
}
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Ahead of Time processing utilities for Spring Data Cassandra.
3+
*/
4+
@NonNullApi
5+
package org.springframework.data.cassandra.aot;
6+
7+
import org.springframework.lang.NonNullApi;

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

+37-15
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.context.annotation.Configuration;
2525
import org.springframework.core.convert.converter.Converter;
2626
import org.springframework.core.io.ByteArrayResource;
27+
import org.springframework.data.cassandra.CassandraManagedTypes;
2728
import org.springframework.data.cassandra.SessionFactory;
2829
import org.springframework.data.cassandra.core.CassandraAdminTemplate;
2930
import org.springframework.data.cassandra.core.convert.CassandraConverter;
@@ -36,6 +37,7 @@
3637
import org.springframework.data.cassandra.core.mapping.Table;
3738
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
3839
import org.springframework.data.convert.CustomConversions;
40+
import org.springframework.data.domain.ManagedTypes;
3941
import org.springframework.data.mapping.context.MappingContext;
4042
import org.springframework.lang.Nullable;
4143

@@ -58,9 +60,8 @@ public abstract class AbstractCassandraConfiguration extends AbstractSessionConf
5860
private @Nullable ClassLoader beanClassLoader;
5961

6062
/**
61-
* Creates a {@link CassandraConverter} using the configured {@link #cassandraMapping()}.
62-
*
63-
* Will apply all specified {@link #customConversions()}.
63+
* Creates a {@link CassandraConverter} using the configured {@link #cassandraMapping()}. Will apply all specified
64+
* {@link #customConversions()}.
6465
*
6566
* @return {@link CassandraConverter} used to convert Java and Cassandra value types during the mapping process.
6667
* @see #cassandraMapping()
@@ -71,11 +72,11 @@ public CassandraConverter cassandraConverter() {
7172

7273
CqlSession cqlSession = getRequiredSession();
7374

74-
UserTypeResolver userTypeResolver =
75-
new SimpleUserTypeResolver(cqlSession, CqlIdentifier.fromCql(getKeyspaceName()));
75+
UserTypeResolver userTypeResolver = new SimpleUserTypeResolver(cqlSession,
76+
CqlIdentifier.fromCql(getKeyspaceName()));
7677

77-
MappingCassandraConverter converter =
78-
new MappingCassandraConverter(requireBeanOfType(CassandraMappingContext.class));
78+
MappingCassandraConverter converter = new MappingCassandraConverter(
79+
requireBeanOfType(CassandraMappingContext.class));
7980

8081
converter.setCodecRegistry(cqlSession.getContext().getCodecRegistry());
8182
converter.setUserTypeResolver(userTypeResolver);
@@ -84,31 +85,52 @@ public CassandraConverter cassandraConverter() {
8485
return converter;
8586
}
8687

88+
/**
89+
* Returns the a {@link CassandraManagedTypes} object holding the initial entity set.
90+
*
91+
* @return new instance of {@link CassandraManagedTypes}.
92+
* @throws ClassNotFoundException
93+
* @since 4.0
94+
*/
95+
@Bean
96+
public CassandraManagedTypes cassandraManagedTypes() throws ClassNotFoundException {
97+
return CassandraManagedTypes.fromIterable(getInitialEntitySet());
98+
}
99+
87100
/**
88101
* Return the {@link MappingContext} instance to map Entities to {@link Object Java Objects}.
89102
*
90-
* @throws ClassNotFoundException if the Cassandra Entity class type identified by name
91-
* cannot be found during the scan.
92103
* @see org.springframework.data.cassandra.core.mapping.CassandraMappingContext
104+
* @deprecated since 4.0, use {@link #cassandraMappingContext(ManagedTypes)} instead.
93105
*/
94-
@Bean
106+
@Deprecated(since = "4.0", forRemoval = true)
95107
public CassandraMappingContext cassandraMapping() throws ClassNotFoundException {
108+
return cassandraMappingContext(cassandraManagedTypes());
109+
}
110+
111+
/**
112+
* Return the {@link MappingContext} instance to map Entities to {@link Object Java Objects}.
113+
*
114+
* @see org.springframework.data.cassandra.core.mapping.CassandraMappingContext
115+
*/
116+
@Bean
117+
public CassandraMappingContext cassandraMappingContext(CassandraManagedTypes cassandraManagedTypes) {
96118

97119
CqlSession cqlSession = getRequiredSession();
98120

99-
UserTypeResolver userTypeResolver =
100-
new SimpleUserTypeResolver(cqlSession, CqlIdentifier.fromCql(getKeyspaceName()));
121+
UserTypeResolver userTypeResolver = new SimpleUserTypeResolver(cqlSession,
122+
CqlIdentifier.fromCql(getKeyspaceName()));
101123

102-
CassandraMappingContext mappingContext =
103-
new CassandraMappingContext(userTypeResolver, SimpleTupleTypeFactory.DEFAULT);
124+
CassandraMappingContext mappingContext = new CassandraMappingContext(userTypeResolver,
125+
SimpleTupleTypeFactory.DEFAULT);
104126

105127
CustomConversions customConversions = requireBeanOfType(CassandraCustomConversions.class);
106128

107129
getBeanClassLoader().ifPresent(mappingContext::setBeanClassLoader);
108130

109131
mappingContext.setCodecRegistry(cqlSession.getContext().getCodecRegistry());
110132
mappingContext.setCustomConversions(customConversions);
111-
mappingContext.setInitialEntitySet(getInitialEntitySet());
133+
mappingContext.setManagedTypes(cassandraManagedTypes);
112134
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
113135

114136
return mappingContext;

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

+42-14
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@
1717

1818
import java.lang.annotation.Annotation;
1919

20+
import org.springframework.beans.factory.ListableBeanFactory;
2021
import org.springframework.beans.factory.config.BeanDefinition;
21-
import org.springframework.beans.factory.support.AbstractBeanDefinition;
2222
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
23+
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
2324
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2425
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
25-
import org.springframework.core.type.AnnotationMetadata;
2626
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
2727
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
2828
import org.springframework.data.auditing.config.AuditingConfiguration;
2929
import org.springframework.data.cassandra.core.mapping.event.AuditingEntityCallback;
3030
import org.springframework.data.config.ParsingUtils;
31+
import org.springframework.data.mapping.context.PersistentEntities;
32+
import org.springframework.lang.Nullable;
3133
import org.springframework.util.Assert;
3234

3335
/**
@@ -49,26 +51,19 @@ protected String getAuditingHandlerBeanName() {
4951
}
5052

5153
@Override
52-
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
53-
54-
Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null");
55-
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
54+
protected void postProcess(BeanDefinitionBuilder builder, AuditingConfiguration configuration,
55+
BeanDefinitionRegistry registry) {
5656

57-
super.registerBeanDefinitions(annotationMetadata, registry);
57+
potentiallyRegisterCassandraPersistentEntities(builder, registry);
5858
}
5959

6060
@Override
6161
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
6262

6363
Assert.notNull(configuration, "AuditingConfiguration must not be null");
6464

65-
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class);
66-
67-
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
68-
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
69-
70-
builder.addConstructorArgValue(definition.getBeanDefinition());
71-
return configureDefaultAuditHandlerAttributes(configuration, builder);
65+
return configureDefaultAuditHandlerAttributes(configuration,
66+
BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class));
7267
}
7368

7469
@Override
@@ -85,7 +80,40 @@ protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandle
8580

8681
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
8782
AuditingEntityCallback.class.getName(), registry);
83+
}
84+
85+
static void potentiallyRegisterCassandraPersistentEntities(BeanDefinitionBuilder builder,
86+
BeanDefinitionRegistry registry) {
87+
88+
String persistentEntitiesBeanName = detectPersistentEntitiesBeanName(registry);
89+
90+
if (persistentEntitiesBeanName == null) {
91+
92+
persistentEntitiesBeanName = BeanDefinitionReaderUtils.uniqueBeanName("cassandraPersistentEntities", registry);
93+
94+
// TODO: https://github.com/spring-projects/spring-framework/issues/28728
95+
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntities.class) //
96+
.setFactoryMethod("of") //
97+
.addConstructorArgReference("cassandraMappingContext");
98+
99+
registry.registerBeanDefinition(persistentEntitiesBeanName, definition.getBeanDefinition());
100+
}
101+
102+
builder.addConstructorArgReference(persistentEntitiesBeanName);
103+
}
104+
105+
@Nullable
106+
private static String detectPersistentEntitiesBeanName(BeanDefinitionRegistry registry) {
107+
108+
if (registry instanceof ListableBeanFactory beanFactory) {
109+
for (String bn : beanFactory.getBeanNamesForType(PersistentEntities.class)) {
110+
if (bn.startsWith("cassandra")) {
111+
return bn;
112+
}
113+
}
114+
}
88115

116+
return null;
89117
}
90118

91119
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ public interface DefaultBeanNames extends DefaultCqlBeanNames {
2323

2424
String DATA_TEMPLATE = "cassandraTemplate";
2525
String CONVERTER = "cassandraConverter";
26-
String CONTEXT = "cassandraMapping";
26+
String CONTEXT = "cassandraMappingContext";
2727
String USER_TYPE_RESOLVER = "userTypeResolver";
2828
}

0 commit comments

Comments
 (0)