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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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