Browse Source

Introduce infrastructure for AOT processing of repository declarations

This commit introduces initial support for framework 6 bases ahead of time processing of data components and adds extension points for module implementations.

See: #2593
Original Pull Request: #2624
pull/2652/head
Christoph Strobl 4 years ago
parent
commit
21ff2a7e34
No known key found for this signature in database
GPG Key ID: 8CC1AB53391458C8
  1. 64
      src/main/java/org/springframework/data/ManagedTypes.java
  2. 146
      src/main/java/org/springframework/data/aot/AotContext.java
  3. 189
      src/main/java/org/springframework/data/aot/AotContributingRepositoryBeanPostProcessor.java
  4. 69
      src/main/java/org/springframework/data/aot/AotDataComponentsBeanFactoryPostProcessor.java
  5. 150
      src/main/java/org/springframework/data/aot/AotManagedTypesPostProcessor.java
  6. 58
      src/main/java/org/springframework/data/aot/AotRepositoryContext.java
  7. 76
      src/main/java/org/springframework/data/aot/AotRepositoryInformation.java
  8. 132
      src/main/java/org/springframework/data/aot/DefaultRepositoryContext.java
  9. 185
      src/main/java/org/springframework/data/aot/RepositoryBeanContribution.java
  10. 88
      src/main/java/org/springframework/data/aot/RepositoryBeanDefinitionReader.java
  11. 243
      src/main/java/org/springframework/data/aot/TypeCollector.java
  12. 108
      src/main/java/org/springframework/data/aot/TypeContributor.java
  13. 107
      src/main/java/org/springframework/data/aot/TypeScanner.java
  14. 253
      src/main/java/org/springframework/data/aot/TypeUtils.java
  15. 40
      src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java
  16. 93
      src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java
  17. 18
      src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java
  18. 7
      src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java
  19. 122
      src/main/java/org/springframework/data/repository/config/RepositoryMetadata.java
  20. 182
      src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java
  21. 9
      src/main/java/org/springframework/data/repository/core/RepositoryMetadata.java
  22. 7
      src/main/java/org/springframework/data/repository/core/support/AnnotationRepositoryMetadata.java
  23. 139
      src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java
  24. 7
      src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMetadata.java
  25. 8
      src/main/java/org/springframework/data/repository/core/support/RepositoryFragment.java
  26. 322
      src/test/java/org/springframework/data/aot/AotContributingRepositoryBeanPostProcessorTests.java
  27. 81
      src/test/java/org/springframework/data/aot/AotDataComponentsBeanFactoryPostProcessorUnitTests.java
  28. 129
      src/test/java/org/springframework/data/aot/AotManagedTypesPostProcessorUnitTests.java
  29. 45
      src/test/java/org/springframework/data/aot/ClassProxyAssert.java
  30. 120
      src/test/java/org/springframework/data/aot/CodeContributionAssert.java
  31. 46
      src/test/java/org/springframework/data/aot/JdkProxyAssert.java
  32. 86
      src/test/java/org/springframework/data/aot/RepositoryBeanContributionAssert.java
  33. 63
      src/test/java/org/springframework/data/aot/TypeCollectorUnitTests.java
  34. 54
      src/test/java/org/springframework/data/aot/sample/ConfigWithCustomFactoryBeanBaseClass.java
  35. 63
      src/test/java/org/springframework/data/aot/sample/ConfigWithCustomImplementation.java
  36. 53
      src/test/java/org/springframework/data/aot/sample/ConfigWithCustomRepositoryBaseClass.java
  37. 77
      src/test/java/org/springframework/data/aot/sample/ConfigWithFragments.java
  38. 69
      src/test/java/org/springframework/data/aot/sample/ConfigWithQueryMethods.java
  39. 46
      src/test/java/org/springframework/data/aot/sample/ConfigWithSimpleCrudRepository.java
  40. 53
      src/test/java/org/springframework/data/aot/sample/ConfigWithTransactionManagerPresent.java
  41. 56
      src/test/java/org/springframework/data/aot/sample/ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.java
  42. 45
      src/test/java/org/springframework/data/aot/sample/ReactiveConfig.java
  43. 30
      src/test/java/org/springframework/data/aot/types/AbstractType.java
  44. 42
      src/test/java/org/springframework/data/aot/types/Address.java
  45. 24
      src/test/java/org/springframework/data/aot/types/BaseEntity.java
  46. 39
      src/test/java/org/springframework/data/aot/types/Customer.java
  47. 24
      src/test/java/org/springframework/data/aot/types/CyclicGenerics.java
  48. 24
      src/test/java/org/springframework/data/aot/types/CyclicPropertiesA.java
  49. 24
      src/test/java/org/springframework/data/aot/types/CyclicPropertiesB.java
  50. 24
      src/test/java/org/springframework/data/aot/types/CyclicPropertiesSelf.java
  51. 29
      src/test/java/org/springframework/data/aot/types/DomainObjectWithSimpleTypesOnly.java
  52. 23
      src/test/java/org/springframework/data/aot/types/EmptyType1.java
  53. 23
      src/test/java/org/springframework/data/aot/types/EmptyType2.java
  54. 55
      src/test/java/org/springframework/data/aot/types/FieldsAndMethods.java
  55. 26
      src/test/java/org/springframework/data/aot/types/InterfaceType.java
  56. 26
      src/test/java/org/springframework/data/aot/types/LocationHolder.java
  57. 23
      src/test/java/org/springframework/data/aot/types/ProjectionInterface.java
  58. 48
      src/test/java/org/springframework/data/aot/types/TypesInMethodSignatures.java
  59. 24
      src/test/java/org/springframework/data/aot/types/WithDeclaredClass.java
  60. 34
      src/test/java/org/springframework/data/repository/config/DummyConfigurationExtension.java
  61. 41
      src/test/java/org/springframework/data/repository/config/DummyRegistrar.java
  62. 55
      src/test/java/org/springframework/data/repository/config/EnableReactiveRepositories.java
  63. 1
      src/test/java/org/springframework/data/repository/config/EnableRepositories.java
  64. 43
      src/test/java/org/springframework/data/repository/config/ReactiveDummyConfigurationExtension.java
  65. 41
      src/test/java/org/springframework/data/repository/config/ReactiveDummyRegistrar.java
  66. 1
      src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportIntegrationTests.java
  67. 3
      src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java
  68. 2
      src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java
  69. 2
      src/test/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadataUnitTests.java
  70. 6
      src/test/java/org/springframework/data/repository/core/support/DummyRepositoryInformation.java
  71. 121
      src/test/java/org/springframework/data/repository/core/support/ReactiveDummyRepositoryFactory.java
  72. 45
      src/test/java/org/springframework/data/repository/core/support/ReactiveDummyRepositoryFactoryBean.java

64
src/main/java/org/springframework/data/ManagedTypes.java

@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
/*
* 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;
import java.util.ArrayList;
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 container/repository initialization phase.
*
* @author Christoph Strobl
* @since 3.0
*/
public interface ManagedTypes {
void forEach(Consumer<Class<?>> action);
default List<Class<?>> toList() {
List<Class<?>> tmp = new ArrayList<>(100);
forEach(tmp::add);
return tmp;
}
static ManagedTypes of(Iterable<? extends Class<?>> types) {
return types::forEach;
}
static ManagedTypes of(Stream<? extends Class<?>> types) {
return types::forEach;
}
static ManagedTypes o(Supplier<Iterable<? extends Class<?>>> dataProvider) {
return new ManagedTypes() {
Lazy<Iterable<? extends Class<?>>> lazyProvider = Lazy.of(dataProvider);
@Override
public void forEach(Consumer<Class<?>> action) {
lazyProvider.get().forEach(action);
}
};
}
}

146
src/main/java/org/springframework/data/aot/AotContext.java

@ -0,0 +1,146 @@ @@ -0,0 +1,146 @@
/*
* 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.aot;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* The context in which the AOT processing happens. Grants access to the {@link ConfigurableListableBeanFactory
* beanFactory} and {@link ClassLoader}. Holds a few convenience methods to check if a type
* {@link #isTypePresent(String) is present} and allows resolution of them. <strong>WARNING:</strong> Unstable internal
* API!
*
* @author Christoph Strobl
*/
public interface AotContext {
/**
* Create an {@link AotContext} backed by the given {@link BeanFactory}.
*
* @param beanFactory must not be {@literal null}.
* @return new instance of {@link AotContext}.
*/
static AotContext context(BeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null!");
return new AotContext() {
private final ConfigurableListableBeanFactory bf = beanFactory instanceof ConfigurableListableBeanFactory
? (ConfigurableListableBeanFactory) beanFactory
: new DefaultListableBeanFactory(beanFactory);
@Override
public ConfigurableListableBeanFactory getBeanFactory() {
return bf;
}
};
}
ConfigurableListableBeanFactory getBeanFactory();
default ClassLoader getClassLoader() {
return getBeanFactory().getBeanClassLoader();
}
default boolean isTypePresent(String typeName) {
return ClassUtils.isPresent(typeName, getBeanFactory().getBeanClassLoader());
}
default TypeScanner getTypeScanner() {
return new TypeScanner(getClassLoader());
}
default Set<Class<?>> scanPackageForTypes(Collection<Class<? extends Annotation>> identifyingAnnotations,
Collection<String> packageNames) {
return getTypeScanner().scanForTypesAnnotatedWith(identifyingAnnotations).inPackages(packageNames);
}
default Optional<Class<?>> resolveType(String typeName) {
if (!isTypePresent(typeName)) {
return Optional.empty();
}
return Optional.of(resolveRequiredType(typeName));
}
default Class<?> resolveRequiredType(String typeName) throws TypeNotPresentException {
try {
return ClassUtils.forName(typeName, getClassLoader());
} catch (ClassNotFoundException e) {
throw new TypeNotPresentException(typeName, e);
}
}
@Nullable
default Class<?> resolveType(BeanReference beanReference) {
return getBeanFactory().getType(beanReference.getBeanName(), false);
}
default BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
return getBeanFactory().getBeanDefinition(beanName);
}
default RootBeanDefinition getRootBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
BeanDefinition val = getBeanFactory().getBeanDefinition(beanName);
if (!(val instanceof RootBeanDefinition)) {
throw new IllegalStateException(String.format("%s is not a root bean", beanName));
}
return RootBeanDefinition.class.cast(val);
}
default boolean isFactoryBean(String beanName) {
return getBeanFactory().isFactoryBean(beanName);
}
default boolean isTransactionManagerPresent() {
return resolveType("org.springframework.transaction.TransactionManager") //
.map(it -> !ObjectUtils.isEmpty(getBeanFactory().getBeanNamesForType(it))) //
.orElse(false);
}
default void ifTypePresent(String typeName, Consumer<Class<?>> action) {
resolveType(typeName).ifPresent(action);
}
default void ifTransactionManagerPresent(Consumer<String[]> beanNamesConsumer) {
ifTypePresent("org.springframework.transaction.TransactionManager", txMgrType -> {
String[] txMgrBeanNames = getBeanFactory().getBeanNamesForType(txMgrType);
if (!ObjectUtils.isEmpty(txMgrBeanNames)) {
beanNamesConsumer.accept(txMgrBeanNames);
}
});
}
}

189
src/main/java/org/springframework/data/aot/AotContributingRepositoryBeanPostProcessor.java

@ -0,0 +1,189 @@ @@ -0,0 +1,189 @@
/*
* 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.aot;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
import org.springframework.data.repository.config.RepositoryMetadata;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* {@link AotContributingBeanPostProcessor} taking care of data repositories.
* <p>
* Post processes {@link RepositoryFactoryBeanSupport repository factory beans} to provide generic type information to
* AOT tooling to allow deriving target type from the {@link org.springframework.beans.factory.config.BeanDefinition
* bean definition}. If generic types to not match, due to customization of the factory bean by the user, at least the
* target repository type is provided via the {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE}.
* </p>
* <p>
* Via {@link #contribute(AotRepositoryContext, CodeContribution)} stores can provide custom logic for contributing
* additional (eg. reflection) configuration. By default reflection configuration will be added for types reachable from
* the repository declaration and query methods as well as all used {@link Annotation annotations} from the
* {@literal org.springframework.data} namespace.
* </p>
* The post processor is typically configured via {@link RepositoryConfigurationExtension#getAotPostProcessor()} and
* gets added by the {@link org.springframework.data.repository.config.RepositoryConfigurationDelegate}.
*
* @author Christoph Strobl
* @since 3.0
*/
public class AotContributingRepositoryBeanPostProcessor implements AotContributingBeanPostProcessor, BeanFactoryAware {
private static final Log logger = LogFactory.getLog(AotContributingBeanPostProcessor.class);
private ConfigurableListableBeanFactory beanFactory;
private Map<String, RepositoryMetadata<?>> configMap;
@Nullable
@Override
public RepositoryBeanContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
if (ObjectUtils.isEmpty(configMap) || !configMap.containsKey(beanName)) {
return null;
}
RepositoryMetadata<?> metadata = configMap.get(beanName);
Set<Class<? extends Annotation>> identifyingAnnotations = Collections.emptySet();
if (metadata.getConfigurationSource() instanceof RepositoryConfigurationExtensionSupport ces) {
identifyingAnnotations = new LinkedHashSet<>(ces.getIdentifyingAnnotations());
}
RepositoryInformation repositoryInformation = RepositoryBeanDefinitionReader.readRepositoryInformation(metadata,
beanFactory);
DefaultRepositoryContext ctx = new DefaultRepositoryContext();
ctx.setAotContext(() -> beanFactory);
ctx.setBeanName(beanName);
ctx.setBasePackages(metadata.getBasePackages().toSet());
ctx.setRepositoryInformation(repositoryInformation);
ctx.setIdentifyingAnnotations(identifyingAnnotations);
/*
* Help the AOT processing render the FactoryBean<T> type correctly that is used to tell the outcome of the FB.
* We just need to set the target repo type of the RepositoryFactoryBeanSupport while keeping the actual ID and DomainType set to object.
* If the generics do not match we do not try to resolve and remap them, but rather set the ObjectType attribute.
*/
if (logger.isDebugEnabled()) {
logger.debug(String.format("Enhancing repository factory bean definition %s.", beanName));
}
ResolvableType resolvedFactoryBean = ResolvableType.forClass(
ctx.resolveType(metadata.getRepositoryFactoryBeanClassName()).orElse(RepositoryFactoryBeanSupport.class));
if (resolvedFactoryBean.getGenerics().length == 3) {
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(
ctx.resolveType(metadata.getRepositoryFactoryBeanClassName()).orElse(RepositoryFactoryBeanSupport.class),
repositoryInformation.getRepositoryInterface(), Object.class, Object.class));
} else {
beanDefinition.setTargetType(resolvedFactoryBean);
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, repositoryInformation.getRepositoryInterface());
}
return new RepositoryBeanContribution(ctx).setModuleContribution(this::contribute);
}
protected void contribute(AotRepositoryContext ctx, CodeContribution contribution) {
ctx.getResolvedTypes() //
.stream() //
.filter(it -> !isJavaOrPrimitiveType(it)) //
.forEach(it -> contributeType(it, contribution));
ctx.getResolvedAnnotations().stream() //
.filter(AotContributingRepositoryBeanPostProcessor::isSpringDataManagedAnnotation) //
.map(MergedAnnotation::getType) //
.forEach(it -> contributeType(it, contribution));
}
protected static boolean isSpringDataManagedAnnotation(MergedAnnotation<?> annotation) {
if (isInDataNamespace(annotation.getType())) {
return true;
}
return annotation.getMetaTypes().stream().anyMatch(AotContributingRepositoryBeanPostProcessor::isInDataNamespace);
}
private static boolean isInDataNamespace(Class<?> type) {
return type.getPackage().getName().startsWith(TypeContributor.DATA_NAMESPACE);
}
private static boolean isJavaOrPrimitiveType(Class<?> type) {
if (TypeUtils.type(type).isPartOf("java") || type.isPrimitive() || ClassUtils.isPrimitiveArray(type)) {
return true;
}
return false;
}
protected void contributeType(Class<?> type, CodeContribution contribution) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Contributing type information for %s.", type));
}
TypeContributor.contribute(type, it -> true, contribution);
}
public Predicate<Class<?>> typeFilter() { // like only document ones.
return it -> true;
}
@Override
public int getOrder() {
return 0;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
throw new IllegalArgumentException(
"AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
}
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
public Map<String, RepositoryMetadata<?>> getConfigMap() {
return configMap;
}
public void setConfigMap(Map<String, RepositoryMetadata<?>> configMap) {
this.configMap = configMap;
}
}

