Introduce infrastructure for AOT processing of repository declarations
This commit introduces initial support for framework 6 bases ahead of time processing of data components and adds extension points for module implementations. See: #2593 Original Pull Request: #2624
This commit is contained in:
146
src/main/java/org/springframework/data/aot/AotContext.java
Normal file
146
src/main/java/org/springframework/data/aot/AotContext.java
Normal file
@@ -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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.aot;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.data.repository.config.RepositoryFragmentConfiguration;
|
||||
import org.springframework.data.repository.config.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.RepositoryFragment;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Reader that allows to extract {@link RepositoryInformation} from metadata.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
*/
|
||||
class RepositoryBeanDefinitionReader {
|
||||
|
||||
static RepositoryInformation readRepositoryInformation(RepositoryMetadata metadata,
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
|
||||
return new AotRepositoryInformation(metadataSupplier(metadata, beanFactory),
|
||||
repositoryBaseClass(metadata, beanFactory), fragments(metadata, beanFactory));
|
||||
}
|
||||
|
||||
private static Supplier<Collection<RepositoryFragment<?>>> fragments(RepositoryMetadata metadata,
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
return Lazy
|
||||
.of(() -> (Collection<RepositoryFragment<?>>) metadata.getFragmentConfiguration().stream().flatMap(it -> {
|
||||
RepositoryFragmentConfiguration fragmentConfiguration = (RepositoryFragmentConfiguration) it;
|
||||
|
||||
List<RepositoryFragment> fragments = new ArrayList<>(2);
|
||||
if (fragmentConfiguration.getClassName() != null) {
|
||||
fragments.add(RepositoryFragment.implemented(forName(fragmentConfiguration.getClassName(), beanFactory)));
|
||||
}
|
||||
if (fragmentConfiguration.getInterfaceName() != null) {
|
||||
fragments
|
||||
.add(RepositoryFragment.structural(forName(fragmentConfiguration.getInterfaceName(), beanFactory)));
|
||||
}
|
||||
|
||||
return fragments.stream();
|
||||
}).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static Supplier<Class<?>> repositoryBaseClass(RepositoryMetadata metadata,
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
return Lazy.of(() -> (Class<?>) metadata.getRepositoryBaseClassName().map(it -> forName(it.toString(), beanFactory))
|
||||
.orElseGet(() -> {
|
||||
// TODO: retrieve the default without loading the actual RepositoryBeanFactory
|
||||
return Object.class;
|
||||
}));
|
||||
}
|
||||
|
||||
static Supplier<org.springframework.data.repository.core.RepositoryMetadata> metadataSupplier(
|
||||
RepositoryMetadata metadata, ConfigurableListableBeanFactory beanFactory) {
|
||||
return Lazy.of(() -> new DefaultRepositoryMetadata(forName(metadata.getRepositoryInterface(), beanFactory)));
|
||||
}
|
||||
|
||||
static Class<?> forName(String name, ConfigurableListableBeanFactory beanFactory) {
|
||||
try {
|
||||
return ClassUtils.forName(name, beanFactory.getBeanClassLoader());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
243
src/main/java/org/springframework/data/aot/TypeCollector.java
Normal file
243
src/main/java/org/springframework/data/aot/TypeCollector.java
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright 2019-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.aot;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class TypeCollector {
|
||||
|
||||
private static Log logger = LogFactory.getLog(TypeCollector.class);
|
||||
|
||||
static final Set<String> EXCLUDED_DOMAINS = new HashSet<>(Arrays.asList("java", "sun.", "jdk.", "reactor.",
|
||||
"kotlinx.", "kotlin.", "org.springframework.core.", "org.springframework.boot."));
|
||||
|
||||
private Predicate<Class<?>> excludedDomainsFilter = (type) -> {
|
||||
return EXCLUDED_DOMAINS.stream().noneMatch(type.getPackageName()::startsWith);
|
||||
};
|
||||
|
||||
Predicate<Class<?>> typeFilter = excludedDomainsFilter;
|
||||
|
||||
private final Predicate<Method> methodFilter = (method) -> {
|
||||
if (method.getName().startsWith("$$_hibernate")) {
|
||||
return false;
|
||||
}
|
||||
if (method.getDeclaringClass().getPackageName().startsWith("java.") || method.getDeclaringClass().isEnum()
|
||||
|| EXCLUDED_DOMAINS.stream().anyMatch(it -> method.getDeclaringClass().getPackageName().startsWith(it))) {
|
||||
return false;
|
||||
}
|
||||
if (method.isBridge() || method.isSynthetic()) {
|
||||
return false;
|
||||
}
|
||||
return (!Modifier.isNative(method.getModifiers()) && !Modifier.isPrivate(method.getModifiers())
|
||||
&& !Modifier.isProtected(method.getModifiers())) || !method.getDeclaringClass().equals(Object.class);
|
||||
};
|
||||
|
||||
private Predicate<Field> fieldFilter = (field) -> {
|
||||
if (field.isSynthetic() | field.getName().startsWith("$$_hibernate")) {
|
||||
return false;
|
||||
}
|
||||
if (field.getDeclaringClass().getPackageName().startsWith("java.")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
public TypeCollector filterFields(Predicate<Field> filter) {
|
||||
this.fieldFilter = filter.and(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TypeCollector filterTypes(Predicate<Class<?>> filter) {
|
||||
this.typeFilter = this.typeFilter.and(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspect the given type and resolve those reachable via fields, methods, generics, ...
|
||||
*
|
||||
* @param types the types to inspect
|
||||
* @return a type model collector for the type
|
||||
*/
|
||||
public static ReachableTypes inspect(Class<?>... types) {
|
||||
return inspect(Arrays.asList(types));
|
||||
}
|
||||
|
||||
public static ReachableTypes inspect(Collection<Class<?>> types) {
|
||||
return new ReachableTypes(new TypeCollector(), types);
|
||||
}
|
||||
|
||||
private void process(Class<?> root, Consumer<ResolvableType> consumer) {
|
||||
processType(ResolvableType.forType(root), new InspectionCache(), consumer);
|
||||
}
|
||||
|
||||
private void processType(ResolvableType type, InspectionCache cache, Consumer<ResolvableType> callback) {
|
||||
|
||||
if (ResolvableType.NONE.equals(type) || cache.contains(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache.add(type);
|
||||
|
||||
// continue inspection but only add those matching the filter criteria to the result
|
||||
if (typeFilter.test(type.toClass())) {
|
||||
callback.accept(type);
|
||||
}
|
||||
|
||||
Set<Type> additionalTypes = new LinkedHashSet<>();
|
||||
additionalTypes.addAll(TypeUtils.resolveTypesInSignature(type));
|
||||
additionalTypes.addAll(visitConstructorsOfType(type));
|
||||
additionalTypes.addAll(visitMethodsOfType(type));
|
||||
additionalTypes.addAll(visitFieldsOfType(type));
|
||||
if (!ObjectUtils.isEmpty(type.toClass().getDeclaredClasses())) {
|
||||
additionalTypes.addAll(Arrays.asList(type.toClass().getDeclaredClasses()));
|
||||
}
|
||||
for (Type discoveredType : additionalTypes) {
|
||||
processType(ResolvableType.forType(discoveredType, type), cache, callback);
|
||||
}
|
||||
}
|
||||
|
||||
Set<Type> visitConstructorsOfType(ResolvableType type) {
|
||||
if (!typeFilter.test(type.toClass())) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<Type> discoveredTypes = new LinkedHashSet<>();
|
||||
for (Constructor<?> constructor : type.toClass().getDeclaredConstructors()) {
|
||||
for (Class<?> signatureType : TypeUtils.resolveTypesInSignature(type.toClass(), constructor)) {
|
||||
if (typeFilter.test(signatureType)) {
|
||||
discoveredTypes.add(signatureType);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new HashSet<>(discoveredTypes);
|
||||
}
|
||||
|
||||
Set<Type> visitMethodsOfType(ResolvableType type) {
|
||||
if (!typeFilter.test(type.toClass())) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
Set<Type> discoveredTypes = new LinkedHashSet<>();
|
||||
try {
|
||||
ReflectionUtils.doWithLocalMethods(type.toClass(), method -> {
|
||||
if (!methodFilter.test(method)) {
|
||||
return;
|
||||
}
|
||||
for (Class<?> signatureType : TypeUtils.resolveTypesInSignature(type.toClass(), method)) {
|
||||
if (typeFilter.test(signatureType)) {
|
||||
discoveredTypes.add(signatureType);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
logger.warn(ex);
|
||||
}
|
||||
return new HashSet<>(discoveredTypes);
|
||||
}
|
||||
|
||||
Set<Type> visitFieldsOfType(ResolvableType type) {
|
||||
Set<Type> discoveredTypes = new LinkedHashSet<>();
|
||||
ReflectionUtils.doWithLocalFields(type.toClass(), field -> {
|
||||
if (!fieldFilter.test(field)) {
|
||||
return;
|
||||
}
|
||||
for (Class<?> signatureType : TypeUtils.resolveTypesInSignature(ResolvableType.forField(field, type))) {
|
||||
if (typeFilter.test(signatureType)) {
|
||||
discoveredTypes.add(signatureType);
|
||||
}
|
||||
}
|
||||
});
|
||||
return discoveredTypes;
|
||||
}
|
||||
|
||||
public static class ReachableTypes {
|
||||
|
||||
private TypeCollector typeCollector;
|
||||
private final Iterable<Class<?>> roots;
|
||||
private final Lazy<List<Class<?>>> reachableTypes = Lazy.of(this::collect);
|
||||
|
||||
public ReachableTypes(TypeCollector typeCollector, Iterable<Class<?>> roots) {
|
||||
|
||||
this.typeCollector = typeCollector;
|
||||
this.roots = roots;
|
||||
}
|
||||
|
||||
public void forEach(Consumer<ResolvableType> consumer) {
|
||||
roots.forEach(it -> typeCollector.process(it, consumer));
|
||||
}
|
||||
|
||||
public List<Class<?>> list() {
|
||||
return reachableTypes.get();
|
||||
}
|
||||
|
||||
private List<Class<?>> collect() {
|
||||
List<Class<?>> target = new ArrayList<>();
|
||||
forEach(it -> target.add(it.toClass()));
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
static class InspectionCache {
|
||||
|
||||
private final Map<String, ResolvableType> mutableCache = new LinkedHashMap<>();
|
||||
|
||||
public void add(ResolvableType resolvableType) {
|
||||
mutableCache.put(resolvableType.toString(), resolvableType);
|
||||
}
|
||||
|
||||
public boolean contains(ResolvableType key) {
|
||||
return mutableCache.containsKey(key.toString());
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return mutableCache.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mutableCache.isEmpty();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mutableCache.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
108
src/main/java/org/springframework/data/aot/TypeContributor.java
Normal file
108
src/main/java/org/springframework/data/aot/TypeContributor.java
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.aot;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.SynthesizedAnnotation;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
*/
|
||||
class TypeContributor {
|
||||
|
||||
public static final String DATA_NAMESPACE = "org.springframework.data";
|
||||
|
||||
/**
|
||||
* Contribute the type with default reflection configuration, skip annotations.
|
||||
*
|
||||
* @param type
|
||||
* @param contribution
|
||||
*/
|
||||
static void contribute(Class<?> type, CodeContribution contribution) {
|
||||
contribute(type, Collections.emptySet(), contribution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contribute the type with default reflection configuration and only include matching annotations.
|
||||
*
|
||||
* @param type
|
||||
* @param filter
|
||||
* @param contribution
|
||||
*/
|
||||
static void contribute(Class<?> type, Predicate<Class<? extends Annotation>> filter, CodeContribution contribution) {
|
||||
|
||||
if (type.isPrimitive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type.isAnnotation() && filter.test((Class<? extends Annotation>) type)) {
|
||||
|
||||
contribution.runtimeHints().reflection().registerType(type, hint -> {
|
||||
hint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS);
|
||||
});
|
||||
|
||||
// TODO: do we need this if meta annotated with SD annotation?
|
||||
if (type.getPackage().getName().startsWith(DATA_NAMESPACE)) {
|
||||
contribution.runtimeHints().proxies().registerJdkProxy(type, SynthesizedAnnotation.class);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type.isInterface()) {
|
||||
contribution.runtimeHints().reflection().registerType(type, hint -> {
|
||||
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
contribution.runtimeHints().reflection().registerType(type, hint -> {
|
||||
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Contribute the type with default reflection configuration and only include annotations from a certain namespace and
|
||||
* those meta annotated with one of them.
|
||||
*
|
||||
* @param type
|
||||
* @param annotationNamespaces
|
||||
* @param contribution
|
||||
*/
|
||||
static void contribute(Class<?> type, Set<String> annotationNamespaces, CodeContribution contribution) {
|
||||
contribute(type, it -> isPartOfOrMetaAnnotatedWith(it, annotationNamespaces), contribution);
|
||||
}
|
||||
|
||||
private static boolean isPartOf(Class<?> type, Set<String> namespaces) {
|
||||
return namespaces.stream().anyMatch(namespace -> type.getPackageName().startsWith(namespace));
|
||||
}
|
||||
|
||||
protected static boolean isPartOfOrMetaAnnotatedWith(Class<? extends Annotation> annotation, Set<String> namespaces) {
|
||||
|
||||
if (isPartOf(annotation, namespaces)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return MergedAnnotation.of(annotation).getMetaTypes().stream().anyMatch(it -> isPartOf(annotation, namespaces));
|
||||
}
|
||||
}
|
||||
107
src/main/java/org/springframework/data/aot/TypeScanner.java
Normal file
107
src/main/java/org/springframework/data/aot/TypeScanner.java
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.aot;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class TypeScanner { // TODO: replace this with AnnotatedTypeScanner maybe?
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
public TypeScanner(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
static TypeScanner scanner(ClassLoader classLoader) {
|
||||
return new TypeScanner(classLoader);
|
||||
}
|
||||
|
||||
public Scanner scanForTypesAnnotatedWith(Class<? extends Annotation>... annotations) {
|
||||
return scanForTypesAnnotatedWith(Arrays.asList(annotations));
|
||||
}
|
||||
|
||||
public Scanner scanForTypesAnnotatedWith(Collection<Class<? extends Annotation>> annotations) {
|
||||
return new ScannerImpl().includeTypesAnnotatedWith(annotations);
|
||||
}
|
||||
|
||||
public interface Scanner {
|
||||
|
||||
default Set<Class<?>> inPackages(String... packageNames) {
|
||||
return inPackages(Arrays.asList(packageNames));
|
||||
}
|
||||
|
||||
Set<Class<?>> inPackages(Collection<String> packageNames);
|
||||
}
|
||||
|
||||
class ScannerImpl implements Scanner {
|
||||
|
||||
ClassPathScanningCandidateComponentProvider componentProvider;
|
||||
|
||||
public ScannerImpl() {
|
||||
|
||||
componentProvider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
componentProvider.setEnvironment(new StandardEnvironment());
|
||||
componentProvider.setResourceLoader(new DefaultResourceLoader(classLoader));
|
||||
}
|
||||
|
||||
ScannerImpl includeTypesAnnotatedWith(Collection<Class<? extends Annotation>> annotations) {
|
||||
annotations.stream().map(AnnotationTypeFilter::new).forEach(componentProvider::addIncludeFilter);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<?>> inPackages(Collection<String> packageNames) {
|
||||
|
||||
Set<Class<?>> types = new LinkedHashSet<>();
|
||||
|
||||
packageNames.forEach(pkg -> {
|
||||
componentProvider.findCandidateComponents(pkg).forEach(it -> {
|
||||
resolveType(it.getBeanClassName()).ifPresent(types::add);
|
||||
});
|
||||
});
|
||||
|
||||
return types;
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Class<?>> resolveType(String typeName) {
|
||||
|
||||
if (!ClassUtils.isPresent(typeName, classLoader)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
try {
|
||||
return Optional.of(ClassUtils.forName(typeName, classLoader));
|
||||
} catch (ClassNotFoundException e) {
|
||||
// just do nothing
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
253
src/main/java/org/springframework/data/aot/TypeUtils.java
Normal file
253
src/main/java/org/springframework/data/aot/TypeUtils.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user