Skip to content

Commit 02bf260

Browse files
christophstroblmp911de
authored andcommitted
Introduce ManagedTypes.
We now provide an abstraction to describe a collection of entity types that can be provided to the mapping context as improvement over `Set<Class>` for easier context setup. Original pull request: #2635. Closes #2634.
1 parent 2a1a8f3 commit 02bf260

11 files changed

+263
-28
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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.domain;
17+
18+
import java.util.ArrayList;
19+
import java.util.Collections;
20+
import java.util.List;
21+
import java.util.function.Consumer;
22+
import java.util.function.Supplier;
23+
import java.util.stream.Stream;
24+
25+
import org.springframework.data.util.Lazy;
26+
27+
/**
28+
* Types managed by a Spring Data implementation. Used to predefine a set of know entities that might need processing
29+
* during the Spring container, Spring Data Repository initialization phase.
30+
*
31+
* @author Christoph Strobl
32+
* @author John Blum
33+
* @see java.lang.FunctionalInterface
34+
* @since 3.0
35+
*/
36+
@FunctionalInterface
37+
public interface ManagedTypes {
38+
39+
/**
40+
* Factory method used to construct a new instance of {@link ManagedTypes} containing no {@link Class types}.
41+
*
42+
* @return an empty {@link ManagedTypes} instance.
43+
* @see java.util.Collections#emptySet()
44+
* @see #fromIterable(Iterable)
45+
*/
46+
static ManagedTypes empty() {
47+
return fromIterable(Collections.emptySet());
48+
}
49+
50+
/**
51+
* Factory method used to construct {@link ManagedTypes} from the given, required {@link Iterable} of {@link Class
52+
* types}.
53+
*
54+
* @param types {@link Iterable} of {@link Class types} used to initialize the {@link ManagedTypes}; must not be
55+
* {@literal null}.
56+
* @return new instance of {@link ManagedTypes} initialized the given, required {@link Iterable} of {@link Class
57+
* types}.
58+
* @see java.lang.Iterable
59+
* @see #fromStream(Stream)
60+
* @see #fromSupplier(Supplier)
61+
*/
62+
static ManagedTypes fromIterable(Iterable<? extends Class<?>> types) {
63+
return types::forEach;
64+
}
65+
66+
/**
67+
* Factory method used to construct {@link ManagedTypes} from the given, required {@link Stream} of {@link Class
68+
* types}.
69+
*
70+
* @param types {@link Stream} of {@link Class types} used to initialize the {@link ManagedTypes}; must not be
71+
* {@literal null}.
72+
* @return new instance of {@link ManagedTypes} initialized the given, required {@link Stream} of {@link Class types}.
73+
* @see java.util.stream.Stream
74+
* @see #fromIterable(Iterable)
75+
* @see #fromSupplier(Supplier)
76+
*/
77+
static ManagedTypes fromStream(Stream<? extends Class<?>> types) {
78+
return types::forEach;
79+
}
80+
81+
/**
82+
* Factory method used to construct {@link ManagedTypes} from the given, required {@link Supplier} of an
83+
* {@link Iterable} of {@link Class types}.
84+
*
85+
* @param dataProvider {@link Supplier} of an {@link Iterable} of {@link Class types} used to lazily initialize the
86+
* {@link ManagedTypes}; must not be {@literal null}.
87+
* @return new instance of {@link ManagedTypes} initialized the given, required {@link Supplier} of an
88+
* {@link Iterable} of {@link Class types}.
89+
* @see java.util.function.Supplier
90+
* @see java.lang.Iterable
91+
* @see #fromIterable(Iterable)
92+
* @see #fromStream(Stream)
93+
*/
94+
static ManagedTypes fromSupplier(Supplier<Iterable<Class<?>>> dataProvider) {
95+
96+
return new ManagedTypes() {
97+
98+
final Lazy<Iterable<Class<?>>> lazyProvider = Lazy.of(dataProvider);
99+
100+
@Override
101+
public void forEach(Consumer<Class<?>> action) {
102+
lazyProvider.get().forEach(action);
103+
}
104+
};
105+
}
106+
107+
/**
108+
* Applies the given {@link Consumer action} to each of the {@link Class types} contained in this {@link ManagedTypes}
109+
* instance.
110+
*
111+
* @param action {@link Consumer} defining the action to perform on the {@link Class types} contained in this
112+
* {@link ManagedTypes} instance; must not be {@literal null}.
113+
* @see java.util.function.Consumer
114+
*/
115+
void forEach(Consumer<Class<?>> action);
116+
117+
/**
118+
* Returns all the {@link ManagedTypes} in a {@link List}.
119+
*
120+
* @return these {@link ManagedTypes} in a {@link List}; never {@literal null}.
121+
* @see java.util.List
122+
*/
123+
default List<Class<?>> toList() {
124+
125+
List<Class<?>> list = new ArrayList<>(100);
126+
forEach(list::add);
127+
return list;
128+
}
129+
}

