diff --git a/pom.xml b/pom.xml
index 2f40cd66af..1f4fef420a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-commons
- 3.0.0-SNAPSHOT
+ 3.0.0-GH-2634-SNAPSHOT
Spring Data Core
Core Spring concepts underpinning every Spring Data module.
diff --git a/src/main/java/org/springframework/data/domain/ManagedTypes.java b/src/main/java/org/springframework/data/domain/ManagedTypes.java
new file mode 100644
index 0000000000..c144eb3f85
--- /dev/null
+++ b/src/main/java/org/springframework/data/domain/ManagedTypes.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.domain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import org.springframework.data.util.Lazy;
+
+/**
+ * Types managed by a Spring Data implementation. Used to predefine a set of know entities that might need processing
+ * during the Spring container, Spring Data Repository initialization phase.
+ *
+ * @author Christoph Strobl
+ * @author John Blum
+ * @see java.lang.FunctionalInterface
+ * @since 3.0
+ */
+@FunctionalInterface
+public interface ManagedTypes {
+
+ /**
+ * Factory method used to construct a new instance of {@link ManagedTypes} containing no {@link Class types}.
+ *
+ * @return an empty {@link ManagedTypes} instance.
+ * @see java.util.Collections#emptySet()
+ * @see #fromIterable(Iterable)
+ */
+ static ManagedTypes empty() {
+ return fromIterable(Collections.emptySet());
+ }
+
+ /**
+ * Factory method used to construct {@link ManagedTypes} from the given, required {@link Iterable} of {@link Class
+ * types}.
+ *
+ * @param types {@link Iterable} of {@link Class types} used to initialize the {@link ManagedTypes}; must not be
+ * {@literal null}.
+ * @return new instance of {@link ManagedTypes} initialized the given, required {@link Iterable} of {@link Class
+ * types}.
+ * @see java.lang.Iterable
+ * @see #fromStream(Stream)
+ * @see #fromSupplier(Supplier)
+ */
+ static ManagedTypes fromIterable(Iterable extends Class>> types) {
+ return types::forEach;
+ }
+
+ /**
+ * Factory method used to construct {@link ManagedTypes} from the given, required {@link Stream} of {@link Class
+ * types}.
+ *
+ * @param types {@link Stream} of {@link Class types} used to initialize the {@link ManagedTypes}; must not be
+ * {@literal null}.
+ * @return new instance of {@link ManagedTypes} initialized the given, required {@link Stream} of {@link Class types}.
+ * @see java.util.stream.Stream
+ * @see #fromIterable(Iterable)
+ * @see #fromSupplier(Supplier)
+ */
+ static ManagedTypes fromStream(Stream extends Class>> types) {
+ return types::forEach;
+ }
+
+ /**
+ * Factory method used to construct {@link ManagedTypes} from the given, required {@link Supplier} of an
+ * {@link Iterable} of {@link Class types}.
+ *
+ * @param dataProvider {@link Supplier} of an {@link Iterable} of {@link Class types} used to lazily initialize the
+ * {@link ManagedTypes}; must not be {@literal null}.
+ * @return new instance of {@link ManagedTypes} initialized the given, required {@link Supplier} of an
+ * {@link Iterable} of {@link Class types}.
+ * @see java.util.function.Supplier
+ * @see java.lang.Iterable
+ * @see #fromIterable(Iterable)
+ * @see #fromStream(Stream)
+ */
+ static ManagedTypes fromSupplier(Supplier>> dataProvider) {
+
+ return new ManagedTypes() {
+
+ final Lazy>> lazyProvider = Lazy.of(dataProvider);
+
+ @Override
+ public void forEach(Consumer> action) {
+ lazyProvider.get().forEach(action);
+ }
+ };
+ }
+
+ /**
+ * Applies the given {@link Consumer action} to each of the {@link Class types} contained in this {@link ManagedTypes}
+ * instance.
+ *
+ * @param action {@link Consumer} defining the action to perform on the {@link Class types} contained in this
+ * {@link ManagedTypes} instance; must not be {@literal null}.
+ * @see java.util.function.Consumer
+ */
+ void forEach(Consumer> action);
+
+ /**
+ * Returns all the {@link ManagedTypes} in a {@link List}.
+ *
+ * @return these {@link ManagedTypes} in a {@link List}; never {@literal null}.
+ * @see java.util.List
+ */
+ default List> toList() {
+
+ List> list = new ArrayList<>(100);
+ forEach(list::add);
+ return list;
+ }
+}
diff --git a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java
index 7570a8f031..93a1902ff0 100644
--- a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java
+++ b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java
@@ -42,6 +42,7 @@
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.KotlinDetector;
import org.springframework.core.NativeDetector;
+import org.springframework.data.domain.ManagedTypes;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
@@ -101,7 +102,8 @@ public abstract class AbstractMappingContext> initialEntitySet = new HashSet<>();
+ private ManagedTypes managedTypes = ManagedTypes.empty();
+
private boolean strict = false;
private SimpleTypeHolder simpleTypeHolder = SimpleTypeHolder.DEFAULT;
@@ -141,9 +143,21 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
* Sets the {@link Set} of types to populate the context initially.
*
* @param initialEntitySet
+ * @see #setManagedTypes(ManagedTypes)
+ *
*/
public void setInitialEntitySet(Set extends Class>> initialEntitySet) {
- this.initialEntitySet = initialEntitySet;
+ setManagedTypes(ManagedTypes.fromIterable(initialEntitySet));
+ }
+
+ /**
+ * Sets the types to populate the context initially.
+ *
+ * @param managedTypes must not be {@literal null}. Use {@link ManagedTypes#empty()} instead;
+ * @since 3.0
+ */
+ public void setManagedTypes(ManagedTypes managedTypes) {
+ this.managedTypes = managedTypes;
}
/**
@@ -467,7 +481,7 @@ public void afterPropertiesSet() {
* context.
*/
public void initialize() {
- initialEntitySet.forEach(this::addPersistentEntity);
+ managedTypes.forEach(this::addPersistentEntity);
}
/**
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java
index 0b133286eb..2c7061003a 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java
@@ -170,7 +170,7 @@ private String registerRepositoryFragments(RepositoryConfiguration> configurat
fragmentsBuilder.addConstructorArgValue(fragmentBeanNames);
String fragmentsBeanName = BeanDefinitionReaderUtils
- .uniqueBeanName(String.format("%s.%s.fragments", extension.getModuleName().toLowerCase(Locale.ROOT),
+ .uniqueBeanName(String.format("%s.%s.fragments", extension.getModulePrefix(),
ClassUtils.getShortName(configuration.getRepositoryInterface())), registry);
registry.registerBeanDefinition(fragmentsBeanName, fragmentsBuilder.getBeanDefinition());
return fragmentsBeanName;
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java
index 561862cd4a..ee964cb4e3 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java
@@ -25,7 +25,6 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java
index 07b29ce47b..ccbd8f4bc2 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java
@@ -15,18 +15,22 @@
*/
package org.springframework.data.repository.config;
+import java.lang.annotation.Annotation;
import java.util.Collection;
+import java.util.Collections;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.io.ResourceLoader;
+import org.springframework.util.StringUtils;
/**
* SPI to implement store specific extension to the repository bean definition registration process.
*
* @see RepositoryConfigurationExtensionSupport
* @author Oliver Gierke
+ * @author Christoph Strobl
*/
public interface RepositoryConfigurationExtension {
@@ -35,7 +39,18 @@ public interface RepositoryConfigurationExtension {
*
* @return
*/
- String getModuleName();
+ default String getModuleName() {
+ return StringUtils.capitalize(getModulePrefix());
+ }
+
+ /**
+ * Returns the prefix of the module to be used to create the default location for Spring Data named queries and module
+ * specific bean definitions.
+ *
+ * @return must not be {@literal null}.
+ * @since 3.0
+ */
+ String getModulePrefix();
/**
* Returns all {@link RepositoryConfiguration}s obtained through the given {@link RepositoryConfigurationSource}.
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java
index 567bbc8db5..ade7871fff 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java
@@ -60,11 +60,6 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
private boolean noMultiStoreSupport = false;
- @Override
- public String getModuleName() {
- return StringUtils.capitalize(getModulePrefix());
- }
-
public Collection> getRepositoryConfigurations(
T configSource, ResourceLoader loader) {
return getRepositoryConfigurations(configSource, loader, false);
@@ -109,13 +104,6 @@ public String getDefaultNamedQueryLocation() {
public void registerBeansForRoot(BeanDefinitionRegistry registry,
RepositoryConfigurationSource configurationSource) {}
- /**
- * Returns the prefix of the module to be used to create the default location for Spring Data named queries.
- *
- * @return must not be {@literal null}.
- */
- protected abstract String getModulePrefix();
-
public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {}
public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {}
diff --git a/src/test/java/org/springframework/data/classloadersupport/HidingClassLoader.java b/src/test/java/org/springframework/data/classloadersupport/HidingClassLoader.java
index d79072c792..215c92f579 100644
--- a/src/test/java/org/springframework/data/classloadersupport/HidingClassLoader.java
+++ b/src/test/java/org/springframework/data/classloadersupport/HidingClassLoader.java
@@ -34,6 +34,7 @@
*
* @author Jens Schauder
* @author Oliver Gierke
+ * @author Christoph Strobl
*/
public class HidingClassLoader extends ShadowingClassLoader {
@@ -61,11 +62,21 @@ public static HidingClassLoader hide(Class>... packages) {
.collect(Collectors.toList()));
}
+ public static HidingClassLoader hideTypes(Class>... types) {
+
+ Assert.notNull(types, "Types must not be null!");
+
+ return new HidingClassLoader(Arrays.stream(types)//
+ .map(it -> it.getName())//
+ .collect(Collectors.toList()));
+ }
+
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
- checkIfHidden(name);
- return super.loadClass(name);
+ Class> loaded = super.loadClass(name);
+ checkIfHidden(loaded);
+ return loaded;
}
@Override
@@ -76,13 +87,14 @@ protected boolean isEligibleForShadowing(String className) {
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
- checkIfHidden(name);
- return super.findClass(name);
+ Class> loaded = super.findClass(name);
+ checkIfHidden(loaded);
+ return loaded;
}
- private void checkIfHidden(String name) throws ClassNotFoundException {
+ private void checkIfHidden(Class> type) throws ClassNotFoundException {
- if (hidden.stream().anyMatch(it -> name.startsWith(it))) {
+ if (hidden.stream().anyMatch(it -> type.getName().startsWith(it))) {
throw new ClassNotFoundException();
}
}
diff --git a/src/test/java/org/springframework/data/domain/ManagedTypesUnitTests.java b/src/test/java/org/springframework/data/domain/ManagedTypesUnitTests.java
new file mode 100644
index 0000000000..eb0be6cfc6
--- /dev/null
+++ b/src/test/java/org/springframework/data/domain/ManagedTypesUnitTests.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.domain;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * @author Christoph Strobl
+ */
+@ExtendWith(MockitoExtension.class)
+class ManagedTypesUnitTests {
+
+ @Mock Consumer> action;
+
+ @Test // GH-2634
+ void emptyNeverCallsAction() {
+
+ ManagedTypes.empty().forEach(action);
+ verify(action, never()).accept(any());
+ }
+
+ @Test // GH-2634
+ void supplierBasedManagedTypesAreEvaluatedLazily() {
+
+ Supplier>> typesSupplier = spy(new Supplier>>() {
+ @Override
+ public Iterable> get() {
+ return Collections.singleton(Object.class);
+ }
+ });
+
+ ManagedTypes managedTypes = ManagedTypes.fromSupplier(typesSupplier);
+
+ managedTypes.forEach(action); // 1st invocation
+ verify(action).accept(any());
+ verify(typesSupplier).get();
+
+ managedTypes.forEach(action); // 2nd invocation
+ verify(action, times(2)).accept(any());
+ verify(typesSupplier, times(1)).get();
+ }
+
+ @Test // GH-2634
+ void toListOnEmptyReturnsEmptyList() {
+ assertThat(ManagedTypes.empty().toList()).isEmpty();
+ }
+
+ @Test // GH-2634
+ void toListContainsEntriesInOrder() {
+ assertThat(ManagedTypes.fromIterable(Arrays.asList(Object.class, List.class)).toList()).containsExactly(Object.class,
+ List.class);
+ }
+}
diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java
index 861e5226b6..52c81e64e7 100755
--- a/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java
@@ -187,7 +187,7 @@ public String getRepositoryFactoryBeanClassName() {
}
@Override
- protected String getModulePrefix() {
+ public String getModulePrefix() {
return "commons";
}
}
diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java
index db57401cd8..3a9895bbf7 100644
--- a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java
@@ -24,7 +24,6 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
-
import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java
index dca0ff7485..b6b9a833bd 100755
--- a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java
@@ -100,7 +100,7 @@ void doesNotClaimEntityIfNoIdentifyingAnnotationsAreExposed() {
static class SampleRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport {
@Override
- protected String getModulePrefix() {
+ public String getModulePrefix() {
return "core";
}
@@ -123,7 +123,7 @@ protected Collection> getIdentifyingTypes() {
static class NonIdentifyingConfigurationExtension extends RepositoryConfigurationExtensionSupport {
@Override
- protected String getModulePrefix() {
+ public String getModulePrefix() {
return "non-identifying";
}