69
src/main/java/org/springframework/data/aot/AotDataComponentsBeanFactoryPostProcessor.java

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
/*
* 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.aot;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.generator.AotContributingBeanFactoryPostProcessor;
import org.springframework.beans.factory.generator.BeanFactoryContribution;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.data.ManagedTypes;
import org.springframework.lang.Nullable;
/**
* {@link AotContributingBeanFactoryPostProcessor} implementation to capture common data infrastructure concerns.
*
* @author Christoph Strobl
* @since 3.0
*/
public class AotDataComponentsBeanFactoryPostProcessor implements AotContributingBeanFactoryPostProcessor {
private static final Log logger = LogFactory.getLog(AotContributingBeanFactoryPostProcessor.class);
@Nullable
@Override
public BeanFactoryContribution contribute(ConfigurableListableBeanFactory beanFactory) {
postProcessManagedTypes(beanFactory);
return null;
}
private void postProcessManagedTypes(ConfigurableListableBeanFactory beanFactory) {
if (beanFactory instanceof BeanDefinitionRegistry registry) {
for (String beanName : beanFactory.getBeanNamesForType(ManagedTypes.class)) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
ValueHolder argumentValue = beanDefinition.getConstructorArgumentValues().getArgumentValue(0, null, null, null);
if (argumentValue.getValue() instanceof Supplier supplier) {
if (logger.isDebugEnabled()) {
logger.info(String.format("Replacing ManagedType bean definition %s.", beanName));
}
registry.removeBeanDefinition(beanName);
registry.registerBeanDefinition(beanName, BeanDefinitionBuilder.rootBeanDefinition(ManagedTypes.class)
.setFactoryMethod("of").addConstructorArgValue(supplier.get()).getBeanDefinition());
}
}
}
}
}

150
src/main/java/org/springframework/data/aot/AotManagedTypesPostProcessor.java

@ -0,0 +1,150 @@ @@ -0,0 +1,150 @@
/*
* 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.aot;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.data.ManagedTypes;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link AotContributingBeanPostProcessor} handling {@link #getModulePrefix() prefixed} {@link ManagedTypes} instances.
* This allows to register store specific handling of discovered types.
*
* @author Christoph Strobl
* @since 3.0
*/
public class AotManagedTypesPostProcessor implements AotContributingBeanPostProcessor, BeanFactoryAware {
private static final Log logger = LogFactory.getLog(AotManagedTypesPostProcessor.class);
private BeanFactory beanFactory;
@Nullable private String modulePrefix;
@Nullable
@Override
public BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType,
String beanName) {
if (!ClassUtils.isAssignable(ManagedTypes.class, beanType) || !matchesPrefix(beanName)) {
return null;
}
return contribute(AotContext.context(beanFactory), beanFactory.getBean(beanName, ManagedTypes.class));
}
/**
* Hook to provide a customized flavor of {@link BeanInstantiationContribution}. By overriding this method calls to
* {@link #contributeType(ResolvableType, CodeContribution)} might no longer be issued.
*
* @param aotContext never {@literal null}.
* @param managedTypes never {@literal null}.
* @return new instance of {@link AotManagedTypesPostProcessor} or {@literal null} if nothing to do.
*/
@Nullable
protected BeanInstantiationContribution contribute(AotContext aotContext, ManagedTypes managedTypes) {
return new ManagedTypesContribution(aotContext, managedTypes, this::contributeType);
}
/**
* Hook to contribute configuration for a given {@literal type}.
*
* @param type never {@literal null}.
* @param contribution never {@literal null}.
*/
protected void contributeType(ResolvableType type, CodeContribution contribution) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Contributing type information for %s.", type.getType()));
}
TypeContributor.contribute(type.toClass(), Collections.singleton(TypeContributor.DATA_NAMESPACE), contribution);
TypeUtils.resolveUsedAnnotations(type.toClass()).forEach(annotation -> TypeContributor
.contribute(annotation.getType(), Collections.singleton(TypeContributor.DATA_NAMESPACE), contribution));
}
protected boolean matchesPrefix(String beanName) {
return StringUtils.startsWithIgnoreCase(beanName, getModulePrefix());
}
public String getModulePrefix() {
return modulePrefix;
}
public void setModulePrefix(@Nullable String modulePrefix) {
this.modulePrefix = modulePrefix;
}
@Override
public int getOrder() {
return 0;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
static class ManagedTypesContribution implements BeanInstantiationContribution {
private AotContext aotContext;
private ManagedTypes managedTypes;
private BiConsumer<ResolvableType, CodeContribution> contributionAction;
public ManagedTypesContribution(AotContext aotContext, ManagedTypes managedTypes,
BiConsumer<ResolvableType, CodeContribution> contributionAction) {
this.aotContext = aotContext;
this.managedTypes = managedTypes;
this.contributionAction = contributionAction;
}
@Override
public void applyTo(CodeContribution contribution) {
List<Class<?>> types = new ArrayList<>(100);
managedTypes.forEach(types::add);
if (types.isEmpty()) {
return;
}
TypeCollector.inspect(types).forEach(type -> {
contributionAction.accept(type, contribution);
});
}
public AotContext getAotContext() {
return aotContext;
}
}
}

58
src/main/java/org/springframework/data/aot/AotRepositoryContext.java

@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
/*
* 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.aot;
import java.lang.annotation.Annotation;
import java.util.Set;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.data.repository.core.RepositoryInformation;
/**
* @author Christoph Strobl
*/
public interface AotRepositoryContext extends AotContext {
/**
* @return the bean name of the repository / factory bean
*/
String getBeanName();
/**
* @return base packages to look for repositories.
*/
Set<String> getBasePackages();
/**
* @return metadata about the repository itself
*/
RepositoryInformation getRepositoryInformation();
/**
* @return the {@link Annotation} types used to identify domain types.
*/
Set<Class<? extends Annotation>> getIdentifyingAnnotations();
/**
* @return all types reachable from the repository.
*/
Set<Class<?>> getResolvedTypes();
/**
* @return all annotations reachable from the repository.
*/
Set<MergedAnnotation<Annotation>> getResolvedAnnotations();
}

76
src/main/java/org/springframework/data/aot/AotRepositoryInformation.java

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
/*
* 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.aot;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryInformationSupport;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.lang.Nullable;
/**
* {@link RepositoryInformation} based on {@link RepositoryMetadata} collected at build time.
*
* @author Christoph Strobl
* @since 3.0
*/
class AotRepositoryInformation extends RepositoryInformationSupport implements RepositoryInformation {
private final Supplier<Collection<RepositoryFragment<?>>> fragments;
AotRepositoryInformation(Supplier<RepositoryMetadata> repositoryMetadata, Supplier<Class<?>> repositoryBaseClass,
Supplier<Collection<RepositoryFragment<?>>> fragments) {
super(repositoryMetadata, repositoryBaseClass);
this.fragments = fragments;
}
@Override
public boolean isCustomMethod(Method method) {
// TODO:
return false;
}
@Override
public boolean isBaseClassMethod(Method method) {
// TODO
return false;
}
@Override
public Method getTargetClassMethod(Method method) {
// TODO
return method;
}
/**
* @return
* @since 3.0
*/
@Nullable
public Set<RepositoryFragment<?>> getFragments() {
return new LinkedHashSet<>(fragments.get());
}
}

132
src/main/java/org/springframework/data/aot/DefaultRepositoryContext.java

@ -0,0 +1,132 @@ @@ -0,0 +1,132 @@
/*
* 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.aot;
import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.data.aot.TypeScanner.Scanner;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.util.Lazy;
/**
* Default implementation of {@link AotRepositoryContext}
*
* @author Christoph Strobl
* @since 3.0
*/
class DefaultRepositoryContext implements AotRepositoryContext {
private AotContext aotContext;
private String beanName;
private java.util.Set<String> basePackages;
private RepositoryInformation repositoryInformation;
private Set<Class<? extends Annotation>> identifyingAnnotations;
private Lazy<Set<MergedAnnotation<Annotation>>> resolvedAnnotations = Lazy.of(this::discoverAnnotations);
private Lazy<Set<Class<?>>> managedTypes = Lazy.of(this::discoverTypes);
@Override
public ConfigurableListableBeanFactory getBeanFactory() {
return aotContext.getBeanFactory();
}
@Override
public String getBeanName() {
return beanName;
}
@Override
public Set<String> getBasePackages() {
return basePackages;
}
@Override
public RepositoryInformation getRepositoryInformation() {
return repositoryInformation;
}
@Override
public Set<Class<? extends Annotation>> getIdentifyingAnnotations() {
return identifyingAnnotations;
}
@Override
public Set<Class<?>> getResolvedTypes() {
return managedTypes.get();
}
@Override
public Set<MergedAnnotation<Annotation>> getResolvedAnnotations() {
return resolvedAnnotations.get();
}
public AotContext getAotContext() {
return aotContext;
}
public void setAotContext(AotContext aotContext) {
this.aotContext = aotContext;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public void setBasePackages(Set<String> basePackages) {
this.basePackages = basePackages;
}
public void setRepositoryInformation(RepositoryInformation repositoryInformation) {
this.repositoryInformation = repositoryInformation;
}
public void setIdentifyingAnnotations(Set<Class<? extends Annotation>> identifyingAnnotations) {
this.identifyingAnnotations = identifyingAnnotations;
}
protected Set<MergedAnnotation<Annotation>> discoverAnnotations() {
Set<MergedAnnotation<Annotation>> annotations = new LinkedHashSet<>(getResolvedTypes().stream().flatMap(type -> {
return TypeUtils.resolveUsedAnnotations(type).stream();
}).collect(Collectors.toSet()));
annotations.addAll(TypeUtils.resolveUsedAnnotations(repositoryInformation.getRepositoryInterface()));
return annotations;
}
protected Set<Class<?>> discoverTypes() {
Set<Class<?>> types = new LinkedHashSet<>(TypeCollector.inspect(repositoryInformation.getDomainType()).list());
repositoryInformation.getQueryMethods()
.flatMap(it -> TypeUtils.resolveTypesInSignature(repositoryInformation.getRepositoryInterface(), it).stream())
.flatMap(it -> TypeCollector.inspect(it).list().stream()).forEach(types::add);
if (!getIdentifyingAnnotations().isEmpty()) {
Scanner typeScanner = aotContext.getTypeScanner().scanForTypesAnnotatedWith(getIdentifyingAnnotations());
Set<Class<?>> classes = typeScanner.inPackages(getBasePackages());
TypeCollector.inspect(classes).list().stream().forEach(types::add);
}
// context.get
return types;
}
}

185
src/main/java/org/springframework/data/aot/RepositoryBeanContribution.java

@ -0,0 +1,185 @@ @@ -0,0 +1,185 @@
/*
* 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.aot;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.BiConsumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.projection.EntityProjectionIntrospector.ProjectionPredicate;
import org.springframework.data.projection.TargetAware;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
/**
* The {@link BeanInstantiationContribution} for a specific data repository.
*
* @author Christoph Strobl
* @since 3.0
*/
public class RepositoryBeanContribution implements BeanInstantiationContribution {
private static final Log logger = LogFactory.getLog(RepositoryBeanContribution.class);
private final AotRepositoryContext context;
private final RepositoryInformation repositoryInformation;
private BiConsumer<AotRepositoryContext, CodeContribution> moduleContribution;
public RepositoryBeanContribution(AotRepositoryContext context) {
this.context = context;
this.repositoryInformation = context.getRepositoryInformation();
}
@Override
public void applyTo(CodeContribution contribution) {
writeRepositoryInfo(contribution);
if (moduleContribution != null) {
moduleContribution.accept(context, contribution);
}
}
private void writeRepositoryInfo(CodeContribution contribution) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Contributing data repository information for %s.",
repositoryInformation.getRepositoryInterface()));
}
// TODO: is this the way?
contribution.runtimeHints().reflection() //
.registerType(repositoryInformation.getRepositoryInterface(), hint -> {
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
}) //
.registerType(repositoryInformation.getRepositoryBaseClass(), hint -> {
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
}) //
.registerType(repositoryInformation.getDomainType(), hint -> {
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
});
// fragments
for (RepositoryFragment<?> fragment : getRepositoryInformation().getFragments()) {
contribution.runtimeHints().reflection() //
.registerType(fragment.getSignatureContributor(), hint -> {
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
if (!fragment.getSignatureContributor().isInterface()) {
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
});
}
// the surrounding proxy
contribution.runtimeHints().proxies() // repository proxy
.registerJdkProxy(repositoryInformation.getRepositoryInterface(), SpringProxy.class, Advised.class,
DecoratingProxy.class);
context.ifTransactionManagerPresent(txMgrBeanNames -> {
contribution.runtimeHints().proxies() // transactional proxy
.registerJdkProxy(transactionalRepositoryProxy());
if (AnnotationUtils.findAnnotation(repositoryInformation.getRepositoryInterface(), Component.class) != null) {
TypeReference[] source = transactionalRepositoryProxy();
TypeReference[] txProxyForSerializableComponent = Arrays.copyOf(source, source.length + 1);
txProxyForSerializableComponent[source.length] = TypeReference.of(Serializable.class);
contribution.runtimeHints().proxies().registerJdkProxy(txProxyForSerializableComponent);
}
});
// reactive repo
if (repositoryInformation.isReactiveRepository()) {
// TODO: do we still need this and how to configure it?
// registry.initialization().add(NativeInitializationEntry.ofBuildTimeType(configuration.getRepositoryInterface()));
}
// Kotlin
Optional<Class<?>> coroutineRepo = context
.resolveType("org.springframework.data.repository.kotlin.CoroutineCrudRepository");
if (coroutineRepo.isPresent()
&& ClassUtils.isAssignable(coroutineRepo.get(), repositoryInformation.getRepositoryInterface())) {
contribution.runtimeHints().reflection() //
.registerTypes(
Arrays.asList(TypeReference.of("org.springframework.data.repository.kotlin.CoroutineCrudRepository"),
TypeReference.of(Repository.class), TypeReference.of(Iterable.class),
TypeReference.of("kotlinx.coroutines.flow.Flow"), TypeReference.of("kotlin.collections.Iterable"),
TypeReference.of("kotlin.Unit"), TypeReference.of("kotlin.Long"), TypeReference.of("kotlin.Boolean")),
hint -> {
hint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS);
});
}
// repository methods
repositoryInformation.getQueryMethods().map(repositoryInformation::getReturnedDomainClass)
.filter(Class::isInterface).forEach(type -> {
if (ProjectionPredicate.typeHierarchy().test(type, repositoryInformation.getDomainType())) {
contributeProjection(type, contribution);
}
});
}
private TypeReference[] transactionalRepositoryProxy() {
return new TypeReference[] { TypeReference.of(repositoryInformation.getRepositoryInterface()),
TypeReference.of(Repository.class),
TypeReference.of("org.springframework.transaction.interceptor.TransactionalProxy"),
TypeReference.of("org.springframework.aop.framework.Advised"), TypeReference.of(DecoratingProxy.class) };
}
protected void contributeProjection(Class<?> type, CodeContribution contribution) {
contribution.runtimeHints().proxies().registerJdkProxy(type, TargetAware.class, SpringProxy.class,
DecoratingProxy.class);
}
/**
* Callback for module specific contributions.
*
* @param moduleContribution can be {@literal null}.
* @return this.
*/
public RepositoryBeanContribution setModuleContribution(
@Nullable BiConsumer<AotRepositoryContext, CodeContribution> moduleContribution) {
this.moduleContribution = moduleContribution;
return this;
}
public RepositoryInformation getRepositoryInformation() {
return repositoryInformation;
}
}

