Browse Source
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: #2624pull/2652/head
72 changed files with 4664 additions and 147 deletions
@ -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); |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
@ -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); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
} |
||||
@ -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(); |
||||
} |
||||
@ -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()); |
||||
} |
||||
|
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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); |
||||
} |
||||
} |
||||
} |
||||
@ -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(); |
||||
} |
||||
} |
||||
} |
||||
@ -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)); |
||||
} |
||||
} |
||||
@ -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(); |
||||
} |
||||
|
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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)); |
||||
} |
||||
} |
||||
@ -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); |
||||
} |
||||
} |
||||
@ -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 {} |
||||
|
||||
} |
||||
@ -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 ¯\_(ツ)_/¯
|
||||
} |
||||
} |
||||
} |
||||
@ -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(); |
||||
} |
||||
} |
||||
@ -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(); |
||||
} |
||||
} |
||||
@ -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(); |
||||
} |
||||
|
||||
} |
||||
@ -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(); |
||||
} |
||||
|
||||
|
||||
|
||||
} |
||||
@ -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); |
||||
} |
||||
|
||||
} |
||||
@ -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); |
||||
} |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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 { |
||||
|
||||
} |
||||
} |
||||
@ -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 { |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -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; |
||||
} |
||||
|
||||
} |
||||
@ -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); |
||||
} |
||||
|
||||
} |
||||
@ -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); |
||||
} |
||||
|
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
@ -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; |
||||
} |
||||
@ -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; |
||||
} |
||||
@ -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; |
||||
} |
||||
@ -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; |
||||
} |
||||
@ -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; |
||||
} |
||||
@ -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; |
||||
} |
||||
@ -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 { |
||||
|
||||
} |
||||
@ -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 { |
||||
|
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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(); |
||||
} |
||||
@ -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 { |
||||
|
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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 {}; |
||||
} |
||||
@ -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"; |
||||
} |
||||
} |
||||
@ -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(); |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
@ -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(); |
||||
} |
||||
} |
||||
@ -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 { |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -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…
Reference in new issue