src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java

+17-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.context.ApplicationEventPublisherAware;
4343
import org.springframework.core.KotlinDetector;
4444
import org.springframework.core.NativeDetector;
45+
import org.springframework.data.domain.ManagedTypes;
4546
import org.springframework.data.mapping.MappingException;
4647
import org.springframework.data.mapping.PersistentEntity;
4748
import org.springframework.data.mapping.PersistentProperty;
@@ -101,7 +102,8 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
101102
private @Nullable ApplicationEventPublisher applicationEventPublisher;
102103
private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
103104

104-
private Set<? extends Class<?>> initialEntitySet = new HashSet<>();
105+
private ManagedTypes managedTypes = ManagedTypes.empty();
106+
105107
private boolean strict = false;
106108
private SimpleTypeHolder simpleTypeHolder = SimpleTypeHolder.DEFAULT;
107109

@@ -141,9 +143,21 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
141143
* Sets the {@link Set} of types to populate the context initially.
142144
*
143145
* @param initialEntitySet
146+
* @see #setManagedTypes(ManagedTypes)
147+
*
144148
*/
145149
public void setInitialEntitySet(Set<? extends Class<?>> initialEntitySet) {
146-
this.initialEntitySet = initialEntitySet;
150+
setManagedTypes(ManagedTypes.fromIterable(initialEntitySet));
151+
}
152+
153+
/**
154+
* Sets the types to populate the context initially.
155+
*
156+
* @param managedTypes must not be {@literal null}. Use {@link ManagedTypes#empty()} instead;
157+
* @since 3.0
158+
*/
159+
public void setManagedTypes(ManagedTypes managedTypes) {
160+
this.managedTypes = managedTypes;
147161
}
148162

149163
/**
@@ -467,7 +481,7 @@ public void afterPropertiesSet() {
467481
* context.
468482
*/
469483
public void initialize() {
470-
initialEntitySet.forEach(this::addPersistentEntity);
484+
managedTypes.forEach(this::addPersistentEntity);
471485
}
472486

473487
/**

src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ private String registerRepositoryFragments(RepositoryConfiguration<?> configurat
170170
fragmentsBuilder.addConstructorArgValue(fragmentBeanNames);
171171

172172
String fragmentsBeanName = BeanDefinitionReaderUtils
173-
.uniqueBeanName(String.format("%s.%s.fragments", extension.getModuleName().toLowerCase(Locale.ROOT),
173+
.uniqueBeanName(String.format("%s.%s.fragments", extension.getModulePrefix(),
174174
ClassUtils.getShortName(configuration.getRepositoryInterface())), registry);
175175
registry.registerBeanDefinition(fragmentsBeanName, fragmentsBuilder.getBeanDefinition());
176176
return fragmentsBeanName;

src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
import org.apache.commons.logging.Log;
2727
import org.apache.commons.logging.LogFactory;
28-
2928
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
3029
import org.springframework.beans.factory.config.DependencyDescriptor;
3130
import org.springframework.beans.factory.parsing.BeanComponentDefinition;

src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,22 @@
1515
*/
1616
package org.springframework.data.repository.config;
1717

18+
import java.lang.annotation.Annotation;
1819
import java.util.Collection;
20+
import java.util.Collections;
1921

2022
import org.springframework.beans.factory.config.BeanDefinition;
2123
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
2224
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2325
import org.springframework.core.io.ResourceLoader;
26+
import org.springframework.util.StringUtils;
2427

