diff --git a/pom.xml b/pom.xml index 13143c9f6f..670806f619 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 4.0.0-SNAPSHOT + 4.0.0-GH-3267-SNAPSHOT Spring Data Core Core Spring concepts underpinning every Spring Data module. diff --git a/src/main/java/org/springframework/data/aot/AotContext.java b/src/main/java/org/springframework/data/aot/AotContext.java index 4c2247a013..29b071a423 100644 --- a/src/main/java/org/springframework/data/aot/AotContext.java +++ b/src/main/java/org/springframework/data/aot/AotContext.java @@ -24,6 +24,8 @@ import java.util.function.Consumer; import org.jspecify.annotations.Nullable; + +import org.springframework.aot.generate.GenerationContext; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; @@ -40,9 +42,7 @@ /** * 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 TypeIntrospector#isTypePresent() is present} and allows resolution of them through {@link TypeIntrospector} - * and {@link IntrospectedBeanDefinition}. + * beanFactory} and {@link ClassLoader}. *

* Mainly for internal use within the framework. * @@ -99,7 +99,7 @@ static AotContext from(BeanFactory beanFactory, Environment environment) { * @param moduleName name of the module. Can be {@literal null} or {@literal empty}, in which case it will only check * the general {@link #GENERATED_REPOSITORIES_ENABLED} flag. * @return indicator if repository code generation is enabled. - * @since 5.0 + * @since 4.0 */ default boolean isGeneratedRepositoriesEnabled(@Nullable String moduleName) { @@ -119,13 +119,13 @@ default boolean isGeneratedRepositoriesEnabled(@Nullable String moduleName) { } /** - * Checks if repository metadata file writing is enabled by checking environment variables for general - * enablement ({@link #GENERATED_REPOSITORIES_JSON_ENABLED}) + * Checks if repository metadata file writing is enabled by checking environment variables for general enablement + * ({@link #GENERATED_REPOSITORIES_JSON_ENABLED}) *

* Unset properties are considered being {@literal true}. * * @return indicator if repository metadata should be written - * @since 5.0 + * @since 4.0 */ default boolean isGeneratedRepositoriesMetadataEnabled() { return getEnvironment().getProperty(GENERATED_REPOSITORIES_JSON_ENABLED, Boolean.class, true); @@ -177,8 +177,12 @@ default ClassLoader getRequiredClassLoader() { * * @param typeName {@link String name} of the {@link Class type} to evaluate; must not be {@literal null}. * @return the type introspector for further type-based introspection. + * @deprecated since 4.0 as this isn't widely used and can be easily implemented within user code. */ - TypeIntrospector introspectType(String typeName); + @Deprecated(since = "4.0", forRemoval = true) + default TypeIntrospector introspectType(String typeName) { + throw new UnsupportedOperationException(); // preparation for implementation removal. + } /** * Returns a new {@link TypeScanner} used to scan for {@link Class types} that will be contributed to the AOT @@ -201,7 +205,9 @@ default TypeScanner getTypeScanner() { * @param packageNames {@link Collection} of {@link String package names} to scan. * @return a {@link Set} of {@link Class types} found during the scan. * @see #getTypeScanner() + * @deprecated since 4.0, use {@link #getTypeScanner()} directly */ + @Deprecated(since = "4.0", forRemoval = true) default Set> scanPackageForTypes(Collection> identifyingAnnotations, Collection packageNames) { @@ -214,7 +220,9 @@ default Set> scanPackageForTypes(Collection * * @param reference {@link BeanReference} to the managed bean. * @return the introspected bean definition. + * @deprecated since 4.0, use {@link #getBeanFactory()} and interact with the bean factory directly. */ + @Deprecated(since = "4.0", forRemoval = true) default IntrospectedBeanDefinition introspectBeanDefinition(BeanReference reference) { return introspectBeanDefinition(reference.getBeanName()); } @@ -225,15 +233,20 @@ default IntrospectedBeanDefinition introspectBeanDefinition(BeanReference refere * * @param beanName {@link String} containing the {@literal name} of the bean to evaluate; must not be {@literal null}. * @return the introspected bean definition. + * @deprecated since 4.0, use {@link #getBeanFactory()} and interact with the bean factory directly. */ - IntrospectedBeanDefinition introspectBeanDefinition(String beanName); + @Deprecated(since = "4.0", forRemoval = true) + default IntrospectedBeanDefinition introspectBeanDefinition(String beanName) { + throw new UnsupportedOperationException(); // preparation for implementation removal. + } /** * Obtain a {@link AotTypeConfiguration} for the given {@link ResolvableType} to customize the AOT processing for the - * given type. + * given type. Repeated calls to the same type will result in merging the configuration. * * @param resolvableType the resolvable type to configure. * @param configurationConsumer configuration consumer function. + * @since 4.0 */ default void typeConfiguration(ResolvableType resolvableType, Consumer configurationConsumer) { typeConfiguration(resolvableType.toClass(), configurationConsumer); @@ -241,24 +254,29 @@ default void typeConfiguration(ResolvableType resolvableType, Consumer type, Consumer configurationConsumer); /** - * Return all type configurations registered with this {@link AotContext}. + * Contribute type configurations to the given {@link GenerationContext}. This method is called once per + * {@link GenerationContext} after all type configurations have been registered. * - * @return all type configurations registered with this {@link AotContext}. + * @param generationContext the context to contribute the type configurations to. */ - Collection typeConfigurations(); + void contributeTypeConfigurations(GenerationContext generationContext); /** * Type-based introspector to resolve {@link Class} from a type name and to introspect the bean factory for presence * of beans. + * + * @deprecated since 4.0 as this isn't widely used and can be easily implemented within user code. */ + @Deprecated(since = "4.0", forRemoval = true) interface TypeIntrospector { /** @@ -318,7 +336,10 @@ default void ifTypePresent(Consumer> action) { /** * Interface defining introspection methods for bean definitions. + * + * @deprecated since 4.0 as this isn't widely used and can be easily implemented within user code. */ + @Deprecated(since = "4.0", forRemoval = true) interface IntrospectedBeanDefinition { /** diff --git a/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java b/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java index 2adebba35c..6879011e2c 100644 --- a/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java +++ b/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java @@ -22,19 +22,15 @@ import org.springframework.aop.SpringProxy; import org.springframework.aop.framework.Advised; -import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.TypeReference; import org.springframework.core.DecoratingProxy; -import org.springframework.core.env.Environment; import org.springframework.data.projection.TargetAware; /** * Configuration object that captures various AOT configuration aspects of types within the data context by offering * predefined methods to register native configuration necessary for data binding, projection proxy definitions, AOT * cglib bytecode generation and other common tasks. - *

- * On {@link #contribute(Environment, GenerationContext)} the configuration is added to the {@link GenerationContext}. * * @author Christoph Strobl * @since 4.0 @@ -134,11 +130,4 @@ default AotTypeConfiguration proxyInterface(Class... proxyInterfaces) { */ AotTypeConfiguration forQuerydsl(); - /** - * Write the configuration to the given {@link GenerationContext}. - * - * @param environment must not be {@literal null}. - * @param generationContext must not be {@literal null}. - */ - void contribute(Environment environment, GenerationContext generationContext); } diff --git a/src/main/java/org/springframework/data/aot/DefaultAotContext.java b/src/main/java/org/springframework/data/aot/DefaultAotContext.java index 1608cf3039..9231471213 100644 --- a/src/main/java/org/springframework/data/aot/DefaultAotContext.java +++ b/src/main/java/org/springframework/data/aot/DefaultAotContext.java @@ -17,7 +17,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -30,9 +29,11 @@ import java.util.stream.Stream; import org.jspecify.annotations.Nullable; + import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.TypeReference; +import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.aot.AotProcessingException; @@ -55,14 +56,15 @@ * @author Christoph Strobl * @since 3.0 */ +@SuppressWarnings("removal") class DefaultAotContext implements AotContext { private final AotMappingContext mappingContext; private final ConfigurableListableBeanFactory factory; - // TODO: should we reuse the config or potentially have multiple ones with different settings for the same type - private final Map, AotTypeConfiguration> typeConfigurations = new HashMap<>(); + private final Map, ContextualTypeConfiguration> typeConfigurations = new HashMap<>(); private final Environment environment; + private final ReflectiveRuntimeHintsRegistrar runtimeHintsRegistrar = new ReflectiveRuntimeHintsRegistrar(); public DefaultAotContext(BeanFactory beanFactory, Environment environment) { this(beanFactory, environment, new AotMappingContext()); @@ -101,10 +103,13 @@ public void typeConfiguration(Class type, Consumer conf } @Override - public Collection typeConfigurations() { - return typeConfigurations.values(); + public void contributeTypeConfigurations(GenerationContext generationContext) { + typeConfigurations.forEach((type, configuration) -> { + configuration.contribute(this.environment, generationContext); + }); } + @SuppressWarnings("removal") class DefaultTypeIntrospector implements TypeIntrospector { private final String typeName; @@ -144,6 +149,7 @@ public List getBeanNames() { } } + @SuppressWarnings("removal") class DefaultIntrospectedBeanDefinition implements IntrospectedBeanDefinition { private final String beanName; @@ -227,7 +233,6 @@ public AotTypeConfiguration forQuerydsl() { return this; } - @Override public void contribute(Environment environment, GenerationContext generationContext) { try { @@ -254,6 +259,7 @@ private void doContribute(Environment environment, GenerationContext generationC } if (forDataBinding) { + runtimeHintsRegistrar.registerRuntimeHints(generationContext.getRuntimeHints(), type); TypeContributor.contribute(type, Set.of(TypeContributor.DATA_NAMESPACE), generationContext); } diff --git a/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java b/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java index f3a99e17a7..2e978a3c1f 100644 --- a/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java +++ b/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java @@ -119,7 +119,7 @@ private ManagedTypes resolveManagedTypes(RegisteredBean registeredBean) { /** * Hook to provide a customized flavor of {@link BeanRegistrationAotContribution}. By overriding this method calls to - * {@link #contributeType(ResolvableType, GenerationContext, AotContext)} might no longer be issued. + * {@link #registerTypeHints(ResolvableType, AotContext, GenerationContext)} might no longer be issued. * * @param aotContext never {@literal null}. * @param managedTypes never {@literal null}. @@ -128,7 +128,7 @@ private ManagedTypes resolveManagedTypes(RegisteredBean registeredBean) { protected BeanRegistrationAotContribution contribute(AotContext aotContext, ManagedTypes managedTypes, RegisteredBean registeredBean) { return new ManagedTypesRegistrationAotContribution(aotContext, managedTypes, registeredBean, - typeCollectorCustomizer(), this::contributeType); + typeCollectorCustomizer(), this::registerTypeHints); } /** @@ -140,13 +140,14 @@ protected BeanRegistrationAotContribution contribute(AotContext aotContext, Mana protected Consumer typeCollectorCustomizer() { return typeCollector -> {}; } + /** * Hook to contribute configuration for a given {@literal type}. * * @param type never {@literal null}. * @param generationContext never {@literal null}. */ - protected void contributeType(ResolvableType type, GenerationContext generationContext, AotContext aotContext) { + protected void registerTypeHints(ResolvableType type, AotContext aotContext, GenerationContext generationContext) { if (logger.isDebugEnabled()) { logger.debug(String.format("Contributing type information for [%s]", type.getType())); @@ -156,10 +157,6 @@ protected void contributeType(ResolvableType type, GenerationContext generationC configureTypeContribution(type.toClass(), aotContext); - aotContext.typeConfiguration(type, config -> { - config.contribute(environment.get(), generationContext); - }); - TypeUtils.resolveUsedAnnotations(type.toClass()).forEach( annotation -> TypeContributor.contribute(annotation.getType(), annotationNamespaces, generationContext)); } diff --git a/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java b/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java index 2463576a4d..daeb21b588 100644 --- a/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java +++ b/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java @@ -98,8 +98,10 @@ public void applyTo(GenerationContext generationContext, BeanRegistrationCode be if (!types.isEmpty()) { TypeCollector.inspect(typeCollectorCustomizer, types) - .forEach(type -> contributionAction.register(type, generationContext, aotContext)); + .forEach(type -> contributionAction.register(type, aotContext, generationContext)); } + + aotContext.contributeTypeConfigurations(generationContext); } @Override @@ -117,7 +119,7 @@ public RegisteredBean getSource() { } interface TypeRegistration { - void register(ResolvableType type, GenerationContext generationContext, AotContext aotContext); + void register(ResolvableType type, AotContext aotContext, GenerationContext generationContext); } /** diff --git a/src/main/java/org/springframework/data/javapoet/LordOfTheStrings.java b/src/main/java/org/springframework/data/javapoet/LordOfTheStrings.java index 6b924d26e5..83cffb8aeb 100644 --- a/src/main/java/org/springframework/data/javapoet/LordOfTheStrings.java +++ b/src/main/java/org/springframework/data/javapoet/LordOfTheStrings.java @@ -858,10 +858,37 @@ public static class TypedReturnBuilder extends ReturnBuilderSupport { */ @Contract("_ -> this") public TypedReturnBuilder number(String resultToReturn) { + return whenBoxedLong("$1L != null ? $1L.longValue() : null", resultToReturn) .whenLong("$1L != null ? $1L.longValue() : 0L", resultToReturn) .whenBoxedInteger("$1L != null ? $1L.intValue() : null", resultToReturn) - .whenInt("$1L != null ? $1L.intValue() : 0", resultToReturn); + .whenInt("$1L != null ? $1L.intValue() : 0", resultToReturn) + .whenBoxed(Byte.class, "$1L != null ? $1L.byteValue() : null", resultToReturn) + .when(byte.class, "$1L != null ? $1L.byteValue() : 0", resultToReturn) + .whenBoxed(Short.class, "$1L != null ? $1L.shortValue() : null", resultToReturn) + .when(short.class, "$1L != null ? $1L.shortValue() : 0", resultToReturn) + .whenBoxed(Double.class, "$1L != null ? $1L.doubleValue() : null", resultToReturn) + .when(double.class, "$1L != null ? $1L.doubleValue() : 0", resultToReturn) + .whenBoxed(Float.class, "$1L != null ? $1L.floatValue() : null", resultToReturn) + .when(float.class, "$1L != null ? $1L.floatValue() : 0f", resultToReturn); + } + + /** + * Add return statements for numeric types if the given {@code resultToReturn} points to a non-nullable + * {@link Number}. Considers all primitive numeric types assuming that {@code resultToReturn} is never + * {@literal null}. + * + * @param resultToReturn the argument or variable name holding the result. + * @return {@code this} builder. + */ + @Contract("_ -> this") + public TypedReturnBuilder nonNullableNumber(String resultToReturn) { + return whenPrimitiveOrBoxed(long.class, "$1L.longValue()", resultToReturn) + .whenPrimitiveOrBoxed(int.class, "$1L.intValue()", resultToReturn) + .whenPrimitiveOrBoxed(short.class, "$1L.shortValue()", resultToReturn) + .whenPrimitiveOrBoxed(byte.class, "$1L.byteValue()", resultToReturn) + .whenPrimitiveOrBoxed(float.class, "$1L.floatValue()", resultToReturn) + .whenPrimitiveOrBoxed(double.class, "$1L.doubleValue()", resultToReturn); } /** diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java index 367162f879..f8abd36650 100644 --- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java +++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java @@ -41,8 +41,8 @@ /** * Delegate to decorate AOT {@code BeanDefinition} properties during AOT processing. Adds a {@link CodeBlock} for the - * fragment function that resolves {@link RepositoryContributor#requiredArgs()} from the {@link BeanFactory} and - * provides them to the generated repository fragment. + * fragment function that resolves {@link RepositoryContributor#getAotFragmentMetadata()} from the {@link BeanFactory} + * and provides them to the generated repository fragment. * * @author Mark Paluch * @author Christoph Strobl @@ -128,7 +128,8 @@ private CodeBlock buildCallbackBody() { CodeBlock.Builder callback = CodeBlock.builder(); List arguments = new ArrayList<>(); - for (Entry entry : repositoryContributor.getConstructorArguments().entrySet()) { + for (Entry entry : repositoryContributor.getAotFragmentMetadata() + .getConstructorArguments().entrySet()) { ConstructorArgument argument = entry.getValue(); AotRepositoryConstructorBuilder.ParameterOrigin parameterOrigin = argument.parameterOrigin(); diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java index f8c0787c97..4534592ba8 100644 --- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java +++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java @@ -19,9 +19,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.function.Consumer; import javax.lang.model.element.Modifier; @@ -30,9 +28,7 @@ import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; -import org.springframework.core.ResolvableType; import org.springframework.data.projection.ProjectionFactory; -import org.springframework.data.repository.aot.generate.AotRepositoryFragmentMetadata.ConstructorArgument; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.RepositoryComposition; import org.springframework.data.repository.core.support.RepositoryFragment; @@ -100,18 +96,8 @@ String packageName() { return repositoryInformation.getRepositoryInterface().getPackageName(); } - Map getAutowireFields() { - - Map autowireFields = new LinkedHashMap<>( - generationMetadata.getConstructorArguments().size()); - for (Map.Entry entry : generationMetadata.getConstructorArguments().entrySet()) { - autowireFields.put(entry.getKey(), entry.getValue().parameterType()); - } - return autowireFields; - } - - Map getConstructorArguments() { - return generationMetadata.getConstructorArguments(); + AotRepositoryFragmentMetadata getRepositoryMetadata() { + return generationMetadata; } RepositoryInformation getRepositoryInformation() { diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java index 9f6c465f65..fe1ca30080 100644 --- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java +++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java @@ -129,6 +129,15 @@ public Map getConstructorArguments() { return constructorArguments; } + Map getAutowireFields() { + + Map autowireFields = new LinkedHashMap<>(getConstructorArguments().size()); + for (Map.Entry entry : getConstructorArguments().entrySet()) { + autowireFields.put(entry.getKey(), entry.getValue().parameterType()); + } + return autowireFields; + } + public Map getMethods() { return methods; } diff --git a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java index 725e4ac460..7823ba9806 100644 --- a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java +++ b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java @@ -18,19 +18,18 @@ import java.io.ByteArrayInputStream; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; -import java.util.Collections; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; + import org.springframework.aot.generate.GeneratedClass; import org.springframework.aot.generate.GeneratedFiles.Kind; import org.springframework.aot.generate.GeneratedTypeReference; import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.TypeReference; -import org.springframework.core.ResolvableType; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.aot.generate.AotRepositoryCreator.AotBundle; @@ -66,7 +65,7 @@ public class RepositoryContributor { public RepositoryContributor(AotRepositoryContext repositoryContext) { this.repositoryContext = repositoryContext; - creator = AotRepositoryCreator.forRepository(repositoryContext.getRepositoryInformation(), + this.creator = AotRepositoryCreator.forRepository(repositoryContext.getRepositoryInformation(), repositoryContext.getModuleName(), createProjectionFactory()); } @@ -102,28 +101,10 @@ TypeReference getContributedTypeName() { } /** - * Get the required constructor arguments for the to be generated repository implementation. Types will be obtained by - * type from {@link org.springframework.beans.factory.BeanFactory} upon initialization of the generated fragment - * during application startup. - *

- * Can be overridden if required. Needs to match arguments of generated repository implementation. - * - * @return key/value pairs of required argument required to instantiate the generated fragment. - */ - // TODO: should we switch from ResolvableType to some custom value object to cover qualifiers? - java.util.Map requiredArgs() { - return Collections.unmodifiableMap(creator.getAutowireFields()); - } - - /** - * Get the required constructor arguments for the to be generated repository implementation. - *

- * Can be overridden if required. Needs to match arguments of generated repository implementation. - * - * @return key/value pairs of required argument required to instantiate the generated fragment. + * @return the associated {@link AotRepositoryFragmentMetadata}. */ - java.util.Map getConstructorArguments() { - return Collections.unmodifiableMap(creator.getConstructorArguments()); + AotRepositoryFragmentMetadata getAotFragmentMetadata() { + return creator.getRepositoryMetadata(); } /** diff --git a/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java b/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java index c0eac7cf29..f4e96b25fc 100644 --- a/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java +++ b/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java @@ -36,8 +36,12 @@ public interface AotRepositoryContext extends AotContext { /** * @return the {@link String bean name} of the repository / factory bean. + * @deprecated since 4.0, this doesn't really belong in here. */ - String getBeanName(); + @Deprecated(since = "4.0", forRemoval = true) + default String getBeanName() { + throw new UnsupportedOperationException(); // prepare for removal + } /** * @return the Spring Data module name, see {@link RepositoryConfigurationExtension#getModuleName()}. @@ -52,7 +56,10 @@ public interface AotRepositoryContext extends AotContext { /** * @return a {@link Set} of {@link String base packages} to search for repositories. + * @deprecated since 4.0, use {@link #getConfigurationSource()} and call + * {@link RepositoryConfigurationSource#getBasePackages()} */ + @Deprecated(since = "4.0", forRemoval = true) default Set getBasePackages() { return getConfigurationSource().getBasePackages().toSet(); } @@ -79,6 +86,4 @@ default Set getBasePackages() { */ Set> getResolvedTypes(); - Set> getUserDomainTypes(); - } diff --git a/src/main/java/org/springframework/data/repository/config/AotRepositoryContextSupport.java b/src/main/java/org/springframework/data/repository/config/AotRepositoryContextSupport.java new file mode 100644 index 0000000000..9bf7eb16a0 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/config/AotRepositoryContextSupport.java @@ -0,0 +1,99 @@ +/* + * Copyright 2025 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.function.Consumer; + +import org.jspecify.annotations.Nullable; + +import org.springframework.aot.generate.GenerationContext; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.ResolvableType; +import org.springframework.core.env.Environment; +import org.springframework.data.aot.AotContext; +import org.springframework.data.aot.AotTypeConfiguration; +import org.springframework.data.util.TypeScanner; + +/** + * Support class for {@link AotRepositoryContext} implementations delegating to an underlying {@link AotContext}. + * + * @author Mark Paluch + * @since 4.0 + */ +public abstract class AotRepositoryContextSupport implements AotRepositoryContext { + + private final AotContext aotContext; + + /** + * Create a new {@code AotRepositoryContextSupport} given the {@link AotContext}. + * + * @param aotContext + */ + public AotRepositoryContextSupport(AotContext aotContext) { + this.aotContext = aotContext; + } + + @Override + public boolean isGeneratedRepositoriesEnabled(@Nullable String moduleName) { + return aotContext.isGeneratedRepositoriesEnabled(moduleName); + } + + @Override + public boolean isGeneratedRepositoriesMetadataEnabled() { + return aotContext.isGeneratedRepositoriesMetadataEnabled(); + } + + @Override + public ConfigurableListableBeanFactory getBeanFactory() { + return aotContext.getBeanFactory(); + } + + @Override + public Environment getEnvironment() { + return aotContext.getEnvironment(); + } + + @Override + public @Nullable ClassLoader getClassLoader() { + return aotContext.getClassLoader(); + } + + @Override + public ClassLoader getRequiredClassLoader() { + return aotContext.getRequiredClassLoader(); + } + + @Override + public TypeScanner getTypeScanner() { + return aotContext.getTypeScanner(); + } + + @Override + public void typeConfiguration(ResolvableType resolvableType, Consumer configurationConsumer) { + aotContext.typeConfiguration(resolvableType, configurationConsumer); + } + + @Override + public void typeConfiguration(Class type, Consumer configurationConsumer) { + aotContext.typeConfiguration(type, configurationConsumer); + } + + @Override + public void contributeTypeConfigurations(GenerationContext generationContext) { + aotContext.contributeTypeConfigurations(generationContext); + } + +} diff --git a/src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java b/src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java index 4c1d0e5384..8d22b31a26 100644 --- a/src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java +++ b/src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java @@ -20,19 +20,14 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; -import java.util.function.Consumer; import java.util.stream.Collectors; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.core.annotation.MergedAnnotation; -import org.springframework.core.env.Environment; import org.springframework.data.aot.AotContext; -import org.springframework.data.aot.AotTypeConfiguration; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeCollector; -import org.springframework.data.util.TypeContributor; import org.springframework.data.util.TypeUtils; /** @@ -44,10 +39,8 @@ * @see AotRepositoryContext * @since 3.0 */ -@SuppressWarnings("NullAway") // TODO -class DefaultAotRepositoryContext implements AotRepositoryContext { +class DefaultAotRepositoryContext extends AotRepositoryContextSupport { - private final RegisteredBean bean; private final String moduleName; private final RepositoryConfigurationSource configurationSource; private final AotContext aotContext; @@ -55,23 +48,19 @@ class DefaultAotRepositoryContext implements AotRepositoryContext { private final Lazy>> resolvedAnnotations = Lazy.of(this::discoverAnnotations); private final Lazy>> managedTypes = Lazy.of(this::discoverTypes); - private Set basePackages = Collections.emptySet(); private Collection> identifyingAnnotations = Collections.emptySet(); private String beanName; public DefaultAotRepositoryContext(RegisteredBean bean, RepositoryInformation repositoryInformation, String moduleName, AotContext aotContext, RepositoryConfigurationSource configurationSource) { - this.bean = bean; + + super(aotContext); + this.repositoryInformation = repositoryInformation; this.moduleName = moduleName; this.configurationSource = configurationSource; this.aotContext = aotContext; this.beanName = bean.getBeanName(); - this.basePackages = configurationSource.getBasePackages().toSet(); - } - - public AotContext getAotContext() { - return aotContext; } @Override @@ -84,25 +73,6 @@ public RepositoryConfigurationSource getConfigurationSource() { return configurationSource; } - @Override - public ConfigurableListableBeanFactory getBeanFactory() { - return getAotContext().getBeanFactory(); - } - - @Override - public Environment getEnvironment() { - return getAotContext().getEnvironment(); - } - - @Override - public Set getBasePackages() { - return basePackages; - } - - public void setBasePackages(Set basePackages) { - this.basePackages = basePackages; - } - @Override public String getBeanName() { return beanName; @@ -136,29 +106,6 @@ public Set> getResolvedTypes() { return managedTypes.get(); } - @Override - public Set> getUserDomainTypes() { - - return getResolvedTypes().stream() - .filter(it -> TypeContributor.isPartOf(it, Set.of(repositoryInformation.getDomainType().getPackageName()))) - .collect(Collectors.toSet()); - } - - @Override - public AotContext.TypeIntrospector introspectType(String typeName) { - return aotContext.introspectType(typeName); - } - - @Override - public void typeConfiguration(Class type, Consumer configurationConsumer) { - aotContext.typeConfiguration(type, configurationConsumer); - } - - @Override - public Collection typeConfigurations() { - return aotContext.typeConfigurations(); - } - @Override public AotContext.IntrospectedBeanDefinition introspectBeanDefinition(String beanName) { return aotContext.introspectBeanDefinition(beanName); @@ -185,7 +132,8 @@ protected Set> discoverTypes() { if (!getIdentifyingAnnotations().isEmpty()) { - Set> classes = aotContext.getTypeScanner().scanPackages(getBasePackages()) + Set> classes = aotContext.getTypeScanner() + .scanPackages(getConfigurationSource().getBasePackages().toSet()) .forTypesAnnotatedWith(getIdentifyingAnnotations()).collectAsSet(); types.addAll(TypeCollector.inspect(classes).list()); } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java index 0d4ebbd053..c2b2db8209 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java @@ -15,43 +15,21 @@ */ package org.springframework.data.repository.config; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.function.Supplier; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; + import org.springframework.aot.generate.GenerationContext; -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.TypeReference; import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.aot.BeanRegistrationCode; import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments; import org.springframework.beans.factory.aot.BeanRegistrationCodeFragmentsDecorator; -import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; -import org.springframework.core.env.StandardEnvironment; -import org.springframework.data.aot.AotContext; -import org.springframework.data.aot.AotTypeConfiguration; -import org.springframework.data.projection.EntityProjectionIntrospector; -import org.springframework.data.repository.Repository; import org.springframework.data.repository.aot.generate.AotRepositoryBeanDefinitionPropertiesDecorator; import org.springframework.data.repository.aot.generate.RepositoryContributor; import org.springframework.data.repository.core.RepositoryInformation; -import org.springframework.data.repository.core.support.RepositoryFragment; -import org.springframework.data.util.Lazy; -import org.springframework.data.util.TypeUtils; import org.springframework.javapoet.CodeBlock; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; /** * {@link BeanRegistrationAotContribution} used to contribute repository registrations. @@ -61,174 +39,32 @@ * @author Mark Paluch * @since 3.0 */ -public class RepositoryRegistrationAotContribution implements BeanRegistrationAotContribution, EnvironmentAware { - - private static final Log logger = LogFactory.getLog(RepositoryRegistrationAotContribution.class); - - private static final String KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME = "org.springframework.data.repository.kotlin.CoroutineCrudRepository"; - - private final RepositoryRegistrationAotProcessor aotProcessor; - - private final AotRepositoryContext repositoryContext; - - private @Nullable RepositoryContributor repositoryContributor; - private Lazy environment = Lazy.of(StandardEnvironment::new); - - private @Nullable BiFunction moduleContribution; - - /** - * Constructs a new instance of the {@link RepositoryRegistrationAotContribution} initialized with the given, required - * {@link RepositoryRegistrationAotProcessor} from which this contribution was created. - * - * @param processor reference back to the {@link RepositoryRegistrationAotProcessor} from which this contribution was - * created. - * @param context reference back to the {@link AotRepositoryContext} from which this contribution was created. - * @throws IllegalArgumentException if the {@link RepositoryRegistrationAotProcessor} is {@literal null}. - * @see RepositoryRegistrationAotProcessor - */ - protected RepositoryRegistrationAotContribution(RepositoryRegistrationAotProcessor processor, - AotRepositoryContext context) { - - Assert.notNull(processor, "RepositoryRegistrationAotProcessor must not be null"); - Assert.notNull(context, "AotRepositoryContext must not be null"); - - this.aotProcessor = processor; - this.repositoryContext = context; - } - - /** - * Factory method used to construct a new instance of {@link RepositoryRegistrationAotContribution} initialized with - * the given, required {@link RepositoryRegistrationAotProcessor} from which this contribution was created. - * - * @param processor reference back to the {@link RepositoryRegistrationAotProcessor} from which this contribution was - * created. - * @return a new instance of {@link RepositoryRegistrationAotContribution} if a contribution can be made; - * {@literal null} if no contribution can be made. - * @see RepositoryRegistrationAotProcessor - */ - public static @Nullable RepositoryRegistrationAotContribution load(RepositoryRegistrationAotProcessor processor, - RegisteredBean repositoryBean) { - - RepositoryConfiguration repositoryMetadata = processor.getRepositoryMetadata(repositoryBean); - - if (repositoryMetadata == null) { - return null; - } - - AotRepositoryContext repositoryContext = buildAotRepositoryContext(processor.getEnvironment(), repositoryBean); - - if (repositoryContext == null) { - return null; - } - - return new RepositoryRegistrationAotContribution(processor, repositoryContext); - } +public class RepositoryRegistrationAotContribution implements BeanRegistrationAotContribution { - /** - * Builds a {@link RepositoryRegistrationAotContribution} for given, required {@link RegisteredBean} representing the - * {@link Repository} registered in the bean registry. - * - * @param repositoryBean {@link RegisteredBean} for the {@link Repository}; must not be {@literal null}. - * @return a {@link RepositoryRegistrationAotContribution} to contribute AOT metadata and code for the - * {@link Repository} {@link RegisteredBean}. - * @throws IllegalArgumentException if the {@link RegisteredBean} is {@literal null}. - * @deprecated since 4.0. - */ - @Deprecated(since = "4.0", forRemoval = true) - public @Nullable RepositoryRegistrationAotContribution forBean(RegisteredBean repositoryBean) { + private final AotRepositoryContext context; + private final BeanRegistrationAotContribution aotContribution; + private final @Nullable RepositoryContributor repositoryContribution; - RepositoryConfiguration repositoryMetadata = getRepositoryRegistrationAotProcessor() - .getRepositoryMetadata(repositoryBean); + RepositoryRegistrationAotContribution(AotRepositoryContext context, BeanRegistrationAotContribution aotContribution, + @Nullable RepositoryContributor repositoryContribution) { - if (repositoryMetadata == null) { - return null; - } - - AotRepositoryContext repositoryContext = buildAotRepositoryContext(aotProcessor.getEnvironment(), repositoryBean); - - if (repositoryContext == null) { - return null; - } - - return new RepositoryRegistrationAotContribution(getRepositoryRegistrationAotProcessor(), repositoryContext); - } - - protected @Nullable BiFunction getModuleContribution() { - return this.moduleContribution; - } - - protected AotRepositoryContext getRepositoryContext() { - return this.repositoryContext; - } - - protected RepositoryRegistrationAotProcessor getRepositoryRegistrationAotProcessor() { - return this.aotProcessor; + this.context = context; + this.aotContribution = aotContribution; + this.repositoryContribution = repositoryContribution; } public RepositoryInformation getRepositoryInformation() { - return getRepositoryContext().getRepositoryInformation(); - } - - private void logTrace(String message, Object... arguments) { - getRepositoryRegistrationAotProcessor().logTrace(message, arguments); - } - - private static @Nullable AotRepositoryContext buildAotRepositoryContext(Environment environment, - RegisteredBean bean) { - - RepositoryBeanDefinitionReader reader = new RepositoryBeanDefinitionReader(bean); - RepositoryConfiguration configuration = reader.getConfiguration(); - RepositoryConfigurationExtensionSupport extension = reader.getConfigurationExtension(); - - if (configuration == null || extension == null) { - logger.warn( - "Cannot create AotRepositoryContext for bean [%s]. No RepositoryConfiguration/RepositoryConfigurationExtension. Please make sure to register the repository bean through @Enable…Repositories." - .formatted(bean.getBeanName())); - return null; - } - RepositoryInformation repositoryInformation = reader.getRepositoryInformation(); - DefaultAotRepositoryContext repositoryContext = new DefaultAotRepositoryContext(bean, repositoryInformation, - extension.getModuleName(), AotContext.from(bean.getBeanFactory(), environment), - configuration.getConfigurationSource()); - - repositoryContext.setIdentifyingAnnotations(extension.getIdentifyingAnnotations()); - - return repositoryContext; - } - - /** - * {@link BiConsumer Callback} for data module specific contributions. - * - * @param moduleContribution {@link BiConsumer} used by data modules to submit contributions; can be {@literal null}. - * @return this. - */ - public RepositoryRegistrationAotContribution withModuleContribution( - @Nullable BiFunction moduleContribution) { - this.moduleContribution = moduleContribution; - return this; - } - - @Override - public void setEnvironment(Environment environment) { - this.environment = Lazy.of(environment); + return context.getRepositoryInformation(); } @Override public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) { - contributeRepositoryInfo(this.repositoryContext, generationContext); + aotContribution.applyTo(generationContext, beanRegistrationCode); - var moduleContribution = getModuleContribution(); - if (moduleContribution != null && this.repositoryContributor == null) { - - this.repositoryContributor = moduleContribution.apply(getRepositoryContext(), generationContext); - - if (this.repositoryContributor != null) { - this.repositoryContributor.contribute(generationContext); - } + if (this.repositoryContribution != null) { + this.repositoryContribution.contribute(generationContext); } - getRepositoryContext().typeConfigurations() - .forEach(typeConfiguration -> typeConfiguration.contribute(environment.get(), generationContext)); } @Override @@ -245,107 +81,16 @@ public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext gener Supplier inheritedProperties = () -> super.generateSetBeanDefinitionPropertiesCode(generationContext, beanRegistrationCode, beanDefinition, attributeFilter); - if (repositoryContributor == null) { // no aot implementation -> go on as + if (repositoryContribution == null) { // no aot implementation -> go on as return inheritedProperties.get(); } AotRepositoryBeanDefinitionPropertiesDecorator decorator = new AotRepositoryBeanDefinitionPropertiesDecorator( - inheritedProperties, repositoryContributor); + inheritedProperties, repositoryContribution); return decorator.decorate(); } }; } - private void contributeRepositoryInfo(AotRepositoryContext repositoryContext, GenerationContext contribution) { - - RepositoryInformation repositoryInformation = getRepositoryInformation(); - - logTrace("Contributing repository information for [%s]", repositoryInformation.getRepositoryInterface()); - - repositoryContext.typeConfiguration(repositoryInformation.getRepositoryInterface(), - config -> config.forReflectiveAccess(MemberCategory.INVOKE_PUBLIC_METHODS).repositoryProxy()); - - repositoryContext.typeConfiguration(repositoryInformation.getRepositoryBaseClass(), config -> config - .forReflectiveAccess(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS)); - - repositoryContext.typeConfiguration(repositoryInformation.getDomainType(), - config -> config.forDataBinding().forQuerydsl()); - - // TODO: purposeful api for uses cases to have some internal logic - repositoryContext.getUserDomainTypes() // - .forEach(it -> repositoryContext.typeConfiguration(it, AotTypeConfiguration::contributeAccessors)); - - // Repository Fragments - contributeFragments(contribution); - - // Kotlin - if (isKotlinCoroutineRepository(repositoryContext, repositoryInformation)) { - contribution.getRuntimeHints().reflection().registerTypes(kotlinRepositoryReflectionTypeReferences(), hint -> {}); - } - - // Repository query methods - repositoryInformation.getQueryMethods().stream().map(repositoryInformation::getReturnedDomainClass) - .filter(Class::isInterface).forEach(type -> { - if (EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy().test(type, - repositoryInformation.getDomainType())) { - repositoryContext.typeConfiguration(type, AotTypeConfiguration::usedAsProjectionInterface); - } - }); - } - - private void contributeFragments(GenerationContext contribution) { - for (RepositoryFragment fragment : getRepositoryInformation().getFragments()) { - - Class repositoryFragmentType = fragment.getSignatureContributor(); - Optional> implementation = fragment.getImplementationClass(); - - contribution.getRuntimeHints().reflection().registerType(repositoryFragmentType, hint -> { - - hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS); - - if (!repositoryFragmentType.isInterface()) { - hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); - } - }); - - implementation.ifPresent(typeToRegister -> { - contribution.getRuntimeHints().reflection().registerType(typeToRegister, hint -> { - - hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS); - - if (!typeToRegister.isInterface()) { - hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); - } - }); - }); - } - } - - private boolean isKotlinCoroutineRepository(AotRepositoryContext repositoryContext, - RepositoryInformation repositoryInformation) { - - return repositoryContext.introspectType(KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME).resolveType() - .filter(it -> ClassUtils.isAssignable(it, repositoryInformation.getRepositoryInterface())).isPresent(); - } - - private List kotlinRepositoryReflectionTypeReferences() { - - return new ArrayList<>( - 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"))); - } - - static boolean isJavaOrPrimitiveType(Class type) { - return TypeUtils.type(type).isPartOf("java") // - || ClassUtils.isPrimitiveOrWrapper(type) // - || ClassUtils.isPrimitiveArray(type); // - } - } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java index c94b648522..c5ae5d09ca 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java @@ -17,18 +17,18 @@ import java.lang.annotation.Annotation; import java.util.Collections; +import java.util.List; import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Predicate; +import java.util.Optional; +import java.util.Set; import java.util.stream.Stream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; import org.springframework.aot.generate.GenerationContext; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.annotation.Reflective; -import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.TypeReference; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -43,12 +43,18 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; +import org.springframework.data.aot.AotContext; import org.springframework.data.aot.AotTypeConfiguration; +import org.springframework.data.projection.EntityProjectionIntrospector; +import org.springframework.data.repository.Repository; import org.springframework.data.repository.aot.generate.RepositoryContributor; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; +import org.springframework.data.repository.core.support.RepositoryFragment; import org.springframework.data.util.TypeContributor; +import org.springframework.data.util.TypeUtils; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * {@link BeanRegistrationAotProcessor} responsible processing and providing AOT configuration for repositories. @@ -57,13 +63,13 @@ * AOT tooling to allow deriving target type from the {@link RootBeanDefinition bean definition}. If generic types do * 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}. - *

*

- * With {@link RepositoryRegistrationAotProcessor#contribute(AotRepositoryContext, GenerationContext)}, 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 + * With {@link #registerRepositoryCompositionHints(AotRepositoryContext, GenerationContext)} (specifically + * {@link #configureTypeContribution(Class, AotContext)} and {@link #contributeAotRepository(AotRepositoryContext)}, + * stores can provide custom logic for contributing additional (e.g. 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. - *

+ *

* The processor is typically configured via {@link RepositoryConfigurationExtension#getRepositoryAotProcessor()} and * gets added by the {@link org.springframework.data.repository.config.RepositoryConfigurationDelegate}. * @@ -75,6 +81,18 @@ public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware, EnvironmentAware, EnvironmentCapable { + private static final String KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME = "org.springframework.data.repository.kotlin.CoroutineCrudRepository"; + + private static final List KOTLIN_REFLECTION_TYPE_REFERENCES = List.of( + 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")); + private final Log logger = LogFactory.getLog(getClass()); private @Nullable ConfigurableListableBeanFactory beanFactory; @@ -83,140 +101,266 @@ public class RepositoryRegistrationAotProcessor private Map> configMap = Collections.emptyMap(); + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + + Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory, + () -> "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory); + + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + @Override + public Environment getEnvironment() { + return this.environment; + } + + /** + * Setter for the config map. See {@code RepositoryConfigurationDelegate#registerAotComponents}. + * + * @param configMap + */ + @SuppressWarnings("unused") + public void setConfigMap(Map> configMap) { + this.configMap = configMap; + } + + public Map> getConfigMap() { + return this.configMap; + } + + protected ConfigurableListableBeanFactory getBeanFactory() { + + if (this.beanFactory == null) { + throw new IllegalStateException( + "No BeanFactory available. Make sure to set the BeanFactory before using this processor."); + } + + return this.beanFactory; + } + @Override public @Nullable BeanRegistrationAotContribution processAheadOfTime(RegisteredBean bean) { - return isRepositoryBean(bean) ? newRepositoryRegistrationAotContribution(bean) : null; + + if (!isRepositoryBean(bean)) { + return null; + } + + RepositoryConfiguration repositoryMetadata = getRepositoryMetadata(bean); + AotRepositoryContext repositoryContext = potentiallyCreateContext(environment, bean); + + if (repositoryMetadata == null || repositoryContext == null) { + return null; + } + + BeanRegistrationAotContribution contribution = (generationContext, beanRegistrationCode) -> { + + registerRepositoryCompositionHints(repositoryContext, generationContext); + configureTypeContributions(repositoryContext, generationContext); + + repositoryContext.contributeTypeConfigurations(generationContext); + }; + + return new RepositoryRegistrationAotContribution(repositoryContext, contribution, + contributeAotRepository(repositoryContext)); } - @Nullable - protected RepositoryContributor contribute(AotRepositoryContext repositoryContext, + /** + * Contribute repository-specific hints, e.g. for repository proxy, base implementation, fragments. Customization hook + * for subclasses that wish to customize repository hint contribution. + * + * @param repositoryContext the repository context. + * @param generationContext the generation context. + * @since 4.0 + */ + protected void registerRepositoryCompositionHints(AotRepositoryContext repositoryContext, GenerationContext generationContext) { - repositoryContext.getResolvedTypes().stream() - .filter(it -> !RepositoryRegistrationAotContribution.isJavaOrPrimitiveType(it)) - .forEach(it -> contributeType(it, generationContext)); + RepositoryInformation repositoryInformation = repositoryContext.getRepositoryInformation(); + + if (logger.isTraceEnabled()) { + logger.trace( + "Contributing repository information for [%s]".formatted(repositoryInformation.getRepositoryInterface())); + } + + // Native hints for repository proxy + repositoryContext.typeConfiguration(repositoryInformation.getRepositoryInterface(), + config -> config.forReflectiveAccess(MemberCategory.INVOKE_PUBLIC_METHODS).repositoryProxy()); + + // Native hints for reflective base implementation access + repositoryContext.typeConfiguration(repositoryInformation.getRepositoryBaseClass(), config -> config + .forReflectiveAccess(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS)); + + // Repository Fragments + registerFragmentsHints(repositoryInformation.getFragments(), generationContext); + + // Kotlin + if (isKotlinCoroutineRepository(repositoryInformation)) { + generationContext.getRuntimeHints().reflection().registerTypes(KOTLIN_REFLECTION_TYPE_REFERENCES, hint -> {}); + } + } + + /** + * Register type-specific hints and AOT artifacts for domain types, reachable types, projection interfaces derived + * from query method return types, and annotations from {@literal org.springframework.data} packages. + * + * @param repositoryContext the repository context. + * @param generationContext the generation context. + * @since 4.0 + */ + protected void configureTypeContributions(AotRepositoryContext repositoryContext, + GenerationContext generationContext) { + + RepositoryInformation information = repositoryContext.getRepositoryInformation(); + + configureDomainTypeContributions(repositoryContext, generationContext); + + // Repository query methods + information.getQueryMethods().stream().map(information::getReturnedDomainClass).filter(Class::isInterface) + .forEach(type -> { + if (EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy().test(type, + information.getDomainType())) { + repositoryContext.typeConfiguration(type, AotTypeConfiguration::usedAsProjectionInterface); + } + }); repositoryContext.getResolvedAnnotations().stream() .filter(RepositoryRegistrationAotProcessor::isSpringDataManagedAnnotation).map(MergedAnnotation::getType) .forEach(it -> contributeType(it, generationContext)); - - return null; } /** - * Processes the repository's domain and alternative domain types to consider {@link Reflective} annotations used on - * it. + * Customization hook for subclasses that wish to customize domain type hint contributions. + *

+ * Type hints are registered for the domain, alternative domain types, and types reachable from there + * ({@link AotRepositoryContext#getResolvedTypes()}) * - * @param repositoryContext must not be {@literal null}. - * @param generationContext must not be {@literal null}. + * @param repositoryContext the repository context. + * @param generationContext the generation context. + * @since 4.0 */ - // TODO: Can we merge #contribute, #registerReflectiveForAggregateRoot into RepositoryRegistrationAotContribution? - // hints and types are contributed from everywhere. - private void registerReflectiveForAggregateRoot(AotRepositoryContext repositoryContext, + private void configureDomainTypeContributions(AotRepositoryContext repositoryContext, GenerationContext generationContext) { RepositoryInformation information = repositoryContext.getRepositoryInformation(); - ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar(); - RuntimeHints hints = generationContext.getRuntimeHints(); Stream.concat(Stream.of(information.getDomainType()), information.getAlternativeDomainTypes().stream()) .forEach(it -> { + configureTypeContribution(it, repositoryContext); + }); - // arent we already registering the types in RepositoryRegistrationAotContribution#contributeRepositoryInfo? - registrar.registerRuntimeHints(hints, it); + // Domain types my be part of this, but it also contains reachable ones. + repositoryContext.getResolvedTypes().stream() + .filter(it -> TypeContributor.isPartOf(it, Set.of(information.getDomainType().getPackageName()))) + .forEach(it -> configureTypeContribution(it, repositoryContext)); - repositoryContext.typeConfiguration(it, AotTypeConfiguration::contributeAccessors); - }); + repositoryContext.getResolvedTypes().stream().filter(it -> !isJavaOrPrimitiveType(it)) + .forEach(it -> contributeType(it, generationContext)); + } + + /** + * Customization hook to configure the {@link TypeContributor} used to register the given {@literal type}. + * + * @param type the class to configure the contribution for. + * @param aotContext AOT context for type configuration. + * @since 4.0 + */ + protected void configureTypeContribution(Class type, AotContext aotContext) { + aotContext.typeConfiguration(type, config -> config.forDataBinding().contributeAccessors().forQuerydsl()); + } + + /** + * This method allows for the creation to be overridden by subclasses. + * + * @param repositoryContext the context for the repository being processed. + * @return a {@link RepositoryContributor} to contribute store-specific AOT artifacts or {@literal null} to skip + * store-specific AOT contributions. + * @since 4.0 + */ + @Nullable + protected RepositoryContributor contributeAotRepository(AotRepositoryContext repositoryContext) { + return null; } private boolean isRepositoryBean(RegisteredBean bean) { return getConfigMap().containsKey(bean.getBeanName()); } - protected @Nullable RepositoryRegistrationAotContribution newRepositoryRegistrationAotContribution( - RegisteredBean repositoryBean) { + private RepositoryConfiguration getRepositoryMetadata(RegisteredBean bean) { - RepositoryRegistrationAotContribution contribution = RepositoryRegistrationAotContribution.load(this, - repositoryBean); + RepositoryConfiguration configuration = getConfigMap().get(bean.getBeanName()); - // cannot contribute a repository bean. - if (contribution == null) { - return null; + if (configuration == null) { + throw new IllegalArgumentException("No configuration for bean [%s]".formatted(bean.getBeanName())); } - // TODO: add the hook for customizing bean initialization code here! - - return contribution.withModuleContribution((repositoryContext, generationContext) -> { - registerReflectiveForAggregateRoot(repositoryContext, generationContext); - return contribute(repositoryContext, generationContext); - }); + return configuration; } - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - - Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory, - () -> "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory); - - this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + private void contributeType(Class type, GenerationContext context) { + TypeContributor.contribute(type, it -> true, context); } - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; + private void registerFragmentsHints(Iterable> fragments, GenerationContext contribution) { + fragments.forEach(it -> registerFragmentHints(it, contribution)); } - @Override - public Environment getEnvironment() { - return this.environment; - } + private static void registerFragmentHints(RepositoryFragment fragment, GenerationContext context) { - public void setConfigMap(Map> configMap) { - this.configMap = configMap; - } + Class repositoryFragmentType = fragment.getSignatureContributor(); + Optional> implementation = fragment.getImplementationClass(); - public Map> getConfigMap() { - return this.configMap; + registerReflectiveHints(repositoryFragmentType, context); + + implementation.ifPresent(typeToRegister -> registerReflectiveHints(typeToRegister, context)); } - protected ConfigurableListableBeanFactory getBeanFactory() { + private static void registerReflectiveHints(Class typeToRegister, GenerationContext context) { - if (this.beanFactory == null) { - throw new IllegalStateException( - "No BeanFactory available. Make sure to set the BeanFactory before using this processor."); - } + context.getRuntimeHints().reflection().registerType(typeToRegister, hint -> { - return this.beanFactory; - } + hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS); - protected @Nullable RepositoryConfiguration getRepositoryMetadata(RegisteredBean bean) { - return getConfigMap().get(bean.getBeanName()); + if (!typeToRegister.isInterface()) { + hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + } + }); } - protected void contributeType(Class type, GenerationContext generationContext) { - TypeContributor.contribute(type, it -> true, generationContext); - } + private @Nullable AotRepositoryContext potentiallyCreateContext(Environment environment, RegisteredBean bean) { - protected Log getLogger() { - return this.logger; - } + RepositoryBeanDefinitionReader reader = new RepositoryBeanDefinitionReader(bean); + RepositoryConfiguration configuration = reader.getConfiguration(); + RepositoryConfigurationExtensionSupport extension = reader.getConfigurationExtension(); - protected void logDebug(String message, Object... arguments) { - logAt(Log::isDebugEnabled, Log::debug, message, arguments); - } + if (configuration == null || extension == null) { + logger.warn( + "Cannot create AotRepositoryContext for bean [%s]. No RepositoryConfiguration/RepositoryConfigurationExtension. Please make sure to register the repository bean through @Enable…Repositories." + .formatted(bean.getBeanName())); + return null; + } + RepositoryInformation repositoryInformation = reader.getRepositoryInformation(); + DefaultAotRepositoryContext repositoryContext = new DefaultAotRepositoryContext(bean, repositoryInformation, + extension.getModuleName(), AotContext.from(bean.getBeanFactory(), environment), + configuration.getConfigurationSource()); - protected void logTrace(String message, Object... arguments) { - logAt(Log::isTraceEnabled, Log::trace, message, arguments); + repositoryContext.setIdentifyingAnnotations(extension.getIdentifyingAnnotations()); + + return repositoryContext; } - private void logAt(Predicate logLevelPredicate, BiConsumer logOperation, String message, - Object... arguments) { + private static boolean isKotlinCoroutineRepository(RepositoryInformation repositoryInformation) { - Log logger = getLogger(); + Class coroutineRepository = org.springframework.data.util.ClassUtils.loadIfPresent( + KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME, repositoryInformation.getRepositoryInterface().getClassLoader()); - if (logLevelPredicate.test(logger)) { - logOperation.accept(logger, String.format(message, arguments)); - } + return coroutineRepository != null + && ClassUtils.isAssignable(coroutineRepository, repositoryInformation.getRepositoryInterface()); } private static boolean isSpringDataManagedAnnotation(MergedAnnotation annotation) { @@ -229,4 +373,10 @@ private static boolean isSpringDataType(Class type) { return type.getPackageName().startsWith(TypeContributor.DATA_NAMESPACE); } + private static boolean isJavaOrPrimitiveType(Class type) { + return ClassUtils.isPrimitiveOrWrapper(type) // + || ClassUtils.isPrimitiveArray(type) // + || TypeUtils.type(type).isPartOf("java"); + } + } diff --git a/src/test/java/org/springframework/data/aot/AotContextUnitTests.java b/src/test/java/org/springframework/data/aot/AotContextUnitTests.java index d164779542..88a365c246 100644 --- a/src/test/java/org/springframework/data/aot/AotContextUnitTests.java +++ b/src/test/java/org/springframework/data/aot/AotContextUnitTests.java @@ -17,8 +17,6 @@ import static org.mockito.Mockito.*; -import java.util.Collection; -import java.util.List; import java.util.function.Consumer; import org.assertj.core.api.Assertions; @@ -28,6 +26,8 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoSettings; + +import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -102,7 +102,7 @@ private void contributeAccessor(Class... classes) { context.typeConfiguration(aClass, AotTypeConfiguration::contributeAccessors); } - context.typeConfigurations().forEach(it -> it.contribute(mockEnvironment, new TestGenerationContext())); + context.contributeTypeConfigurations(new TestGenerationContext()); } @ParameterizedTest // GH-3322 @@ -163,8 +163,8 @@ public void typeConfiguration(Class type, Consumer conf } @Override - public Collection typeConfigurations() { - return List.of(); + public void contributeTypeConfigurations(GenerationContext generationContext) { + } @Override diff --git a/src/test/java/org/springframework/data/javapoet/JavaPoetUnitTests.java b/src/test/java/org/springframework/data/javapoet/JavaPoetUnitTests.java index a39d09fb64..d4346db76b 100644 --- a/src/test/java/org/springframework/data/javapoet/JavaPoetUnitTests.java +++ b/src/test/java/org/springframework/data/javapoet/JavaPoetUnitTests.java @@ -188,6 +188,50 @@ void shouldRenderConditionalNumericReturn() { block = LordOfTheStrings.returning(Integer.class).number("someNumericVariable").otherwise(":-[").build(); assertThat(block).hasToString("return someNumericVariable != null ? someNumericVariable.intValue() : null"); + + block = LordOfTheStrings.returning(Short.class).number("someNumericVariable").otherwise(":-[").build(); + assertThat(block).hasToString("return someNumericVariable != null ? someNumericVariable.shortValue() : null"); + + block = LordOfTheStrings.returning(short.class).number("someNumericVariable").otherwise(":-[").build(); + assertThat(block).hasToString("return someNumericVariable != null ? someNumericVariable.shortValue() : 0"); + + block = LordOfTheStrings.returning(Byte.class).number("someNumericVariable").otherwise(":-[").build(); + assertThat(block).hasToString("return someNumericVariable != null ? someNumericVariable.byteValue() : null"); + + block = LordOfTheStrings.returning(Float.class).number("someNumericVariable").otherwise(":-[").build(); + assertThat(block).hasToString("return someNumericVariable != null ? someNumericVariable.floatValue() : null"); + + block = LordOfTheStrings.returning(float.class).number("someNumericVariable").otherwise(":-[").build(); + assertThat(block).hasToString("return someNumericVariable != null ? someNumericVariable.floatValue() : 0f"); + } + + @Test // GH-3357 + void shouldRenderConditionalSafeNumericReturn() { + + CodeBlock block = LordOfTheStrings.returning(boolean.class).nonNullableNumber("someNumericVariable") + .otherwise(":-[").build(); + assertThat(block).hasToString("return :-["); + + block = LordOfTheStrings.returning(long.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build(); + assertThat(block).hasToString("return someNumericVariable.longValue()"); + + block = LordOfTheStrings.returning(Long.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build(); + assertThat(block).hasToString("return someNumericVariable.longValue()"); + + block = LordOfTheStrings.returning(Integer.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build(); + assertThat(block).hasToString("return someNumericVariable.intValue()"); + + block = LordOfTheStrings.returning(short.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build(); + assertThat(block).hasToString("return someNumericVariable.shortValue()"); + + block = LordOfTheStrings.returning(byte.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build(); + assertThat(block).hasToString("return someNumericVariable.byteValue()"); + + block = LordOfTheStrings.returning(Double.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build(); + assertThat(block).hasToString("return someNumericVariable.doubleValue()"); + + block = LordOfTheStrings.returning(Float.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build(); + assertThat(block).hasToString("return someNumericVariable.floatValue()"); } @Test // GH-3357 diff --git a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java index dadad5311b..d82397b304 100644 --- a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java +++ b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java @@ -56,10 +56,9 @@ class AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests { void beforeEach() { when(contributor.getContributedTypeName()).thenReturn(GeneratedTypeReference.of(ClassName.bestGuess(TYPE_NAME))); + when(contributor.getAotFragmentMetadata()).thenReturn(metadata); inheritedSource = CodeBlock.builder(); decorator = new AotRepositoryBeanDefinitionPropertiesDecorator(() -> inheritedSource.build(), contributor); - - when(contributor.getConstructorArguments()).thenReturn(metadata.getConstructorArguments()); } @Test // GH-3344 diff --git a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryConfigurationUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryConfigurationUnitTests.java index a38f869949..5249edf8f5 100644 --- a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryConfigurationUnitTests.java @@ -69,7 +69,8 @@ protected void customizeConstructor(AotRepositoryConstructorBuilder builder) { assertThat(contributedTypeName).isNotNull(); // required constructor arguments need to be present at this point - Map requiredArgs = new LinkedHashMap<>(contributor.requiredArgs()); + Map requiredArgs = new LinkedHashMap<>( + contributor.getAotFragmentMetadata().getAutowireFields()); assertThat(requiredArgs).hasSize(1); // decorator kicks in and enhanced the BeanDefinition. No files written so far. @@ -86,7 +87,7 @@ protected void customizeConstructor(AotRepositoryConstructorBuilder builder) { // make sure write operation for generated content did not change constructor nor type name assertThat(contributor.getContributedTypeName()).isEqualTo(contributedTypeName); - assertThat(contributor.requiredArgs()).containsExactlyEntriesOf(requiredArgs); + assertThat(contributor.getAotFragmentMetadata().getAutowireFields()).containsExactlyEntriesOf(requiredArgs); // file is actually present now assertThat(generationContext.getGeneratedFiles().getGeneratedFiles(Kind.SOURCE)) diff --git a/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java b/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java index 7e4e0747b8..1b3a7861c9 100644 --- a/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java +++ b/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java @@ -15,20 +15,17 @@ */ package org.springframework.data.repository.aot.generate; -import java.io.IOException; import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.List; import java.util.Set; -import java.util.function.Consumer; import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.core.annotation.MergedAnnotation; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.test.tools.ClassFile; -import org.springframework.data.aot.AotTypeConfiguration; +import org.springframework.data.aot.AotContext; import org.springframework.data.repository.config.AotRepositoryContext; +import org.springframework.data.repository.config.AotRepositoryContextSupport; import org.springframework.data.repository.config.RepositoryConfigurationSource; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.RepositoryComposition; @@ -39,12 +36,12 @@ * * @author Christoph Strobl */ -class DummyModuleAotRepositoryContext implements AotRepositoryContext { +class DummyModuleAotRepositoryContext extends AotRepositoryContextSupport { private final StubRepositoryInformation repositoryInformation; - private final MockEnvironment environment = new MockEnvironment(); public DummyModuleAotRepositoryContext(Class repositoryInterface, @Nullable RepositoryComposition composition) { + super(AotContext.from(new DefaultListableBeanFactory(), new MockEnvironment())); this.repositoryInformation = new StubRepositoryInformation(repositoryInterface, composition); } @@ -65,7 +62,7 @@ public ConfigurableListableBeanFactory getBeanFactory() { @Override public MockEnvironment getEnvironment() { - return environment; + return (MockEnvironment) super.getEnvironment(); } @Override @@ -78,16 +75,6 @@ public IntrospectedBeanDefinition introspectBeanDefinition(String beanName) { return null; } - @Override - public void typeConfiguration(Class type, Consumer configurationConsumer) { - - } - - @Override - public Collection typeConfigurations() { - return List.of(); - } - @Override public String getBeanName() { return "dummyRepository"; @@ -118,24 +105,4 @@ public Set> getResolvedTypes() { return Set.of(); } - @Override - public Set> getUserDomainTypes() { - return Set.of(); - } - - public List getRequiredContextFiles() { - return List.of(classFileForType(repositoryInformation.getRepositoryBaseClass())); - } - - static ClassFile classFileForType(Class type) { - - String name = type.getName(); - ClassPathResource cpr = new ClassPathResource(name.replaceAll("\\.", "/") + ".class"); - - try { - return ClassFile.of(name, cpr.getContentAsByteArray()); - } catch (IOException e) { - throw new IllegalArgumentException("Cannot open [%s].".formatted(cpr.getPath())); - } - } }