88
src/main/java/org/springframework/data/aot/RepositoryBeanDefinitionReader.java

@ -0,0 +1,88 @@ @@ -0,0 +1,88 @@
/*
* 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.aot;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.data.repository.config.RepositoryFragmentConfiguration;
import org.springframework.data.repository.config.RepositoryMetadata;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.util.Lazy;
import org.springframework.util.ClassUtils;
/**
* Reader that allows to extract {@link RepositoryInformation} from metadata.
*
* @author Christoph Strobl
* @since 3.0
*/
class RepositoryBeanDefinitionReader {
static RepositoryInformation readRepositoryInformation(RepositoryMetadata metadata,
ConfigurableListableBeanFactory beanFactory) {
return new AotRepositoryInformation(metadataSupplier(metadata, beanFactory),
repositoryBaseClass(metadata, beanFactory), fragments(metadata, beanFactory));
}
private static Supplier<Collection<RepositoryFragment<?>>> fragments(RepositoryMetadata metadata,
ConfigurableListableBeanFactory beanFactory) {
return Lazy
.of(() -> (Collection<RepositoryFragment<?>>) metadata.getFragmentConfiguration().stream().flatMap(it -> {
RepositoryFragmentConfiguration fragmentConfiguration = (RepositoryFragmentConfiguration) it;
List<RepositoryFragment> fragments = new ArrayList<>(2);
if (fragmentConfiguration.getClassName() != null) {
fragments.add(RepositoryFragment.implemented(forName(fragmentConfiguration.getClassName(), beanFactory)));
}
if (fragmentConfiguration.getInterfaceName() != null) {
fragments
.add(RepositoryFragment.structural(forName(fragmentConfiguration.getInterfaceName(), beanFactory)));
}
return fragments.stream();
}).collect(Collectors.toList()));
}
private static Supplier<Class<?>> repositoryBaseClass(RepositoryMetadata metadata,
ConfigurableListableBeanFactory beanFactory) {
return Lazy.of(() -> (Class<?>) metadata.getRepositoryBaseClassName().map(it -> forName(it.toString(), beanFactory))
.orElseGet(() -> {
// TODO: retrieve the default without loading the actual RepositoryBeanFactory
return Object.class;
}));
}
static Supplier<org.springframework.data.repository.core.RepositoryMetadata> metadataSupplier(
RepositoryMetadata metadata, ConfigurableListableBeanFactory beanFactory) {
return Lazy.of(() -> new DefaultRepositoryMetadata(forName(metadata.getRepositoryInterface(), beanFactory)));
}
static Class<?> forName(String name, ConfigurableListableBeanFactory beanFactory) {
try {
return ClassUtils.forName(name, beanFactory.getBeanClassLoader());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}

243
src/main/java/org/springframework/data/aot/TypeCollector.java

@ -0,0 +1,243 @@ @@ -0,0 +1,243 @@
/*
* Copyright 2019-2021 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.aot;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.ResolvableType;
import org.springframework.data.util.Lazy;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* @author Christoph Strobl
* @author Sebastien Deleuze
*/
public class TypeCollector {
private static Log logger = LogFactory.getLog(TypeCollector.class);
static final Set<String> EXCLUDED_DOMAINS = new HashSet<>(Arrays.asList("java", "sun.", "jdk.", "reactor.",
"kotlinx.", "kotlin.", "org.springframework.core.", "org.springframework.boot."));
private Predicate<Class<?>> excludedDomainsFilter = (type) -> {
return EXCLUDED_DOMAINS.stream().noneMatch(type.getPackageName()::startsWith);
};
Predicate<Class<?>> typeFilter = excludedDomainsFilter;
private final Predicate<Method> methodFilter = (method) -> {
if (method.getName().startsWith("$$_hibernate")) {
return false;
}
if (method.getDeclaringClass().getPackageName().startsWith("java.") || method.getDeclaringClass().isEnum()
|| EXCLUDED_DOMAINS.stream().anyMatch(it -> method.getDeclaringClass().getPackageName().startsWith(it))) {
return false;
}
if (method.isBridge() || method.isSynthetic()) {
return false;
}
return (!Modifier.isNative(method.getModifiers()) && !Modifier.isPrivate(method.getModifiers())
&& !Modifier.isProtected(method.getModifiers())) || !method.getDeclaringClass().equals(Object.class);
};
private Predicate<Field> fieldFilter = (field) -> {
if (field.isSynthetic() | field.getName().startsWith("$$_hibernate")) {
return false;
}
if (field.getDeclaringClass().getPackageName().startsWith("java.")) {
return false;
}
return true;
};
public TypeCollector filterFields(Predicate<Field> filter) {
this.fieldFilter = filter.and(filter);
return this;
}
public TypeCollector filterTypes(Predicate<Class<?>> filter) {
this.typeFilter = this.typeFilter.and(filter);
return this;
}
/**
* Inspect the given type and resolve those reachable via fields, methods, generics, ...
*
* @param types the types to inspect
* @return a type model collector for the type
*/
public static ReachableTypes inspect(Class<?>... types) {
return inspect(Arrays.asList(types));
}
public static ReachableTypes inspect(Collection<Class<?>> types) {
return new ReachableTypes(new TypeCollector(), types);
}
private void process(Class<?> root, Consumer<ResolvableType> consumer) {
processType(ResolvableType.forType(root), new InspectionCache(), consumer);
}
private void processType(ResolvableType type, InspectionCache cache, Consumer<ResolvableType> callback) {
if (ResolvableType.NONE.equals(type) || cache.contains(type)) {
return;
}
cache.add(type);
// continue inspection but only add those matching the filter criteria to the result
if (typeFilter.test(type.toClass())) {
callback.accept(type);
}
Set<Type> additionalTypes = new LinkedHashSet<>();
additionalTypes.addAll(TypeUtils.resolveTypesInSignature(type));
additionalTypes.addAll(visitConstructorsOfType(type));
additionalTypes.addAll(visitMethodsOfType(type));
additionalTypes.addAll(visitFieldsOfType(type));
if (!ObjectUtils.isEmpty(type.toClass().getDeclaredClasses())) {
additionalTypes.addAll(Arrays.asList(type.toClass().getDeclaredClasses()));
}
for (Type discoveredType : additionalTypes) {
processType(ResolvableType.forType(discoveredType, type), cache, callback);
}
}
Set<Type> visitConstructorsOfType(ResolvableType type) {
if (!typeFilter.test(type.toClass())) {
return Collections.emptySet();
}
Set<Type> discoveredTypes = new LinkedHashSet<>();
for (Constructor<?> constructor : type.toClass().getDeclaredConstructors()) {
for (Class<?> signatureType : TypeUtils.resolveTypesInSignature(type.toClass(), constructor)) {
if (typeFilter.test(signatureType)) {
discoveredTypes.add(signatureType);
}
}
}
return new HashSet<>(discoveredTypes);
}
Set<Type> visitMethodsOfType(ResolvableType type) {
if (!typeFilter.test(type.toClass())) {
return Collections.emptySet();
}
Set<Type> discoveredTypes = new LinkedHashSet<>();
try {
ReflectionUtils.doWithLocalMethods(type.toClass(), method -> {
if (!methodFilter.test(method)) {
return;
}
for (Class<?> signatureType : TypeUtils.resolveTypesInSignature(type.toClass(), method)) {
if (typeFilter.test(signatureType)) {
discoveredTypes.add(signatureType);
}
}
});
} catch (Exception ex) {
logger.warn(ex);
}
return new HashSet<>(discoveredTypes);
}
Set<Type> visitFieldsOfType(ResolvableType type) {
Set<Type> discoveredTypes = new LinkedHashSet<>();
ReflectionUtils.doWithLocalFields(type.toClass(), field -> {
if (!fieldFilter.test(field)) {
return;
}
for (Class<?> signatureType : TypeUtils.resolveTypesInSignature(ResolvableType.forField(field, type))) {
if (typeFilter.test(signatureType)) {
discoveredTypes.add(signatureType);
}
}
});
return discoveredTypes;
}
public static class ReachableTypes {
private TypeCollector typeCollector;
private final Iterable<Class<?>> roots;
private final Lazy<List<Class<?>>> reachableTypes = Lazy.of(this::collect);
public ReachableTypes(TypeCollector typeCollector, Iterable<Class<?>> roots) {
this.typeCollector = typeCollector;
this.roots = roots;
}
public void forEach(Consumer<ResolvableType> consumer) {
roots.forEach(it -> typeCollector.process(it, consumer));
}
public List<Class<?>> list() {
return reachableTypes.get();
}
private List<Class<?>> collect() {
List<Class<?>> target = new ArrayList<>();
forEach(it -> target.add(it.toClass()));
return target;
}
}
static class InspectionCache {
private final Map<String, ResolvableType> mutableCache = new LinkedHashMap<>();
public void add(ResolvableType resolvableType) {
mutableCache.put(resolvableType.toString(), resolvableType);
}
public boolean contains(ResolvableType key) {
return mutableCache.containsKey(key.toString());
}
public int size() {
return mutableCache.size();
}
public boolean isEmpty() {
return mutableCache.isEmpty();
}
public void clear() {
mutableCache.clear();
}
}
}

108
src/main/java/org/springframework/data/aot/TypeContributor.java

@ -0,0 +1,108 @@ @@ -0,0 +1,108 @@
/*
* 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.aot;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.Set;
import java.util.function.Predicate;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.SynthesizedAnnotation;
/**
* @author Christoph Strobl
* @since 3.0
*/
class TypeContributor {
public static final String DATA_NAMESPACE = "org.springframework.data";
/**
* Contribute the type with default reflection configuration, skip annotations.
*
* @param type
* @param contribution
*/
static void contribute(Class<?> type, CodeContribution contribution) {
contribute(type, Collections.emptySet(), contribution);
}
/**
* Contribute the type with default reflection configuration and only include matching annotations.
*
* @param type
* @param filter
* @param contribution
*/
static void contribute(Class<?> type, Predicate<Class<? extends Annotation>> filter, CodeContribution contribution) {
if (type.isPrimitive()) {
return;
}
if (type.isAnnotation() && filter.test((Class<? extends Annotation>) type)) {
contribution.runtimeHints().reflection().registerType(type, hint -> {
hint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS);
});
// TODO: do we need this if meta annotated with SD annotation?
if (type.getPackage().getName().startsWith(DATA_NAMESPACE)) {
contribution.runtimeHints().proxies().registerJdkProxy(type, SynthesizedAnnotation.class);
}
return;
}
if (type.isInterface()) {
contribution.runtimeHints().reflection().registerType(type, hint -> {
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
});
return;
}
contribution.runtimeHints().reflection().registerType(type, hint -> {
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
});
}
/**
* Contribute the type with default reflection configuration and only include annotations from a certain namespace and
* those meta annotated with one of them.
*
* @param type
* @param annotationNamespaces
* @param contribution
*/
static void contribute(Class<?> type, Set<String> annotationNamespaces, CodeContribution contribution) {
contribute(type, it -> isPartOfOrMetaAnnotatedWith(it, annotationNamespaces), contribution);
}
private static boolean isPartOf(Class<?> type, Set<String> namespaces) {
return namespaces.stream().anyMatch(namespace -> type.getPackageName().startsWith(namespace));
}
protected static boolean isPartOfOrMetaAnnotatedWith(Class<? extends Annotation> annotation, Set<String> namespaces) {
if (isPartOf(annotation, namespaces)) {
return true;
}
return MergedAnnotation.of(annotation).getMetaTypes().stream().anyMatch(it -> isPartOf(annotation, namespaces));
}
}

107
src/main/java/org/springframework/data/aot/TypeScanner.java

@ -0,0 +1,107 @@ @@ -0,0 +1,107 @@
/*
* 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.aot;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;
/**
* @author Christoph Strobl
*/
public class TypeScanner { // TODO: replace this with AnnotatedTypeScanner maybe?
private final ClassLoader classLoader;
public TypeScanner(ClassLoader classLoader) {
this.classLoader = classLoader;
}
static TypeScanner scanner(ClassLoader classLoader) {
return new TypeScanner(classLoader);
}
public Scanner scanForTypesAnnotatedWith(Class<? extends Annotation>... annotations) {
return scanForTypesAnnotatedWith(Arrays.asList(annotations));
}
public Scanner scanForTypesAnnotatedWith(Collection<Class<? extends Annotation>> annotations) {
return new ScannerImpl().includeTypesAnnotatedWith(annotations);
}
public interface Scanner {
default Set<Class<?>> inPackages(String... packageNames) {
return inPackages(Arrays.asList(packageNames));
}
Set<Class<?>> inPackages(Collection<String> packageNames);
}
class ScannerImpl implements Scanner {
ClassPathScanningCandidateComponentProvider componentProvider;
public ScannerImpl() {
componentProvider = new ClassPathScanningCandidateComponentProvider(false);
componentProvider.setEnvironment(new StandardEnvironment());
componentProvider.setResourceLoader(new DefaultResourceLoader(classLoader));
}
ScannerImpl includeTypesAnnotatedWith(Collection<Class<? extends Annotation>> annotations) {
annotations.stream().map(AnnotationTypeFilter::new).forEach(componentProvider::addIncludeFilter);
return this;
}
@Override
public Set<Class<?>> inPackages(Collection<String> packageNames) {
Set<Class<?>> types = new LinkedHashSet<>();
packageNames.forEach(pkg -> {
componentProvider.findCandidateComponents(pkg).forEach(it -> {
resolveType(it.getBeanClassName()).ifPresent(types::add);
});
});
return types;
}
}
private Optional<Class<?>> resolveType(String typeName) {
if (!ClassUtils.isPresent(typeName, classLoader)) {
return Optional.empty();
}
try {
return Optional.of(ClassUtils.forName(typeName, classLoader));
} catch (ClassNotFoundException e) {
// just do nothing
}
return Optional.empty();
}
}

253
src/main/java/org/springframework/data/aot/TypeUtils.java

@ -0,0 +1,253 @@ @@ -0,0 +1,253 @@
/*
* Copyright 2019-2021 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.aot;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationFilter;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.annotation.RepeatableContainers;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
*/
public class TypeUtils {
/**
* Resolve ALL annotations present for a given type. Will inspect type, constructors, parameters, methods, fields,...
*
* @param type
* @return never {@literal null}.
*/
public static Set<MergedAnnotation<Annotation>> resolveUsedAnnotations(Class<?> type) {
Set<MergedAnnotation<Annotation>> annotations = new LinkedHashSet<>();
annotations.addAll(TypeUtils.resolveAnnotationsFor(type).collect(Collectors.toSet()));
for (Constructor<?> ctor : type.getDeclaredConstructors()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(ctor).collect(Collectors.toSet()));
for (Parameter parameter : ctor.getParameters()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(parameter).collect(Collectors.toSet()));
}
}
for (Field field : type.getDeclaredFields()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(field).collect(Collectors.toSet()));
}
try {
for (Method method : type.getDeclaredMethods()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(method).collect(Collectors.toSet()));
for (Parameter parameter : method.getParameters()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(parameter).collect(Collectors.toSet()));
}
}
} catch (NoClassDefFoundError e) {
// ignore an move on
}
return annotations;
}
public static Stream<MergedAnnotation<Annotation>> resolveAnnotationsFor(AnnotatedElement element) {
return resolveAnnotationsFor(element, AnnotationFilter.PLAIN);
}
public static Stream<MergedAnnotation<Annotation>> resolveAnnotationsFor(AnnotatedElement element,
AnnotationFilter filter) {
return MergedAnnotations
.from(element, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.standardRepeatables(), filter).stream();
}
public static Set<Class<?>> resolveAnnotationTypesFor(AnnotatedElement element, AnnotationFilter filter) {
return MergedAnnotations
.from(element, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.standardRepeatables(), filter).stream()
.map(MergedAnnotation::getType).collect(Collectors.toSet());
}
public static Set<Class<?>> resolveAnnotationTypesFor(AnnotatedElement element) {
return resolveAnnotationTypesFor(element, AnnotationFilter.PLAIN);
}
public static boolean isAnnotationFromOrMetaAnnotated(Class<? extends Annotation> annotation, String prefix) {
if (annotation.getPackage().getName().startsWith(prefix)) {
return true;
}
return TypeUtils.resolveAnnotationsFor(annotation)
.anyMatch(it -> it.getType().getPackage().getName().startsWith(prefix));
}
public static boolean hasAnnotatedField(Class<?> type, String annotationName) {
for (Field field : type.getDeclaredFields()) {
MergedAnnotations fieldAnnotations = MergedAnnotations.from(field);
boolean hasAnnotation = fieldAnnotations.get(annotationName).isPresent();
if (hasAnnotation) {
return true;
}
}
return false;
}
public static Set<Field> getAnnotatedField(Class<?> type, String annotationName) {
Set<Field> fields = new LinkedHashSet<>();
for (Field field : type.getDeclaredFields()) {
if (MergedAnnotations.from(field).get(annotationName).isPresent()) {
fields.add(field);
}
}
return fields;
}
public static Set<Class<?>> resolveTypesInSignature(Class<?> owner, Method method) {
Set<Class<?>> signature = new LinkedHashSet<>();
signature.addAll(resolveTypesInSignature(ResolvableType.forMethodReturnType(method, owner)));
for (Parameter parameter : method.getParameters()) {
signature
.addAll(resolveTypesInSignature(ResolvableType.forMethodParameter(MethodParameter.forParameter(parameter))));
}
return signature;
}
public static Set<Class<?>> resolveTypesInSignature(Class<?> owner, Constructor<?> constructor) {
Set<Class<?>> signature = new LinkedHashSet<>();
for (int i = 0; i < constructor.getParameterCount(); i++) {
signature.addAll(resolveTypesInSignature(ResolvableType.forConstructorParameter(constructor, i, owner)));
}
return signature;
}
public static Set<Class<?>> resolveTypesInSignature(Class<?> root) {
Set<Class<?>> signature = new LinkedHashSet<>();
resolveTypesInSignature(ResolvableType.forClass(root), signature);
return signature;
}
public static Set<Class<?>> resolveTypesInSignature(ResolvableType root) {
Set<Class<?>> signature = new LinkedHashSet<>();
resolveTypesInSignature(root, signature);
return signature;
}
private static void resolveTypesInSignature(ResolvableType current, Set<Class<?>> signatures) {
if (ResolvableType.NONE.equals(current) || ObjectUtils.nullSafeEquals(Void.TYPE, current.getType())
|| ObjectUtils.nullSafeEquals(Object.class, current.getType())) {
return;
}
if (signatures.contains(current.toClass())) {
return;
}
signatures.add(current.toClass());
resolveTypesInSignature(current.getSuperType(), signatures);
for (ResolvableType type : current.getGenerics()) {
resolveTypesInSignature(type, signatures);
}
for (ResolvableType type : current.getInterfaces()) {
resolveTypesInSignature(type, signatures);
}
}
public static TypeOps type(Class<?> type) {
return new TypeOpsImpl(type);
}
public interface TypeOps {
Class<?> getType();
default boolean isPartOf(String... packageNames) {
return isPartOf(TypeUtils.PackageFilter.of(packageNames));
}
default boolean isPartOf(PackageFilter... packageFilters) {
for (PackageFilter filter : packageFilters) {
if (filter.matches(getType().getName())) {
return true;
}
}
return false;
}
default Set<Class<?>> signatureTypes() {
return TypeUtils.resolveTypesInSignature(getType());
}
interface PackageFilter {
default boolean matches(Class<?> type) {
return matches(type.getName());
}
boolean matches(String typeName);
static PackageFilter of(String... packages) {
return TypeUtils.PackageFilter.of(packages);
}
}
}
private static class TypeOpsImpl implements TypeOps {
private Class<?> type;
TypeOpsImpl(Class<?> type) {
this.type = type;
}
public Class<?> getType() {
return type;
}
}
private static class PackageFilter implements TypeOps.PackageFilter {
Set<String> packageNames;
PackageFilter(Set<String> packageNames) {
this.packageNames = packageNames;
}
static PackageFilter of(String... packageNames) {
Set<String> target = new LinkedHashSet<>();
for (String pkgName : packageNames) {
target.add(pkgName.endsWith(".") ? pkgName : (pkgName + '.'));
}
return new PackageFilter(target);
}
@Override
public boolean matches(String typeName) {
for (String pgkName : packageNames) {
if (typeName.startsWith(pgkName)) {
return true;
}
}
return false;
}
}
}

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