2528
/**
2629
* SPI to implement store specific extension to the repository bean definition registration process.
2730
*
2831
* @see RepositoryConfigurationExtensionSupport
2932
* @author Oliver Gierke
33+
* @author Christoph Strobl
3034
*/
3135
public interface RepositoryConfigurationExtension {
3236

@@ -35,7 +39,18 @@ public interface RepositoryConfigurationExtension {
3539
*
3640
* @return
3741
*/
38-
String getModuleName();
42+
default String getModuleName() {
43+
return StringUtils.capitalize(getModulePrefix());
44+
}
45+
46+
/**
47+
* Returns the prefix of the module to be used to create the default location for Spring Data named queries and module
48+
* specific bean definitions.
49+
*
50+
* @return must not be {@literal null}.
51+
* @since 3.0
52+
*/
53+
String getModulePrefix();
3954

4055
/**
4156
* Returns all {@link RepositoryConfiguration}s obtained through the given {@link RepositoryConfigurationSource}.

src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java

-12
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,6 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
6060

6161
private boolean noMultiStoreSupport = false;
6262

63-
@Override
64-
public String getModuleName() {
65-
return StringUtils.capitalize(getModulePrefix());
66-
}
67-
6863
public <T extends RepositoryConfigurationSource> Collection<RepositoryConfiguration<T>> getRepositoryConfigurations(
6964
T configSource, ResourceLoader loader) {
7065
return getRepositoryConfigurations(configSource, loader, false);
@@ -109,13 +104,6 @@ public String getDefaultNamedQueryLocation() {
109104
public void registerBeansForRoot(BeanDefinitionRegistry registry,
110105
RepositoryConfigurationSource configurationSource) {}
111106

112-
/**
113-
* Returns the prefix of the module to be used to create the default location for Spring Data named queries.
114-
*
115-
* @return must not be {@literal null}.
116-
*/
117-
protected abstract String getModulePrefix();
118-
119107
public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {}
120108

121109
public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {}

src/test/java/org/springframework/data/classloadersupport/HidingClassLoader.java

+18-6
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
*
3535
* @author Jens Schauder
3636
* @author Oliver Gierke
37+
* @author Christoph Strobl
3738
*/
3839
public class HidingClassLoader extends ShadowingClassLoader {
3940

@@ -61,11 +62,21 @@ public static HidingClassLoader hide(Class<?>... packages) {
6162
.collect(Collectors.toList()));
6263
}
6364

65+
public static HidingClassLoader hideTypes(Class<?>... types) {
66+
67+
Assert.notNull(types, "Types must not be null!");
68+
69+
return new HidingClassLoader(Arrays.stream(types)//
70+
.map(it -> it.getName())//
71+
.collect(Collectors.toList()));
72+
}
73+
6474
@Override
6575
public Class<?> loadClass(String name) throws ClassNotFoundException {
6676

67-
checkIfHidden(name);
68-
return super.loadClass(name);
77+
Class<?> loaded = super.loadClass(name);
78+
checkIfHidden(loaded);
79+
return loaded;
6980
}
7081

7182
@Override
@@ -76,13 +87,14 @@ protected boolean isEligibleForShadowing(String className) {
7687
@Override
7788
protected Class<?> findClass(String name) throws ClassNotFoundException {
7889

79-
checkIfHidden(name);
80-
return super.findClass(name);
90+
Class<?> loaded = super.findClass(name);
91+
checkIfHidden(loaded);
92+
return loaded;
8193
}
8294

83-
private void checkIfHidden(String name) throws ClassNotFoundException {
95+
private void checkIfHidden(Class<?> type) throws ClassNotFoundException {
8496

85-
if (hidden.stream().anyMatch(it -> name.startsWith(it))) {
97+
if (hidden.stream().anyMatch(it -> type.getName().startsWith(it))) {
8698
throw new ClassNotFoundException();
8799
}
88100
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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.domain;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.ArgumentMatchers.*;
20+
import static org.mockito.Mockito.*;
21+
22+
import java.util.Arrays;
23+
import java.util.Collections;
24+
import java.util.List;
25+
import java.util.function.Consumer;
26+
import java.util.function.Supplier;
27+
28+
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.api.extension.ExtendWith;
30+
import org.mockito.Mock;
31+
import org.mockito.junit.jupiter.MockitoExtension;
32+
33+
/**
34+
* @author Christoph Strobl
35+
*/
36+
@ExtendWith(MockitoExtension.class)
37+
class ManagedTypesUnitTests {
38+
39+
@Mock Consumer<Class<?>> action;
40+
41+
@Test // GH-2634
42+
void emptyNeverCallsAction() {
43+
44+
ManagedTypes.empty().forEach(action);
45+
verify(action, never()).accept(any());
46+
}
47+
48+
@Test // GH-2634
49+
void supplierBasedManagedTypesAreEvaluatedLazily() {
50+
51+
Supplier<Iterable<Class<?>>> typesSupplier = spy(new Supplier<Iterable<Class<?>>>() {
52+
@Override
53+
public Iterable<Class<?>> get() {
54+
return Collections.singleton(Object.class);
55+
}
56+
});
57+
58+
ManagedTypes managedTypes = ManagedTypes.fromSupplier(typesSupplier);
59+
60+
managedTypes.forEach(action); // 1st invocation
61+
verify(action).accept(any());
62+
verify(typesSupplier).get();
63+
64+
managedTypes.forEach(action); // 2nd invocation
65+
verify(action, times(2)).accept(any());
66+
verify(typesSupplier, times(1)).get();
67+
}
68+
69+
@Test // GH-2634
70+
void toListOnEmptyReturnsEmptyList() {
71+
assertThat(ManagedTypes.empty().toList()).isEmpty();
72+
}
73+
74+
@Test // GH-2634
75+
void toListContainsEntriesInOrder() {
76+
assertThat(ManagedTypes.fromIterable(Arrays.asList(Object.class, List.class)).toList()).containsExactly(Object.class,
77+
List.class);
78+
}
79+
}

0 commit comments

Comments
 (0)