From 02bf260d6d865f3ba1990706fd893123c7689d4d Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 18 May 2022 10:17:15 +0200 Subject: [PATCH] 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` for easier context setup. Original pull request: #2635. Closes #2634. --- .../data/domain/ManagedTypes.java | 129 ++++++++++++++++++ .../context/AbstractMappingContext.java | 20 ++- .../RepositoryBeanDefinitionBuilder.java | 2 +- .../RepositoryConfigurationDelegate.java | 1 - .../RepositoryConfigurationExtension.java | 17 ++- ...positoryConfigurationExtensionSupport.java | 12 -- .../classloadersupport/HidingClassLoader.java | 24 +++- .../data/domain/ManagedTypesUnitTests.java | 79 +++++++++++ ...anDefinitionRegistrarSupportUnitTests.java | 2 +- ...ositoryConfigurationDelegateUnitTests.java | 1 - ...onfigurationExtensionSupportUnitTests.java | 4 +- 11 files changed, 263 insertions(+), 28 deletions(-) create mode 100644 src/main/java/org/springframework/data/domain/ManagedTypes.java create mode 100644 src/test/java/org/springframework/data/domain/ManagedTypesUnitTests.java 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 000000000..c144eb3f8 --- /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> 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> 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 7570a8f03..93a1902ff 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.ApplicationEventPublisher; 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 abstract class AbstractMappingContext> 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 abstract class AbstractMappingContext Collection> getRepositoryConfigurations( T configSource, ResourceLoader loader) { return getRepositoryConfigurations(configSource, loader, false); @@ -109,13 +104,6 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit 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 d79072c79..215c92f57 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 @@ import org.springframework.util.Assert; * * @author Jens Schauder * @author Oliver Gierke + * @author Christoph Strobl */ public class HidingClassLoader extends ShadowingClassLoader { @@ -61,11 +62,21 @@ public class HidingClassLoader extends ShadowingClassLoader { .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 @@ public class HidingClassLoader extends ShadowingClassLoader { @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 000000000..eb0be6cfc --- /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 861e5226b..52c81e64e 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 @@ class RepositoryBeanDefinitionRegistrarSupportUnitTests { } @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 db57401cd..3a9895bbf 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.Mockito; 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 dca0ff748..b6b9a833b 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 @@ class RepositoryConfigurationExtensionSupportUnitTests { static class SampleRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport { @Override - protected String getModulePrefix() { + public String getModulePrefix() { return "core"; } @@ -123,7 +123,7 @@ class RepositoryConfigurationExtensionSupportUnitTests { static class NonIdentifyingConfigurationExtension extends RepositoryConfigurationExtensionSupport { @Override - protected String getModulePrefix() { + public String getModulePrefix() { return "non-identifying"; }