@ -17,6 +17,8 @@ package org.springframework.data.repository.config; @@ -17,6 +17,8 @@ package org.springframework.data.repository.config;
import static org.springframework.beans.factory.config.BeanDefinition.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
@ -130,6 +132,44 @@ class RepositoryBeanDefinitionBuilder { @@ -130,6 +132,44 @@ class RepositoryBeanDefinitionBuilder {
return builder;
}
// TODO: merge that with the one that creates the BD
RepositoryMetadata buildMetadata(RepositoryConfiguration<?> configuration) {
ImplementationDetectionConfiguration config = configuration
.toImplementationDetectionConfiguration(metadataReaderFactory);
List<RepositoryFragmentConfiguration> repositoryFragmentConfigurationStream = fragmentMetadata.getFragmentInterfaces(configuration.getRepositoryInterface()) //
.map(it -> detectRepositoryFragmentConfiguration(it, config)) //
.flatMap(Optionals::toStream)
.collect(Collectors.toList());//
if(repositoryFragmentConfigurationStream.isEmpty()) {
ImplementationLookupConfiguration lookup = configuration.toLookupConfiguration(metadataReaderFactory);
Optional<AbstractBeanDefinition> beanDefinition = implementationDetector.detectCustomImplementation(lookup);
if(beanDefinition.isPresent()) {
repositoryFragmentConfigurationStream = new ArrayList<>(1);
List<String> interfaceNames = fragmentMetadata.getFragmentInterfaces(configuration.getRepositoryInterface()).collect(Collectors.toList());
String implClassName = beanDefinition.get().getBeanClassName();
try {
for (String iName : metadataReaderFactory.getMetadataReader(implClassName).getClassMetadata().getInterfaceNames()) {
if(interfaceNames.contains(iName)) {
repositoryFragmentConfigurationStream.add(new RepositoryFragmentConfiguration(iName, implClassName));
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
return new RepositoryMetadata(configuration, repositoryFragmentConfigurationStream);
}
private Optional<String> registerCustomImplementation(RepositoryConfiguration<?> configuration) {
ImplementationLookupConfiguration lookup = configuration.toLookupConfiguration(metadataReaderFactory);

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

@ -21,10 +21,14 @@ import java.util.Collection; @@ -21,10 +21,14 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
@ -43,7 +47,12 @@ import org.springframework.core.io.support.SpringFactoriesLoader; @@ -43,7 +47,12 @@ import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.log.LogMessage;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.metrics.StartupStep;
import org.springframework.data.ManagedTypes;
import org.springframework.data.aot.AotDataComponentsBeanFactoryPostProcessor;
import org.springframework.data.aot.TypeScanner;
import org.springframework.data.repository.config.RepositoryConfigurationDelegate.LazyRepositoryInjectionPointResolver.ManagedTypesBean;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.util.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StopWatch;
@ -65,7 +74,7 @@ public class RepositoryConfigurationDelegate { @@ -65,7 +74,7 @@ public class RepositoryConfigurationDelegate {
private static final String MULTIPLE_MODULES = "Multiple Spring Data modules found, entering strict repository configuration mode";
private static final String NON_DEFAULT_AUTOWIRE_CANDIDATE_RESOLVER = "Non-default AutowireCandidateResolver (%s) detected. Skipping the registration of LazyRepositoryInjectionPointResolver. Lazy repository injection will not be working";
static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";
static final String FACTORY_BEAN_OBJECT_TYPE = FactoryBean.OBJECT_TYPE_ATTRIBUTE; // "factoryBeanObjectType";
private static final Log logger = LogFactory.getLog(RepositoryConfigurationDelegate.class);
@ -159,6 +168,7 @@ public class RepositoryConfigurationDelegate { @@ -159,6 +168,7 @@ public class RepositoryConfigurationDelegate {
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode);
Map<String, RepositoryConfiguration<?>> configurationsByRepositoryName = new HashMap<>(configurations.size());
Map<String, RepositoryMetadata<?>> metadataMap = new HashMap<>(configurations.size());
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : configurations) {
@ -180,12 +190,13 @@ public class RepositoryConfigurationDelegate { @@ -180,12 +190,13 @@ public class RepositoryConfigurationDelegate {
String beanName = configurationSource.generateBeanName(beanDefinition);
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName, configuration.getRepositoryInterface(),
configuration.getRepositoryFactoryBeanClassName()));
logger.trace(LogMessage.format(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName,
configuration.getRepositoryInterface(), configuration.getRepositoryFactoryBeanClassName()));
}
beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
metadataMap.put(beanName, builder.buildMetadata(configuration));
beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}
@ -198,13 +209,66 @@ public class RepositoryConfigurationDelegate { @@ -198,13 +209,66 @@ public class RepositoryConfigurationDelegate {
repoScan.end();
if (logger.isInfoEnabled()) {
logger.info(LogMessage.format("Finished Spring Data repository scanning in %s ms. Found %s %s repository interfaces.", //
watch.getLastTaskTimeMillis(), configurations.size(), extension.getModuleName()));
logger.info(
LogMessage.format("Finished Spring Data repository scanning in %s ms. Found %s %s repository interfaces.", //
watch.getLastTaskTimeMillis(), configurations.size(), extension.getModuleName()));
}
// TODO: AOT Processing -> guard this one with a flag so it's not always present
registerAotComponents(registry, extension, metadataMap);
return definitions;
}
private void registerAotComponents(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension,
Map<String, RepositoryMetadata<?>> metadataMap) {
{ // overall general data bean factory postprocessor - TODO: move this to spring factories!!!
if (!registry.isBeanNameInUse(AotDataComponentsBeanFactoryPostProcessor.class.getName())) {
registry.registerBeanDefinition(AotDataComponentsBeanFactoryPostProcessor.class.getName(), BeanDefinitionBuilder
.rootBeanDefinition(AotDataComponentsBeanFactoryPostProcessor.class).getBeanDefinition());
}
}
{ // Managed types lookup if possible
if (extension instanceof RepositoryConfigurationExtensionSupport configExtensionSupport) {
String targetManagedTypesBeanName = String.format("%s.managed-types", extension.getModulePrefix());
if (!registry.isBeanNameInUse(targetManagedTypesBeanName)) {
// this needs to be lazy or we'd resolve types to early maybe
Supplier<Set<Class<?>>> args = new Supplier<Set<Class<?>>>() {
@Override
public Set<Class<?>> get() {
Set<String> packages = metadataMap.values().stream().flatMap(it -> it.getBasePackages().stream())
.collect(Collectors.toSet());
return new TypeScanner(resourceLoader.getClassLoader())
.scanForTypesAnnotatedWith(configExtensionSupport.getIdentifyingAnnotations()).inPackages(packages);
}
};
registry.registerBeanDefinition(targetManagedTypesBeanName, BeanDefinitionBuilder
.rootBeanDefinition(ManagedTypesBean.class).addConstructorArgValue(args).getBeanDefinition());
}
}
}
{ // module specific repository post processor
String aotRepoPostProcessorBeanName = String.format("data-%s.repository-post-processor" /* might be duplicate */,
extension.getModulePrefix());
if (!registry.isBeanNameInUse(aotRepoPostProcessorBeanName)) {
BeanDefinitionBuilder aotRepoPostProcessor = BeanDefinitionBuilder
.rootBeanDefinition(extension.getAotPostProcessor());
aotRepoPostProcessor.addPropertyValue("configMap", metadataMap);
registry.registerBeanDefinition(aotRepoPostProcessorBeanName, aotRepoPostProcessor.getBeanDefinition());
}
}
}
/**
* Registers a {@link LazyRepositoryInjectionPointResolver} over the default
* {@link ContextAnnotationAutowireCandidateResolver} to make injection points of lazy repositories lazy, too. Will
@ -325,10 +389,25 @@ public class RepositoryConfigurationDelegate { @@ -325,10 +389,25 @@ public class RepositoryConfigurationDelegate {
boolean lazyInit = configuration.isLazyInit();
if (lazyInit) {
logger.debug(LogMessage.format("Creating lazy injection proxy for %s…", configuration.getRepositoryInterface()));
logger
.debug(LogMessage.format("Creating lazy injection proxy for %s…", configuration.getRepositoryInterface()));
}
return lazyInit;
}
static class ManagedTypesBean implements ManagedTypes {
private Lazy<Set<Class<?>>> types;
public ManagedTypesBean(Supplier<Set<Class<?>>> types) {
this.types = Lazy.of(types);
}
@Override
public void forEach(Consumer<Class<?>> action) {
types.get().forEach(action);
}
}
}
}

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

@ -19,9 +19,12 @@ import java.util.Collection; @@ -19,9 +19,12 @@ import java.util.Collection;
import java.util.Locale;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.aot.AotContributingRepositoryBeanPostProcessor;
import org.springframework.util.StringUtils;
/**
* SPI to implement store specific extension to the repository bean definition registration process.
@ -50,7 +53,11 @@ public interface RepositoryConfigurationExtension { @@ -50,7 +53,11 @@ public interface RepositoryConfigurationExtension {
*
* @return will never be {@literal null}.
*/
String getModuleName();
default String getModuleName() {
return StringUtils.capitalize(getModulePrefix());
}
String getModulePrefix();
/**
* Returns all {@link RepositoryConfiguration}s obtained through the given {@link RepositoryConfigurationSource}.
@ -80,6 +87,15 @@ public interface RepositoryConfigurationExtension { @@ -80,6 +87,15 @@ public interface RepositoryConfigurationExtension {
*/
String getRepositoryFactoryBeanClassName();
/**
* @return the {@link AotContributingBeanPostProcessor} type responsible for contributing AOT/native configuration.
* Defaults to {@link AotContributingRepositoryBeanPostProcessor}. Must not be {@literal null}
* @since 3.0
*/
default Class<? extends AotContributingBeanPostProcessor> getAotPostProcessor() {
return AotContributingRepositoryBeanPostProcessor.class;
}
/**
* Callback to register additional bean definitions for a {@literal repositories} root node. This usually includes
* beans you have to set up once independently of the number of repositories to be created. Will be called before any

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

@ -59,11 +59,6 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit @@ -59,11 +59,6 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
private boolean noMultiStoreSupport = false;
@Override
public String getModuleName() {
return StringUtils.capitalize(getModulePrefix());
}
public <T extends RepositoryConfigurationSource> Collection<RepositoryConfiguration<T>> getRepositoryConfigurations(
T configSource, ResourceLoader loader) {
return getRepositoryConfigurations(configSource, loader, false);
@ -132,7 +127,7 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit @@ -132,7 +127,7 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
* @return
* @since 1.9
*/
protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
public Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
return Collections.emptySet();
}

122
src/main/java/org/springframework/data/repository/config/RepositoryMetadata.java

@ -0,0 +1,122 @@ @@ -0,0 +1,122 @@
/*
* 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.repository.config;
import java.util.List;
import java.util.Optional;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @since 3.0
*/
public class RepositoryMetadata<T extends RepositoryConfigurationSource> implements RepositoryConfiguration<T> {
RepositoryConfiguration<T> repositoryConfiguration;
List<RepositoryFragmentConfiguration> fragmentConfiguration;
public RepositoryMetadata(RepositoryConfiguration<T> repositoryConfiguration,
List<RepositoryFragmentConfiguration> fragmentConfiguration) {
this.repositoryConfiguration = repositoryConfiguration;
this.fragmentConfiguration = fragmentConfiguration;
}
@Override
public Streamable<String> getBasePackages() {
return repositoryConfiguration.getBasePackages();
}
@Override
public Streamable<String> getImplementationBasePackages() {
return repositoryConfiguration.getImplementationBasePackages();
}
@Override
public String getRepositoryInterface() {
return repositoryConfiguration.getRepositoryInterface();
}
@Override
public Object getQueryLookupStrategyKey() {
return repositoryConfiguration.getQueryLookupStrategyKey();
}
@Override
public Optional<String> getNamedQueriesLocation() {
return repositoryConfiguration.getNamedQueriesLocation();
}
@Override
public Optional<String> getRepositoryBaseClassName() {
return repositoryConfiguration.getRepositoryBaseClassName();
}
@Override
public String getRepositoryFactoryBeanClassName() {
return repositoryConfiguration.getRepositoryFactoryBeanClassName();
}
@Override
@Nullable
public Object getSource() {
return repositoryConfiguration.getSource();
}
@Override
public T getConfigurationSource() {
return repositoryConfiguration.getConfigurationSource();
}
@Override
public boolean isLazyInit() {
return repositoryConfiguration.isLazyInit();
}
@Override
public boolean isPrimary() {
return repositoryConfiguration.isPrimary();
}
@Override
public Streamable<TypeFilter> getExcludeFilters() {
return repositoryConfiguration.getExcludeFilters();
}
@Override
public ImplementationDetectionConfiguration toImplementationDetectionConfiguration(MetadataReaderFactory factory) {
return repositoryConfiguration.toImplementationDetectionConfiguration(factory);
}
@Override
public ImplementationLookupConfiguration toLookupConfiguration(MetadataReaderFactory factory) {
return repositoryConfiguration.toLookupConfiguration(factory);
}
@Override
@Nullable
public String getResourceDescription() {
return repositoryConfiguration.getResourceDescription();
}
public List<RepositoryFragmentConfiguration> getFragmentConfiguration() {
return fragmentConfiguration;
}
}

182
src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java

@ -0,0 +1,182 @@ @@ -0,0 +1,182 @@
/*
* 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.repository.core;
import static org.springframework.data.repository.util.ClassUtils.*;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.annotation.QueryAnnotation;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Common base class for {@link RepositoryInformation} that delays resolution of {@link RepositoryMetadata} and the
* repository base to the latest possible time.
*
* @author Christoph Strobl
* @since 3.0
*/
public abstract class RepositoryInformationSupport implements RepositoryInformation {
private final Supplier<RepositoryMetadata> metadata;
private final Supplier<Class<?>> repositoryBaseClass;
public RepositoryInformationSupport(Supplier<RepositoryMetadata> metadata, Supplier<Class<?>> repositoryBaseClass) {
Assert.notNull(metadata, "Repository metadata must not be null!");
Assert.notNull(repositoryBaseClass, "Repository base class must not be null!");
this.metadata = Lazy.of(metadata);
this.repositoryBaseClass = Lazy.of(repositoryBaseClass);
}
@Override
public Streamable<Method> getQueryMethods() {
Set<Method> result = new HashSet<>();
for (Method method : getRepositoryInterface().getMethods()) {
method = ClassUtils.getMostSpecificMethod(method, getRepositoryInterface());
if (isQueryMethodCandidate(method)) {
result.add(method);
}
}
return Streamable.of(Collections.unmodifiableSet(result));
}
private RepositoryMetadata getMetadata() {
return metadata.get();
}
@Override
public Class<?> getIdType() {
return getMetadata().getIdType();
}
@Override
public Class<?> getDomainType() {
return getMetadata().getDomainType();
}
@Override
public Class<?> getRepositoryInterface() {
return getMetadata().getRepositoryInterface();
}
@Override
public TypeInformation<?> getReturnType(Method method) {
return getMetadata().getReturnType(method);
}
@Override
public Class<?> getReturnedDomainClass(Method method) {
return getMetadata().getReturnedDomainClass(method);
}
@Override
public CrudMethods getCrudMethods() {
return getMetadata().getCrudMethods();
}
@Override
public boolean isPagingRepository() {
return getMetadata().isPagingRepository();
}
@Override
public Set<Class<?>> getAlternativeDomainTypes() {
return getMetadata().getAlternativeDomainTypes();
}
@Override
public boolean isReactiveRepository() {
return getMetadata().isReactiveRepository();
}
@Override
public Class<?> getRepositoryBaseClass() {
return repositoryBaseClass.get();
}
@Override
public boolean isQueryMethod(Method method) {
return getQueryMethods().stream().anyMatch(it -> it.equals(method));
}
@Override
public TypeInformation<?> getDomainTypeInformation() {
return getMetadata().getDomainTypeInformation();
}
@Override
public TypeInformation<?> getIdTypeInformation() {
return getMetadata().getIdTypeInformation();
}
@Override
public boolean hasCustomMethod() {
Class<?> repositoryInterface = getRepositoryInterface();
// No detection required if no typing interface was configured
if (isGenericRepositoryInterface(repositoryInterface)) {
return false;
}
for (Method method : repositoryInterface.getMethods()) {
if (isCustomMethod(method) && !isBaseClassMethod(method)) {
return true;
}
}
return false;
}
/**
* Checks whether the given method contains a custom store specific query annotation annotated with
* {@link QueryAnnotation}. The method-hierarchy is also considered in the search for the annotation.
*
* @param method
* @return
*/
protected boolean isQueryAnnotationPresentOn(Method method) {
return AnnotationUtils.findAnnotation(method, QueryAnnotation.class) != null;
}
/**
* Checks whether the given method is a query method candidate.
*
* @param method
* @return
*/
protected boolean isQueryMethodCandidate(Method method) {
return !method.isBridge() && !method.isDefault() //
&& !Modifier.isStatic(method.getModifiers()) //
&& (isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method));
}
}

9
src/main/java/org/springframework/data/repository/core/RepositoryMetadata.java

@ -19,6 +19,7 @@ import java.lang.reflect.Method; @@ -19,6 +19,7 @@ import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Set;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.util.TypeInformation;
@ -126,4 +127,12 @@ public interface RepositoryMetadata { @@ -126,4 +127,12 @@ public interface RepositoryMetadata {
* @since 2.0
*/
boolean isReactiveRepository();
/**
*
* @return
* @since 3.0
*
*/
Set<RepositoryFragment<?>> getFragments();
}

7
src/main/java/org/springframework/data/repository/core/support/AnnotationRepositoryMetadata.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.data.repository.core.support;
import java.util.function.Function;
import java.util.Collections;
import java.util.Set;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.repository.RepositoryDefinition;
@ -79,4 +81,9 @@ public class AnnotationRepositoryMetadata extends AbstractRepositoryMetadata { @@ -79,4 +81,9 @@ public class AnnotationRepositoryMetadata extends AbstractRepositoryMetadata {
return TypeInformation.of(extractor.apply(annotation));
}
@Override
public Set<RepositoryFragment<?>> getFragments() {
return Collections.emptySet();
}
}

139
src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java

@ -15,26 +15,19 @@ @@ -15,26 +15,19 @@
*/
package org.springframework.data.repository.core.support;
import static org.springframework.data.repository.util.ClassUtils.*;
import static org.springframework.util.ReflectionUtils.*;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.annotation.QueryAnnotation;
import org.springframework.data.repository.core.CrudMethods;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryInformationSupport;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Default implementation of {@link RepositoryInformation}.
@ -45,12 +38,10 @@ import org.springframework.util.ClassUtils; @@ -45,12 +38,10 @@ import org.springframework.util.ClassUtils;
* @author Christoph Strobl
* @author Alessandro Nistico
*/
class DefaultRepositoryInformation implements RepositoryInformation {
class DefaultRepositoryInformation extends RepositoryInformationSupport implements RepositoryInformation {
private final Map<Method, Method> methodCache = new ConcurrentHashMap<>();
private final RepositoryMetadata metadata;
private final Class<?> repositoryBaseClass;
private final RepositoryComposition composition;
private final RepositoryComposition baseComposition;
@ -64,33 +55,15 @@ class DefaultRepositoryInformation implements RepositoryInformation { @@ -64,33 +55,15 @@ class DefaultRepositoryInformation implements RepositoryInformation {
public DefaultRepositoryInformation(RepositoryMetadata metadata, Class<?> repositoryBaseClass,
RepositoryComposition composition) {
Assert.notNull(metadata, "Repository metadata must not be null");
Assert.notNull(repositoryBaseClass, "Repository base class must not be null");
super(() -> metadata, () -> repositoryBaseClass);
Assert.notNull(composition, "Repository composition must not be null");
this.metadata = metadata;
this.repositoryBaseClass = repositoryBaseClass;
this.composition = composition;
this.baseComposition = RepositoryComposition.of(RepositoryFragment.structural(repositoryBaseClass)) //
.withArgumentConverter(composition.getArgumentConverter()) //
.withMethodLookup(composition.getMethodLookup());
}
@Override
public TypeInformation<?> getDomainTypeInformation() {
return metadata.getDomainTypeInformation();
}
@Override
public TypeInformation<?> getIdTypeInformation() {
return metadata.getIdTypeInformation();
}
@Override
public Class<?> getRepositoryBaseClass() {
return this.repositoryBaseClass;
}
@Override
public Method getTargetClassMethod(Method method) {
@ -117,55 +90,11 @@ class DefaultRepositoryInformation implements RepositoryInformation { @@ -117,55 +90,11 @@ class DefaultRepositoryInformation implements RepositoryInformation {
return value;
}
@Override
public Streamable<Method> getQueryMethods() {
Set<Method> result = new HashSet<>();
for (Method method : getRepositoryInterface().getMethods()) {
method = ClassUtils.getMostSpecificMethod(method, getRepositoryInterface());
if (isQueryMethodCandidate(method)) {
result.add(method);
}
}
return Streamable.of(Collections.unmodifiableSet(result));
}
/**
* Checks whether the given method is a query method candidate.
*
* @param method
* @return
*/
private boolean isQueryMethodCandidate(Method method) {
return !method.isBridge() && !method.isDefault() //
&& !Modifier.isStatic(method.getModifiers()) //
&& (isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method));
}
/**
* Checks whether the given method contains a custom store specific query annotation annotated with
* {@link QueryAnnotation}. The method-hierarchy is also considered in the search for the annotation.
*
* @param method
* @return
*/
private boolean isQueryAnnotationPresentOn(Method method) {
return AnnotationUtils.findAnnotation(method, QueryAnnotation.class) != null;
}
@Override
public boolean isCustomMethod(Method method) {
return composition.getMethod(method) != null;
}
@Override
public boolean isQueryMethod(Method method) {
return getQueryMethods().stream().anyMatch(it -> it.equals(method));
}
@Override
public boolean isBaseClassMethod(Method method) {
@ -173,57 +102,15 @@ class DefaultRepositoryInformation implements RepositoryInformation { @@ -173,57 +102,15 @@ class DefaultRepositoryInformation implements RepositoryInformation {
return baseComposition.getMethod(method) != null;
}
@Override
public boolean hasCustomMethod() {
Class<?> repositoryInterface = getRepositoryInterface();
// No detection required if no typing interface was configured
if (isGenericRepositoryInterface(repositoryInterface)) {
return false;
}
for (Method method : repositoryInterface.getMethods()) {
if (isCustomMethod(method) && !isBaseClassMethod(method)) {
return true;
}
}
return false;
}
@Override
public Class<?> getRepositoryInterface() {
return metadata.getRepositoryInterface();
}
@Override
public Class<?> getReturnedDomainClass(Method method) {
return metadata.getReturnedDomainClass(method);
}
@Override
public TypeInformation<?> getReturnType(Method method) {
return metadata.getReturnType(method);
}
@Override
public CrudMethods getCrudMethods() {
return metadata.getCrudMethods();
}
@Override
public boolean isPagingRepository() {
return metadata.isPagingRepository();
}
@Override
public Set<Class<?>> getAlternativeDomainTypes() {
return metadata.getAlternativeDomainTypes();
/**
*
* @return
* @since 3.0
*
*/
@Nullable
public Set<RepositoryFragment<?>> getFragments() {
return composition.getFragments().toSet();
}
@Override
public boolean isReactiveRepository() {
return metadata.isReactiveRepository();
}
}

7
src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMetadata.java

@ -15,7 +15,9 @@ @@ -15,7 +15,9 @@
*/
package org.springframework.data.repository.core.support;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.data.repository.Repository;
@ -79,4 +81,9 @@ public class DefaultRepositoryMetadata extends AbstractRepositoryMetadata { @@ -79,4 +81,9 @@ public class DefaultRepositoryMetadata extends AbstractRepositoryMetadata {
return arguments.get(index);
}
@Override
public Set<RepositoryFragment<?>> getFragments() {
return Collections.emptySet();
}
}

8
src/main/java/org/springframework/data/repository/core/support/RepositoryFragment.java

@ -193,7 +193,13 @@ public interface RepositoryFragment<T> { @@ -193,7 +193,13 @@ public interface RepositoryFragment<T> {
@SuppressWarnings({ "rawtypes", "unchecked" })
public Class<?> getSignatureContributor() {
return interfaceClass.orElse((Class) implementation.getClass());
return interfaceClass.orElseGet(() -> {
if(implementation instanceof Class type) {
return type;
}
return (Class<T>) implementation.getClass();
});
}
@Override

322
src/test/java/org/springframework/data/aot/AotContributingRepositoryBeanPostProcessorTests.java

@ -0,0 +1,322 @@ @@ -0,0 +1,322 @@
/*
* 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.aot;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.aot.RepositoryBeanContributionAssert.*;
import java.io.Serializable;
import org.junit.jupiter.api.Test;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.annotation.SynthesizedAnnotation;
import org.springframework.data.annotation.QueryAnnotation;
import org.springframework.data.aot.sample.ConfigWithCustomImplementation;
import org.springframework.data.aot.sample.ConfigWithCustomRepositoryBaseClass;
import org.springframework.data.aot.sample.ConfigWithFragments;
import org.springframework.data.aot.sample.ConfigWithQueryMethods;
import org.springframework.data.aot.sample.ConfigWithQueryMethods.ProjectionInterface;
import org.springframework.data.aot.sample.ConfigWithSimpleCrudRepository;
import org.springframework.data.aot.sample.ConfigWithTransactionManagerPresent;
import org.springframework.data.aot.sample.ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty;
import org.springframework.data.aot.sample.ReactiveConfig;
import org.springframework.data.domain.Page;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
import org.springframework.transaction.interceptor.TransactionalProxy;
/**
* @author Christoph Strobl
*/
public class AotContributingRepositoryBeanPostProcessorTests {
@Test // GH-2593
void simpleRepositoryNoTxManagerNoKotlinNoReactiveNoComponent() {
RepositoryBeanContribution repositoryBeanContribution = computeConfiguration(ConfigWithSimpleCrudRepository.class)
.forRepository(ConfigWithSimpleCrudRepository.MyRepo.class);
assertThatContribution(repositoryBeanContribution) //
.targetRepositoryTypeIs(ConfigWithSimpleCrudRepository.MyRepo.class) //
.hasNoFragments() //
.codeContributionSatisfies(contribution -> { //
contribution.contributesReflectionFor(ConfigWithSimpleCrudRepository.MyRepo.class) // repository interface
.contributesReflectionFor(PagingAndSortingRepository.class) // base repository
.contributesReflectionFor(ConfigWithSimpleCrudRepository.Person.class) // repository domain type
.contributesJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, SpringProxy.class, Advised.class,
DecoratingProxy.class) //
.doesNotContributeJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, Repository.class,
TransactionalProxy.class, Advised.class, DecoratingProxy.class)
.doesNotContributeJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, Repository.class,
TransactionalProxy.class, Advised.class, DecoratingProxy.class, Serializable.class);
});
}
@Test // GH-2593
void simpleRepositoryWithTxManagerNoKotlinNoReactiveNoComponent() {
RepositoryBeanContribution repositoryBeanContribution = computeConfiguration(
ConfigWithTransactionManagerPresent.class).forRepository(ConfigWithTransactionManagerPresent.MyTxRepo.class);
assertThatContribution(repositoryBeanContribution) //
.targetRepositoryTypeIs(ConfigWithTransactionManagerPresent.MyTxRepo.class) //
.hasNoFragments() //
.codeContributionSatisfies(contribution -> { //
contribution.contributesReflectionFor(ConfigWithTransactionManagerPresent.MyTxRepo.class) // repository
// interface
.contributesReflectionFor(PagingAndSortingRepository.class) // base repository
.contributesReflectionFor(ConfigWithTransactionManagerPresent.Person.class) // repository domain type
// proxies
.contributesJdkProxy(ConfigWithTransactionManagerPresent.MyTxRepo.class, SpringProxy.class, Advised.class,
DecoratingProxy.class)
.contributesJdkProxy(ConfigWithTransactionManagerPresent.MyTxRepo.class, Repository.class,
TransactionalProxy.class, Advised.class, DecoratingProxy.class)
.doesNotContributeJdkProxy(ConfigWithTransactionManagerPresent.MyTxRepo.class, Repository.class,
TransactionalProxy.class, Advised.class, DecoratingProxy.class, Serializable.class);
});
}
@Test // GH-2593
void simpleRepositoryWithTxManagerNoKotlinNoReactiveButComponent() {
RepositoryBeanContribution repositoryBeanContribution = computeConfiguration(
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.class)
.forRepository(ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.MyComponentTxRepo.class);
assertThatContribution(repositoryBeanContribution) //
.targetRepositoryTypeIs(
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.MyComponentTxRepo.class) //
.hasNoFragments() //
.codeContributionSatisfies(contribution -> { //
contribution
.contributesReflectionFor(
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.MyComponentTxRepo.class) // repository
// interface
.contributesReflectionFor(PagingAndSortingRepository.class) // base repository
.contributesReflectionFor(
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.Person.class) // repository domain
// type
// proxies
.contributesJdkProxy(
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.MyComponentTxRepo.class,
SpringProxy.class, Advised.class, DecoratingProxy.class)
.contributesJdkProxy(
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.MyComponentTxRepo.class,
Repository.class, TransactionalProxy.class, Advised.class, DecoratingProxy.class)
.contributesJdkProxy(
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.MyComponentTxRepo.class,
Repository.class, TransactionalProxy.class, Advised.class, DecoratingProxy.class, Serializable.class);
});
}
@Test // GH-2593
void contributesFragmentsCorrectly() {
RepositoryBeanContribution repositoryBeanContribution = computeConfiguration(ConfigWithFragments.class)
.forRepository(ConfigWithFragments.RepositoryWithFragments.class);
assertThatContribution(repositoryBeanContribution) //
.targetRepositoryTypeIs(ConfigWithFragments.RepositoryWithFragments.class) //
.hasFragments() //
.codeContributionSatisfies(contribution -> { //
contribution.contributesReflectionFor(ConfigWithFragments.RepositoryWithFragments.class) // repository
// interface
.contributesReflectionFor(PagingAndSortingRepository.class) // base repository
.contributesReflectionFor(ConfigWithFragments.Person.class) // repository domain type
// fragments
.contributesReflectionFor(ConfigWithFragments.CustomImplInterface1.class,
ConfigWithFragments.CustomImplInterface1Impl.class)
.contributesReflectionFor(ConfigWithFragments.CustomImplInterface2.class,
ConfigWithFragments.CustomImplInterface2Impl.class)
// proxies
.contributesJdkProxy(ConfigWithFragments.RepositoryWithFragments.class, SpringProxy.class, Advised.class,
DecoratingProxy.class)
.doesNotContributeJdkProxy(
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.MyComponentTxRepo.class,
Repository.class, TransactionalProxy.class, Advised.class, DecoratingProxy.class)
.doesNotContributeJdkProxy(
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.MyComponentTxRepo.class,
Repository.class, TransactionalProxy.class, Advised.class, DecoratingProxy.class, Serializable.class);
});
}
@Test // GH-2593
void contributesCustomImplementationCorrectly() {
RepositoryBeanContribution repositoryBeanContribution = computeConfiguration(ConfigWithCustomImplementation.class)
.forRepository(ConfigWithCustomImplementation.RepositoryWithCustomImplementation.class);
assertThatContribution(repositoryBeanContribution) //
.targetRepositoryTypeIs(ConfigWithCustomImplementation.RepositoryWithCustomImplementation.class) //
.hasFragments() //
.codeContributionSatisfies(contribution -> { //
contribution.contributesReflectionFor(ConfigWithCustomImplementation.RepositoryWithCustomImplementation.class) // repository
// interface
.contributesReflectionFor(PagingAndSortingRepository.class) // base repository
.contributesReflectionFor(ConfigWithCustomImplementation.Person.class) // repository domain type
// fragments
.contributesReflectionFor(ConfigWithCustomImplementation.CustomImplInterface.class,
ConfigWithCustomImplementation.RepositoryWithCustomImplementationImpl.class);
});
}
@Test // GH-2593
void contributesDomainTypeAndReachablesCorrectly() {
RepositoryBeanContribution repositoryBeanContribution = computeConfiguration(ConfigWithSimpleCrudRepository.class)
.forRepository(ConfigWithSimpleCrudRepository.MyRepo.class);
assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> {
contribution.contributesReflectionFor(ConfigWithSimpleCrudRepository.Person.class,
ConfigWithSimpleCrudRepository.Address.class);
});
}
@Test // GH-2593
void contributesReactiveRepositoryCorrectly() {
RepositoryBeanContribution repositoryBeanContribution = computeConfiguration(ReactiveConfig.class)
.forRepository(ReactiveConfig.CustomerRepositoryReactive.class);
assertThatContribution(repositoryBeanContribution) //
.targetRepositoryTypeIs(ReactiveConfig.CustomerRepositoryReactive.class) //
.hasNoFragments() //
.codeContributionSatisfies(contribution -> { //
// interface
contribution.contributesReflectionFor(ReactiveConfig.CustomerRepositoryReactive.class) // repository
.contributesReflectionFor(ReactiveSortingRepository.class) // base repo class
.contributesReflectionFor(ReactiveConfig.Person.class); // repository domain type
});
}
@Test // GH-2593
void contributesRepositoryBaseClassCorrectly() {
RepositoryBeanContribution repositoryBeanContribution = computeConfiguration(
ConfigWithCustomRepositoryBaseClass.class)
.forRepository(ConfigWithCustomRepositoryBaseClass.CustomerRepositoryWithCustomBaseRepo.class);
assertThatContribution(repositoryBeanContribution) //
.targetRepositoryTypeIs(ConfigWithCustomRepositoryBaseClass.CustomerRepositoryWithCustomBaseRepo.class) //
.hasNoFragments() //
.codeContributionSatisfies(contribution -> { //
// interface
contribution
.contributesReflectionFor(ConfigWithCustomRepositoryBaseClass.CustomerRepositoryWithCustomBaseRepo.class) // repository
.contributesReflectionFor(ConfigWithCustomRepositoryBaseClass.RepoBaseClass.class) // base repo class
.contributesReflectionFor(ConfigWithCustomRepositoryBaseClass.Person.class); // repository domain type
});
}
@Test // GH-2593
void contributesTypesFromQueryMethods() {
RepositoryBeanContribution repositoryBeanContribution = computeConfiguration(ConfigWithQueryMethods.class)
.forRepository(ConfigWithQueryMethods.CustomerRepositoryWithQueryMethods.class);
assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> {
contribution.contributesReflectionFor(ProjectionInterface.class);
});
}
@Test // GH-2593
void contributesProxiesForPotentialProjections() {
RepositoryBeanContribution repositoryBeanContribution = computeConfiguration(ConfigWithQueryMethods.class)
.forRepository(ConfigWithQueryMethods.CustomerRepositoryWithQueryMethods.class);
assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> {
contribution.contributesJdkProxyFor(ProjectionInterface.class);
contribution.doesNotContributeJdkProxyFor(Page.class);
contribution.doesNotContributeJdkProxyFor(ConfigWithQueryMethods.Person.class);
});
}
@Test // GH-2593
void contributesProxiesForDataAnnotations() {
RepositoryBeanContribution repositoryBeanContribution = computeConfiguration(ConfigWithQueryMethods.class)
.forRepository(ConfigWithQueryMethods.CustomerRepositoryWithQueryMethods.class);
assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> {
contribution.contributesJdkProxy(Param.class, SynthesizedAnnotation.class);
contribution.contributesJdkProxy(ConfigWithQueryMethods.CustomQuery.class, SynthesizedAnnotation.class);
contribution.contributesJdkProxy(QueryAnnotation.class, SynthesizedAnnotation.class);
});
}
@Test // GH-2593
void doesNotCareAboutNonDataAnnotations() {
RepositoryBeanContribution repositoryBeanContribution = computeConfiguration(ConfigWithSimpleCrudRepository.class)
.forRepository(ConfigWithSimpleCrudRepository.MyRepo.class);
assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> {
contribution.doesNotContributeReflectionFor(javax.annotation.Nullable.class);
contribution.doesNotContributeJdkProxyFor(javax.annotation.Nullable.class);
});
}
BeanContributionBuilder computeConfiguration(Class<?> configuration, AnnotationConfigApplicationContext ctx) {
ctx.register(configuration);
ctx.refreshForAotProcessing();
return it -> {
String[] repoBeanNames = ctx.getBeanNamesForType(it);
assertThat(repoBeanNames).describedAs("Unable to find repository %s in configuration %s.", it, configuration)
.hasSize(1);
String beanName = repoBeanNames[0];
BeanDefinition beanDefinition = ctx.getBeanDefinition(beanName);
AotContributingRepositoryBeanPostProcessor postProcessor = ctx
.getBean(AotContributingRepositoryBeanPostProcessor.class);
postProcessor.setBeanFactory(ctx.getDefaultListableBeanFactory());
return postProcessor.contribute((RootBeanDefinition) beanDefinition, it, beanName);
};
}
BeanContributionBuilder computeConfiguration(Class<?> configuration) {
return computeConfiguration(configuration, new AnnotationConfigApplicationContext());
}
interface BeanContributionBuilder {
RepositoryBeanContribution forRepository(Class<?> repositoryInterface);
}
}

81
src/test/java/org/springframework/data/aot/AotDataComponentsBeanFactoryPostProcessorUnitTests.java

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
/*
* 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.aot;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.data.ManagedTypes;
/**
* @author Christoph Strobl
*/
class AotDataComponentsBeanFactoryPostProcessorUnitTests {
@Test // Gh-2593
void replacesManagedTypesBeanDefinitionUsingSupplierForCtorValue() {
Supplier<Iterable<Class<?>>> typesSupplier = mock(Supplier.class);
Mockito.when(typesSupplier.get()).thenReturn(Collections.singleton(DomainType.class));
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("data.managed-types", BeanDefinitionBuilder
.rootBeanDefinition(ManagedTypes.class).addConstructorArgValue(typesSupplier).getBeanDefinition());
new AotDataComponentsBeanFactoryPostProcessor().contribute(beanFactory);
assertThat(beanFactory.getBeanNamesForType(ManagedTypes.class)).hasSize(1);
verify(typesSupplier).get();
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("data.managed-types");
assertThat(beanDefinition.getFactoryMethodName()).isEqualTo("of");
assertThat(beanDefinition.hasConstructorArgumentValues()).isTrue();
assertThat(beanDefinition.getConstructorArgumentValues().getArgumentValue(0, null).getValue())
.isEqualTo(Collections.singleton(DomainType.class));
}
@Test // Gh-2593
void leavesManagedTypesBeanDefinitionNotUsingSupplierForCtorValue() {
Iterable<Class<?>> types = spy(new LinkedHashSet<>(Collections.singleton(DomainType.class)));
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition sourceBD = BeanDefinitionBuilder.rootBeanDefinition(ManagedTypes.class)
.addConstructorArgValue(types).getBeanDefinition();
beanFactory.registerBeanDefinition("data.managed-types", sourceBD);
new AotDataComponentsBeanFactoryPostProcessor().contribute(beanFactory);
assertThat(beanFactory.getBeanNamesForType(ManagedTypes.class)).hasSize(1);
verifyNoInteractions(types);
assertThat(beanFactory.getBeanDefinition("data.managed-types")).isSameAs(sourceBD);
}
private static class DomainType {}
}

129
src/test/java/org/springframework/data/aot/AotManagedTypesPostProcessorUnitTests.java

@ -0,0 +1,129 @@ @@ -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.aot;
import static org.assertj.core.api.Assertions.*;
import java.util.Collections;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generator.DefaultCodeContribution;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.data.ManagedTypes;
/**
* @author Christoph Strobl
*/
class AotManagedTypesPostProcessorUnitTests {
final RootBeanDefinition managedTypesDefinition = (RootBeanDefinition) BeanDefinitionBuilder
.rootBeanDefinition(ManagedTypes.class).setFactoryMethod("of")
.addConstructorArgValue(Collections.singleton(A.class)).getBeanDefinition();
final RootBeanDefinition myManagedTypesDefinition = (RootBeanDefinition) BeanDefinitionBuilder
.rootBeanDefinition(MyManagedTypes.class).getBeanDefinition();
@Test // GH-2593
void processesBeanWithMatchingModulePrefix() {
BeanInstantiationContribution contribution = createPostProcessor("commons", bf -> {
bf.registerBeanDefinition("commons.managed-types", managedTypesDefinition);
}).contribute(managedTypesDefinition, ManagedTypes.class, "commons.managed-types");
assertThat(contribution).isNotNull();
}
@Test // GH-2593
void contributesReflectionForManagedTypes() {
BeanInstantiationContribution contribution = createPostProcessor("commons", bf -> {
bf.registerBeanDefinition("commons.managed-types", managedTypesDefinition);
}).contribute(managedTypesDefinition, ManagedTypes.class, "commons.managed-types");
DefaultCodeContribution codeContribution = new DefaultCodeContribution(new RuntimeHints());
contribution.applyTo(codeContribution);
new CodeContributionAssert(codeContribution) //
.contributesReflectionFor(A.class) //
.doesNotContributeReflectionFor(B.class);
}
@Test // GH-2593
void processesMatchingSubtypeBean() {
BeanInstantiationContribution contribution = createPostProcessor("commons", bf -> {
bf.registerBeanDefinition("commons.managed-types", myManagedTypesDefinition);
}).contribute(myManagedTypesDefinition, MyManagedTypes.class, "commons.managed-types");
assertThat(contribution).isNotNull();
}
@Test // GH-2593
void ignoresBeanNotMatchingRequiredType() {
BeanInstantiationContribution contribution = createPostProcessor("commons", bf -> {
bf.registerBeanDefinition("commons.managed-types", managedTypesDefinition);
}).contribute(managedTypesDefinition, Object.class, "commons.managed-types");
assertThat(contribution).isNull();
}
@Test // GH-2593
void ignoresBeanNotMatchingPrefix() {
BeanInstantiationContribution contribution = createPostProcessor("commons", bf -> {
bf.registerBeanDefinition("commons.managed-types", managedTypesDefinition);
}).contribute(managedTypesDefinition, ManagedTypes.class, "jpa.managed-types");
assertThat(contribution).isNull();
}
private AotManagedTypesPostProcessor createPostProcessor(String prefix, Consumer<DefaultListableBeanFactory> action) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
action.accept(beanFactory);
AotManagedTypesPostProcessor postProcessor = createPostProcessor(beanFactory);
postProcessor.setModulePrefix(prefix);
return postProcessor;
}
private AotManagedTypesPostProcessor createPostProcessor(BeanFactory beanFactory) {
AotManagedTypesPostProcessor aotManagedTypesPostProcessor = new AotManagedTypesPostProcessor();
aotManagedTypesPostProcessor.setBeanFactory(beanFactory);
return aotManagedTypesPostProcessor;
}
static class A {}
static class B {}
static class MyManagedTypes implements ManagedTypes {
@Override
public void forEach(Consumer<Class<?>> action) {
// just do nothing ¯\_(ツ)_/¯
}
}
}

45
src/test/java/org/springframework/data/aot/ClassProxyAssert.java

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
/*
* 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.aot;
import static org.assertj.core.api.Assertions.*;
import java.util.Arrays;
import java.util.List;
import org.assertj.core.api.AbstractAssert;
import org.springframework.aot.hint.ClassProxyHint;
import org.springframework.aot.hint.TypeReference;
/**
* @author Christoph Strobl
* @since 2022/04
*/
public class ClassProxyAssert extends AbstractAssert<ClassProxyAssert, ClassProxyHint> {
protected ClassProxyAssert(ClassProxyHint classProxyHint) {
super(classProxyHint, ClassProxyAssert.class);
}
public void matches(Class<?>... proxyInterfaces) {
assertThat(actual.getProxiedInterfaces().stream().map(TypeReference::getCanonicalName))
.containsExactly(Arrays.stream(proxyInterfaces).map(Class::getCanonicalName).toArray(String[]::new));
}
public List<TypeReference> getProxiedInterfaces() {
return actual.getProxiedInterfaces();
}
}

120
src/test/java/org/springframework/data/aot/CodeContributionAssert.java

@ -0,0 +1,120 @@ @@ -0,0 +1,120 @@
/*
* 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.aot;
import static org.assertj.core.api.Assertions.*;
import java.util.Arrays;
import java.util.stream.Stream;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Assertions;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.aot.generator.ProtectedAccess;
import org.springframework.aot.hint.ClassProxyHint;
import org.springframework.aot.hint.JdkProxyHint;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.javapoet.support.MultiStatement;
/**
* @author Christoph Strobl
* @since 2022/04
*/
public class CodeContributionAssert extends AbstractAssert<CodeContributionAssert, CodeContribution>
implements CodeContribution {
public CodeContributionAssert(CodeContribution contribution) {
super(contribution, CodeContributionAssert.class);
}
public CodeContributionAssert doesNotContributeReflectionFor(Class<?>... types) {
for (Class<?> type : types) {
assertThat(this.actual.runtimeHints().reflection().getTypeHint(type))
.describedAs("Reflection entry found for %s", type).isNull();
}
return this;
}
public CodeContributionAssert contributesReflectionFor(Class<?>... types) {
for (Class<?> type : types) {
assertThat(this.actual.runtimeHints().reflection().getTypeHint(type))
.describedAs("No reflection entry found for %s", type).isNotNull();
}
return this;
}
public CodeContributionAssert contributesJdkProxyFor(Class<?> entryPoint) {
assertThat(jdkProxiesFor(entryPoint).findFirst()).describedAs("No jdk proxy found for %s", entryPoint).isPresent();
return this;
}
public CodeContributionAssert doesNotContributeJdkProxyFor(Class<?> entryPoint) {
assertThat(jdkProxiesFor(entryPoint).findFirst()).describedAs("Found jdk proxy matching %s though it should not be present.", entryPoint).isNotPresent();
return this;
}
public CodeContributionAssert doesNotContributeJdkProxy(Class<?>... proxyInterfaces) {
assertThat(jdkProxiesFor(proxyInterfaces[0])).describedAs("Found jdk proxy matching %s though it should not be present.", Arrays.asList(proxyInterfaces)).noneSatisfy(it -> {
new JdkProxyAssert(it).matches(proxyInterfaces);
});
return this;
}
public CodeContributionAssert contributesJdkProxy(Class<?>... proxyInterfaces) {
assertThat(jdkProxiesFor(proxyInterfaces[0])).describedAs("Unable to find jdk proxy matching %s", Arrays.asList(proxyInterfaces)).anySatisfy(it -> {
new JdkProxyAssert(it).matches(proxyInterfaces);
});
return this;
}
private Stream<JdkProxyHint> jdkProxiesFor(Class<?> entryPoint) {
return this.actual.runtimeHints().proxies().jdkProxies().filter(jdkProxyHint -> {
return jdkProxyHint.getProxiedInterfaces().get(0).getCanonicalName().equals(entryPoint.getCanonicalName());
});
}
public CodeContributionAssert contributesClassProxy(Class<?>... proxyInterfaces) {
assertThat(classProxiesFor(proxyInterfaces[0])).describedAs("Unable to find jdk proxy matching %s", Arrays.asList(proxyInterfaces)).anySatisfy(it -> {
new ClassProxyAssert(it).matches(proxyInterfaces);
});
return this;
}
private Stream<ClassProxyHint> classProxiesFor(Class<?> entryPoint) {
return this.actual.runtimeHints().proxies().classProxies().filter(jdkProxyHint -> {
return jdkProxyHint.getProxiedInterfaces().get(0).getCanonicalName().equals(entryPoint.getCanonicalName());
});
}
public MultiStatement statements() {
return actual.statements();
}
public RuntimeHints runtimeHints() {
return actual.runtimeHints();
}
public ProtectedAccess protectedAccess() {
return actual.protectedAccess();
}
}

46
src/test/java/org/springframework/data/aot/JdkProxyAssert.java

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
/*
* 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.aot;
import static org.assertj.core.api.Assertions.*;
import java.util.Arrays;
import java.util.List;
import org.assertj.core.api.AbstractAssert;
import org.springframework.aot.hint.JdkProxyHint;
import org.springframework.aot.hint.TypeReference;
/**
* @author Christoph Strobl
* @since 2022/04
*/
public class JdkProxyAssert extends AbstractAssert<JdkProxyAssert, JdkProxyHint> {
public JdkProxyAssert(JdkProxyHint jdkProxyHint) {
super(jdkProxyHint, JdkProxyAssert.class);
}
public void matches(Class<?>... proxyInterfaces) {
assertThat(actual.getProxiedInterfaces().stream().map(TypeReference::getCanonicalName))
.containsExactly(Arrays.stream(proxyInterfaces).map(Class::getCanonicalName).toArray(String[]::new));
}
public List<TypeReference> getProxiedInterfaces() {
return actual.getProxiedInterfaces();
}
}

86
src/test/java/org/springframework/data/aot/RepositoryBeanContributionAssert.java

@ -0,0 +1,86 @@ @@ -0,0 +1,86 @@
/*
* 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.aot;
import static org.assertj.core.api.Assertions.*;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Consumer;
import org.assertj.core.api.AbstractAssert;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.aot.generator.DefaultCodeContribution;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFragment;
/**
* @author Christoph Strobl
* @since 2022/04
*/
public class RepositoryBeanContributionAssert
extends AbstractAssert<RepositoryBeanContributionAssert, RepositoryBeanContribution> {
public RepositoryBeanContributionAssert(RepositoryBeanContribution actual) {
super(actual, RepositoryBeanContributionAssert.class);
}
public static RepositoryBeanContributionAssert assertThatContribution(RepositoryBeanContribution actual) {
return new RepositoryBeanContributionAssert(actual);
}
public RepositoryBeanContributionAssert targetRepositoryTypeIs(Class<?> expected) {
assertThat(getRepositoryInformation().getRepositoryInterface()).isEqualTo(expected);
return myself;
}
public RepositoryBeanContributionAssert hasNoFragments() {
assertThat(getRepositoryInformation().getFragments()).isEmpty();
return this;
}
public RepositoryBeanContributionAssert hasFragments() {
assertThat(getRepositoryInformation().getFragments()).isNotEmpty();
return this;
}
public RepositoryBeanContributionAssert verifyFragments(Consumer<Set<RepositoryFragment<?>>> consumer) {
assertThat(getRepositoryInformation().getFragments()).satisfies(it -> consumer.accept(new LinkedHashSet<>(it)));
return this;
}
public RepositoryBeanContributionAssert codeContributionSatisfies(Consumer<CodeContributionAssert> consumer) {
DefaultCodeContribution codeContribution = new DefaultCodeContribution(new RuntimeHints());
this.actual.applyTo(codeContribution);
consumer.accept(new CodeContributionAssert(codeContribution));
return this;
}
private RepositoryInformation getRepositoryInformation() {
assertThat(this.actual).describedAs("No repository interface found on null bean contribution.").isNotNull();
assertThat(this.actual.getRepositoryInformation())
.describedAs("No repository interface found on null repository information.").isNotNull();
return this.actual.getRepositoryInformation();
}
}

63
src/test/java/org/springframework/data/aot/TypeCollectorUnitTests.java

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
/*
* 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.aot;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.springframework.data.aot.types.*;
/**
* @author Christoph Strobl
*/
public class TypeCollectorUnitTests {
@Test // GH-2593
void detectsSignatureTypes() {
assertThat(TypeCollector.inspect(FieldsAndMethods.class).list()).containsExactlyInAnyOrder(FieldsAndMethods.class,
AbstractType.class, InterfaceType.class);
}
@Test // GH-2593
void detectsMethodArgs() {
assertThat(TypeCollector.inspect(TypesInMethodSignatures.class).list())
.containsExactlyInAnyOrder(TypesInMethodSignatures.class, EmptyType1.class, EmptyType2.class);
}
@Test // GH-2593
void doesNotOverflowOnCyclicPropertyReferences() {
assertThat(TypeCollector.inspect(CyclicPropertiesA.class).list()).containsExactlyInAnyOrder(CyclicPropertiesA.class,
CyclicPropertiesB.class);
}
@Test
void doesNotOverflowOnCyclicSelfReferences() {
assertThat(TypeCollector.inspect(CyclicPropertiesSelf.class).list())
.containsExactlyInAnyOrder(CyclicPropertiesSelf.class);
}
@Test
void doesNotOverflowOnCyclicGenericsReferences() {
assertThat(TypeCollector.inspect(CyclicGenerics.class).list()).containsExactlyInAnyOrder(CyclicGenerics.class);
}
@Test
void includesDeclaredClassesInInspection() {
assertThat(TypeCollector.inspect(WithDeclaredClass.class).list()).containsExactlyInAnyOrder(WithDeclaredClass.class,
WithDeclaredClass.SomeEnum.class);
}
}

54
src/test/java/org/springframework/data/aot/sample/ConfigWithCustomFactoryBeanBaseClass.java

@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
/*
* Copyright 2021 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.aot.sample;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.aot.sample.ConfigWithCustomFactoryBeanBaseClass.MyFixedRepoFactory;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.config.EnableRepositories;
import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean;
/**
* @author Christoph Strobl
*/
@Configuration
@EnableRepositories(repositoryFactoryBeanClass = MyFixedRepoFactory.class, considerNestedRepositories = true,
includeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*FixedFactoryRepository") })
public class ConfigWithCustomFactoryBeanBaseClass {
public interface FixedFactoryRepository extends CrudRepository<Person, String> {
}
public static class Person {
Address address;
}
public static class Address {
String street;
}
public static class MyFixedRepoFactory extends DummyRepositoryFactoryBean<FixedFactoryRepository, Person, String> {
public MyFixedRepoFactory(Class<? extends FixedFactoryRepository> repositoryInterface) {
super(FixedFactoryRepository.class);
}
}
}

63
src/test/java/org/springframework/data/aot/sample/ConfigWithCustomImplementation.java

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
/*
* Copyright 2019-2021 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.aot.sample;
import java.util.Collections;
import java.util.List;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.config.EnableRepositories;
import org.springframework.stereotype.Component;
/**
* @author Christoph Strobl
*/
@Configuration
@EnableRepositories(considerNestedRepositories = true,
includeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*RepositoryWithCustomImplementation") })
public class ConfigWithCustomImplementation {
public interface RepositoryWithCustomImplementation extends Repository<Person, String>, CustomImplInterface {
}
public interface CustomImplInterface {
List<Person> findMyCustomer();
}
@Component
public static class RepositoryWithCustomImplementationImpl implements CustomImplInterface {
@Override
public List<Person> findMyCustomer() {
return Collections.emptyList();
}
}
public static class Person {
Address address;
}
public static class Address {
String street;
}
}

53
src/test/java/org/springframework/data/aot/sample/ConfigWithCustomRepositoryBaseClass.java

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
/*
* Copyright 2021 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.aot.sample;
import lombok.experimental.Delegate;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.aot.sample.ConfigWithCustomRepositoryBaseClass.RepoBaseClass;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.config.EnableRepositories;
/**
* @author Christoph Strobl
*/
@Configuration
@EnableRepositories(repositoryBaseClass = RepoBaseClass.class, considerNestedRepositories = true,
includeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*CustomerRepositoryWithCustomBaseRepo$") })
public class ConfigWithCustomRepositoryBaseClass {
public interface CustomerRepositoryWithCustomBaseRepo extends CrudRepository<Person, String> {
}
public static class RepoBaseClass<T, ID> implements CrudRepository<T, ID> {
private @Delegate CrudRepository<T, ID> delegate;
}
public static class Person {
Address address;
}
public static class Address {
String street;
}
}

77
src/test/java/org/springframework/data/aot/sample/ConfigWithFragments.java

@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
/*
* Copyright 2019-2021 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.aot.sample;
import java.util.Collections;
import java.util.List;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.config.EnableRepositories;
import org.springframework.stereotype.Component;
/**
* @author Christoph Strobl
*/
@Configuration
@EnableRepositories(considerNestedRepositories = true,
includeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*RepositoryWithFragments") })
public class ConfigWithFragments {
public interface RepositoryWithFragments
extends Repository<Person, String>, CustomImplInterface1, CustomImplInterface2 {
}
public interface CustomImplInterface1 {
List<Customer> findMyCustomer();
}
@Component
public static class CustomImplInterface1Impl implements CustomImplInterface1 {
@Override
public List<Customer> findMyCustomer() {
return Collections.emptyList();
}
}
public interface CustomImplInterface2 {
}
@Component
public static class CustomImplInterface2Impl implements CustomImplInterface2 {
}
public static class Person {
Address address;
}
public static class Address {
String street;
}
public static class Customer {
}
}

69
src/test/java/org/springframework/data/aot/sample/ConfigWithQueryMethods.java

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
/*
* Copyright 2019-2021 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.aot.sample;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.annotation.Nullable;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.annotation.QueryAnnotation;
import org.springframework.data.domain.Page;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.config.EnableRepositories;
import org.springframework.data.repository.query.Param;
/**
* @author Christoph Strobl
*/
@Configuration
@EnableRepositories(considerNestedRepositories = true, includeFilters = {@Filter(type = FilterType.REGEX, pattern = ".*CustomerRepositoryWithQueryMethods")})
public class ConfigWithQueryMethods {
public interface CustomerRepositoryWithQueryMethods extends Repository<Person, String> {
Page<Person> findAllBy(@Param("longValue") Long val);
@CustomQuery
String customQuery();
ProjectionInterface findProjectionBy();
}
public static class Person {
Address address;
}
public static class Address {
String street;
}
public interface ProjectionInterface {}
@Nullable
@QueryAnnotation
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomQuery {
}
}

46
src/test/java/org/springframework/data/aot/sample/ConfigWithSimpleCrudRepository.java

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
/*
* 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.aot.sample;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.aot.sample.ConfigWithSimpleCrudRepository.MyRepo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.config.EnableRepositories;
/**
* @author Christoph Strobl
*/
@EnableRepositories(includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = MyRepo.class) },
basePackageClasses = ConfigWithSimpleCrudRepository.class, considerNestedRepositories = true)
public class ConfigWithSimpleCrudRepository {
public interface MyRepo extends CrudRepository<Person, String> {
}
public static class Person {
@javax.annotation.Nullable
Address address;
}
public static class Address {
String street;
}
}

53
src/test/java/org/springframework/data/aot/sample/ConfigWithTransactionManagerPresent.java

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
/*
* 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.aot.sample;
import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.aot.sample.ConfigWithTransactionManagerPresent.MyTxRepo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.config.EnableRepositories;
import org.springframework.transaction.TransactionManager;
/**
* @author Christoph Strobl
*/
@EnableRepositories(includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = MyTxRepo.class) },
basePackageClasses = ConfigWithTransactionManagerPresent.class, considerNestedRepositories = true)
public class ConfigWithTransactionManagerPresent {
public interface MyTxRepo extends CrudRepository<Person, String> {
}
public static class Person {
Address address;
}
public static class Address {
String street;
}
@Bean
TransactionManager txManager() {
return Mockito.mock(TransactionManager.class);
}
}

56
src/test/java/org/springframework/data/aot/sample/ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.java

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
/*
* 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.aot.sample;
import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.aot.sample.ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.MyComponentTxRepo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.config.EnableRepositories;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionManager;
/**
* @author Christoph Strobl
*/
@EnableRepositories(includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = MyComponentTxRepo.class) },
basePackageClasses = ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.class,
considerNestedRepositories = true)
public class ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty {
@Component
public interface MyComponentTxRepo extends CrudRepository<Person, String> {
}
public static class Person {
Address address;
}
public static class Address {
String street;
}
@Bean
TransactionManager txManager() {
return Mockito.mock(TransactionManager.class);
}
}

45
src/test/java/org/springframework/data/aot/sample/ReactiveConfig.java

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
/*
* Copyright 2019-2021 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.aot.sample;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.repository.config.EnableReactiveRepositories;
import org.springframework.data.repository.config.EnableRepositories;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
/**
* @author Christoph Strobl
*/
@Configuration
@EnableReactiveRepositories(considerNestedRepositories = true, includeFilters = {@Filter(type = FilterType.REGEX, pattern = ".*CustomerRepositoryReactive$")})
public class ReactiveConfig {
public interface CustomerRepositoryReactive extends ReactiveCrudRepository<Person, String> {
}
public static class Person {
Address address;
}
public static class Address {
String street;
}
}

30
src/test/java/org/springframework/data/aot/types/AbstractType.java

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
/*
* Copyright 2021 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.aot.types;
/**
* @author Christoph Strobl
*/
public abstract class AbstractType {
private Object fieldInAbstractType;
abstract Object abstractMethod();
Object methodDefinedInAbstractType() {
return null;
}
}

42
src/test/java/org/springframework/data/aot/types/Address.java

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
/*
* Copyright 2021 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.aot.types;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.geo.Point;
/**
* @author Christoph Strobl
*/
public class Address implements LocationHolder {
String street;
Point location;
Address() {
}
@PersistenceCreator
Address(String street) {
this.street = street;
}
@Override
public Point getLocation() {
return location;
}
}

24
src/test/java/org/springframework/data/aot/types/BaseEntity.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
/*
* Copyright 2019-2021 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.aot.types;
/**
* @author Christoph Strobl
*/
public class BaseEntity {
Address address;
}

39
src/test/java/org/springframework/data/aot/types/Customer.java

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
/*
* Copyright 2019-2021 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.aot.types;
import java.time.Instant;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.Transient;
import org.springframework.data.annotation.TypeAlias;
/**
* @author Christoph Strobl
*/
@TypeAlias("cu")
public class Customer extends BaseEntity {
@Id
String id;
@Transient
String transientProperty;
@LastModifiedDate
Instant modifiedAt;
}

24
src/test/java/org/springframework/data/aot/types/CyclicGenerics.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
/*
* Copyright 2021 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.aot.types;
/**
* @author Christoph Strobl
*/
public class CyclicGenerics<T extends CyclicGenerics<? extends CyclicGenerics<T>>> {
T property;
}

24
src/test/java/org/springframework/data/aot/types/CyclicPropertiesA.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
/*
* Copyright 2019-2021 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.aot.types;
/**
* @author Christoph Strobl
*/
public class CyclicPropertiesA {
CyclicPropertiesB b;
}

24
src/test/java/org/springframework/data/aot/types/CyclicPropertiesB.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
/*
* Copyright 2021 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.aot.types;
/**
* @author Christoph Strobl
*/
public class CyclicPropertiesB {
CyclicPropertiesA refToA;
}

24
src/test/java/org/springframework/data/aot/types/CyclicPropertiesSelf.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
/*
* Copyright 2021 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.aot.types;
/**
* @author Christoph Strobl
*/
public class CyclicPropertiesSelf {
CyclicPropertiesSelf refSelf;
}

29
src/test/java/org/springframework/data/aot/types/DomainObjectWithSimpleTypesOnly.java

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
/*
* Copyright 2021 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.aot.types;
import org.springframework.data.annotation.Id;
/**
* @author Christoph Strobl
*/
public class DomainObjectWithSimpleTypesOnly {
@Id
String id;
Long longValue;
int primValue;
}

23
src/test/java/org/springframework/data/aot/types/EmptyType1.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
/*
* 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.aot.types;
/**
* @author Christoph Strobl
*/
public class EmptyType1 {
}

23
src/test/java/org/springframework/data/aot/types/EmptyType2.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
/*
* 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.aot.types;
/**
* @author Christoph Strobl
*/
public class EmptyType2 {
}

55
src/test/java/org/springframework/data/aot/types/FieldsAndMethods.java

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
/*
* Copyright 2019-2021 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.aot.types;
/**
* @author Christoph Strobl
*/
public class FieldsAndMethods extends AbstractType implements InterfaceType {
public static final Object CONSTANT_FIELD = null;
private Object privateField;
Object packagePrivateField;
protected Object protectedField;
public Object publicField;
@Override
Long abstractMethod() {
return null;
}
@Override
public Integer someDefaultMethod() {
return null;
}
private Object privateMethod() {
return null;
}
Object packagePrivateMethod() {
return null;
}
protected Object protectedMethod() {
return null;
}
public Object publicMethod() {
return null;
}
}

26
src/test/java/org/springframework/data/aot/types/InterfaceType.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
/*
* Copyright 2021 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.aot.types;
/**
* @author Christoph Strobl
*/
public interface InterfaceType {
default Object someDefaultMethod() {
return null;
}
}

26
src/test/java/org/springframework/data/aot/types/LocationHolder.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
/*
* Copyright 2019-2021 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.aot.types;
import org.springframework.data.geo.Point;
/**
* @author Christoph Strobl
*/
public interface LocationHolder {
Point getLocation();
}

23
src/test/java/org/springframework/data/aot/types/ProjectionInterface.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
/*
* Copyright 2021 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.aot.types;
/**
* @author Christoph Strobl
*/
public interface ProjectionInterface {
}

48
src/test/java/org/springframework/data/aot/types/TypesInMethodSignatures.java

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
/*
* Copyright 2019-2021 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.aot.types;
/**
* @author Christoph Strobl
*/
public class TypesInMethodSignatures {
TypesInMethodSignatures(String ctorArg) {
}
Long returnValue() {
return null;
}
void voidReturn() {
}
EmptyType1 aDomainType() {
return null;
}
void setSomething(EmptyType2 something) {
}
Object methodArg(Integer methodArg) {
return null;
}
}

24
src/test/java/org/springframework/data/aot/types/WithDeclaredClass.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
/*
* Copyright 2021 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.aot.types;
/**
* @author Christoph Strobl
*/
public class WithDeclaredClass {
public enum SomeEnum {};
}

34
src/test/java/org/springframework/data/repository/config/DummyConfigurationExtension.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* 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.repository.config;
import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean;
/**
* @author Christoph Strobl
* @since 2022/04
*/
class DummyConfigurationExtension extends RepositoryConfigurationExtensionSupport {
public String getRepositoryFactoryBeanClassName() {
return DummyRepositoryFactoryBean.class.getName();
}
@Override
public String getModulePrefix() {
return "commons";
}
}

41
src/test/java/org/springframework/data/repository/config/DummyRegistrar.java

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
/*
* 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.repository.config;
import java.lang.annotation.Annotation;
import org.springframework.core.io.DefaultResourceLoader;
/**
* @author Christoph Strobl
* @since 2022/04
*/
class DummyRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
DummyRegistrar() {
setResourceLoader(new DefaultResourceLoader());
}
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableRepositories.class;
}
@Override
protected RepositoryConfigurationExtension getExtension() {
return new DummyConfigurationExtension();
}
}

55
src/test/java/org/springframework/data/repository/config/EnableReactiveRepositories.java

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
/*
* Copyright 2012-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.repository.config;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Import;
import org.springframework.data.repository.core.support.ReactiveDummyRepositoryFactoryBean;
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
@Retention(RetentionPolicy.RUNTIME)
@Import(ReactiveDummyRegistrar.class)
@Inherited
public @interface EnableReactiveRepositories {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
Class<?> repositoryFactoryBeanClass() default ReactiveDummyRepositoryFactoryBean.class;
Class<?> repositoryBaseClass() default ReactiveSortingRepository.class;
String namedQueriesLocation() default "";
String repositoryImplementationPostfix() default "Impl";
boolean considerNestedRepositories() default false;
boolean limitImplementationBasePackages() default true;
BootstrapMode bootstrapMode() default BootstrapMode.DEFAULT;
}

1
src/test/java/org/springframework/data/repository/config/EnableRepositories.java

@ -22,7 +22,6 @@ import java.lang.annotation.RetentionPolicy; @@ -22,7 +22,6 @@ import java.lang.annotation.RetentionPolicy;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Import;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupportUnitTests.DummyRegistrar;
import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean;
@Retention(RetentionPolicy.RUNTIME)

43
src/test/java/org/springframework/data/repository/config/ReactiveDummyConfigurationExtension.java

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
/*
* 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.repository.config;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.ReactiveDummyRepositoryFactoryBean;
/**
* @author Christoph Strobl
* @since 2022/04
*/
class ReactiveDummyConfigurationExtension extends RepositoryConfigurationExtensionSupport {
public String getRepositoryFactoryBeanClassName() {
return ReactiveDummyRepositoryFactoryBean.class.getName();
}
@Override
public String getModulePrefix() {
return "commons";
}
@Override
protected boolean useRepositoryConfiguration(RepositoryMetadata metadata) {
if(metadata.isReactiveRepository()) {
return true;
}
return false;
}
}

41
src/test/java/org/springframework/data/repository/config/ReactiveDummyRegistrar.java

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
/*
* 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.repository.config;
import java.lang.annotation.Annotation;
import org.springframework.core.io.DefaultResourceLoader;
/**
* @author Christoph Strobl
* @since 2022/04
*/
class ReactiveDummyRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
ReactiveDummyRegistrar() {
setResourceLoader(new DefaultResourceLoader());
}
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableReactiveRepositories.class;
}
@Override
protected RepositoryConfigurationExtension getExtension() {
return new ReactiveDummyConfigurationExtension();
}
}

1
src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportIntegrationTests.java

@ -25,7 +25,6 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext @@ -25,7 +25,6 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupportUnitTests.DummyConfigurationExtension;
import org.springframework.util.ClassUtils;
/**

3
src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java

@ -18,8 +18,6 @@ package org.springframework.data.repository.config; @@ -18,8 +18,6 @@ package org.springframework.data.repository.config;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.lang.annotation.Annotation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -33,7 +31,6 @@ import org.springframework.context.annotation.AnnotationBeanNameGenerator; @@ -33,7 +31,6 @@ import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.data.mapping.Person;

2
src/test/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupportUnitTests.java

@ -110,7 +110,7 @@ class RepositoryConfigurationExtensionSupportUnitTests { @@ -110,7 +110,7 @@ class RepositoryConfigurationExtensionSupportUnitTests {
}
@Override
protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
public Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
return Collections.singleton(Primary.class);
}

2
src/test/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadataUnitTests.java

@ -19,8 +19,10 @@ import static org.assertj.core.api.Assertions.*; @@ -19,8 +19,10 @@ import static org.assertj.core.api.Assertions.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;

6
src/test/java/org/springframework/data/repository/core/support/DummyRepositoryInformation.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.data.repository.core.support;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
import org.springframework.data.repository.core.CrudMethods;
@ -103,4 +104,9 @@ public final class DummyRepositoryInformation implements RepositoryInformation { @@ -103,4 +104,9 @@ public final class DummyRepositoryInformation implements RepositoryInformation {
public boolean isReactiveRepository() {
return metadata.isReactiveRepository();
}
@Override
public Set<RepositoryFragment<?>> getFragments() {
return Collections.emptySet();
}
}

121
src/test/java/org/springframework/data/repository/core/support/ReactiveDummyRepositoryFactory.java

@ -0,0 +1,121 @@ @@ -0,0 +1,121 @@
/*
* Copyright 2012-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.repository.core.support;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.function.Supplier;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.metrics.StartupStep;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
/**
* Dummy implementation for {@link RepositoryFactorySupport} that is equipped with mocks to simulate behavior for test
* cases.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class ReactiveDummyRepositoryFactory extends ReactiveRepositoryFactorySupport {
public final MyRepositoryQuery queryOne = mock(MyRepositoryQuery.class);
public final RepositoryQuery queryTwo = mock(RepositoryQuery.class);
public final QueryLookupStrategy strategy = mock(QueryLookupStrategy.class);
private final ApplicationStartup applicationStartup;
@SuppressWarnings("unchecked") private final QuerydslPredicateExecutor<Object> querydsl = mock(
QuerydslPredicateExecutor.class);
private final Object repository;
public ReactiveDummyRepositoryFactory(Object repository) {
this.repository = repository;
when(strategy.resolveQuery(Mockito.any(Method.class), Mockito.any(RepositoryMetadata.class),
Mockito.any(ProjectionFactory.class), Mockito.any(NamedQueries.class))).thenReturn(queryOne);
this.applicationStartup = mock(ApplicationStartup.class);
var startupStep = mock(StartupStep.class);
when(applicationStartup.start(anyString())).thenReturn(startupStep);
when(startupStep.tag(anyString(), anyString())).thenReturn(startupStep);
when(startupStep.tag(anyString(), ArgumentMatchers.<Supplier<String>> any())).thenReturn(startupStep);
var beanFactory = Mockito.mock(BeanFactory.class);
when(beanFactory.getBean(ApplicationStartup.class)).thenReturn(applicationStartup);
setBeanFactory(beanFactory);
}
@Override
@SuppressWarnings("unchecked")
public <T, ID> EntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
return mock(EntityInformation.class);
}
@Override
protected Object getTargetRepository(RepositoryInformation information) {
return repository;
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return repository.getClass();
}
@Override
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
return Optional.of(strategy);
}
@Override
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
var fragments = super.getRepositoryFragments(metadata);
return QuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface()) //
? fragments.append(RepositoryFragments.just(querydsl)) //
: fragments;
}
ApplicationStartup getApplicationStartup() {
return this.applicationStartup;
}
/**
* @author Mark Paluch
*/
public interface MyRepositoryQuery extends RepositoryQuery {
}
}

45
src/test/java/org/springframework/data/repository/core/support/ReactiveDummyRepositoryFactoryBean.java

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
/*
* Copyright 2012-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.repository.core.support;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import org.springframework.data.mapping.context.SampleMappingContext;
import org.springframework.data.repository.Repository;
/**
* @author Oliver Gierke
*/
public class ReactiveDummyRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable>
extends RepositoryFactoryBeanSupport<T, S, ID> {
private final T repository;
public ReactiveDummyRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
this.repository = mock(repositoryInterface);
setMappingContext(new SampleMappingContext());
}
@Override
protected RepositoryFactorySupport createRepositoryFactory() {
return new ReactiveDummyRepositoryFactory(repository);
}
}
Loading…
Cancel
Save