diff --git a/pom.xml b/pom.xml index a6dc167a03..b6cf6dcb33 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 4.0.0-SNAPSHOT + 4.0.x-GH-3322-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 ab62c1a45e..f99531aeb4 100644 --- a/src/main/java/org/springframework/data/aot/AotContext.java +++ b/src/main/java/org/springframework/data/aot/AotContext.java @@ -18,12 +18,12 @@ import java.lang.annotation.Annotation; import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; - import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; @@ -35,6 +35,7 @@ import org.springframework.core.env.StandardEnvironment; import org.springframework.data.util.TypeScanner; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * The context in which the AOT processing happens. Grants access to the {@link ConfigurableListableBeanFactory @@ -84,6 +85,37 @@ static AotContext from(BeanFactory beanFactory, Environment environment) { return new DefaultAotContext(beanFactory, environment); } + /** + * Checks if repository code generation is enabled for a given module by checking environment variables for general + * enablement ({@link #GENERATED_REPOSITORIES_ENABLED}) and store specific ones following the pattern + * {@literal spring.aot.repositories.<module-name>.enabled}. + *

+ * {@link #GENERATED_REPOSITORIES_ENABLED} acts as a kill switch, if disabled, store specific flags have no effect. + *

+ * Missing properties are interpreted as {@literal true}. + * + * @param moduleName The 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 + */ + default boolean isGeneratedRepositoriesEnabled(@Nullable String moduleName) { + + Environment environment = getEnvironment(); + Boolean codeGenerationEnabled = environment.getProperty(GENERATED_REPOSITORIES_ENABLED, Boolean.class, true); + if (!codeGenerationEnabled) { + return false; + } + + if (!StringUtils.hasText(moduleName)) { + return true; + } + + String modulePropertyName = GENERATED_REPOSITORIES_ENABLED.replace("enabled", + "%s.enabled".formatted(moduleName.toLowerCase(Locale.US))); + return environment.getProperty(modulePropertyName, Boolean.class, true); + } + /** * Returns a reference to the {@link ConfigurableListableBeanFactory} backing this {@link AotContext}. * diff --git a/src/test/java/org/springframework/data/aot/AotContextUnitTests.java b/src/test/java/org/springframework/data/aot/AotContextUnitTests.java new file mode 100644 index 0000000000..ce22035bd3 --- /dev/null +++ b/src/test/java/org/springframework/data/aot/AotContextUnitTests.java @@ -0,0 +1,91 @@ +/* + * 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.aot; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.Mockito; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.env.Environment; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.util.StringUtils; + +/** + * Tests for {@link AotContext}. + * + * @author Christoph Strobl + */ +class AotContextUnitTests { + + @ParameterizedTest // GH-3322 + @CsvSource({ // + "'spring.aot.repositories.enabled', '', '', '', true", // + "'spring.aot.repositories.enabled', 'true', '', '', true", // + "'spring.aot.repositories.enabled', 'false', '', '', false", // + "'spring.aot.repositories.enabled', '', 'commons', 'true', true", // + "'spring.aot.repositories.enabled', 'true', 'commons', 'true', true", // + "'spring.aot.repositories.enabled', '', 'commons', 'false', false", // + "'spring.aot.repositories.enabled', 'false', 'commons', 'true', false" // + }) + void considersEnvironmentSettingsForGeneratedRepositories(String generalFlag, String generalValue, String storeName, + String storeValue, boolean enabled) { + + MockAotContext ctx = new MockAotContext(); + if (StringUtils.hasText(generalFlag) && StringUtils.hasText(generalValue)) { + ctx.withProperty(generalFlag, generalValue); + } + if (StringUtils.hasText(storeName) && StringUtils.hasText(storeValue)) { + ctx.withProperty("spring.aot.repositories.%s.enabled".formatted(storeName), storeValue); + } + + Assertions.assertThat(ctx.isGeneratedRepositoriesEnabled(storeName)).isEqualTo(enabled); + } + + class MockAotContext implements AotContext { + + private final MockEnvironment environment; + + public MockAotContext() { + this.environment = new MockEnvironment(); + } + + MockAotContext withProperty(String key, String value) { + environment.setProperty(key, value); + return this; + } + + @Override + public ConfigurableListableBeanFactory getBeanFactory() { + return Mockito.mock(ConfigurableListableBeanFactory.class); + } + + @Override + public TypeIntrospector introspectType(String typeName) { + return Mockito.mock(TypeIntrospector.class); + } + + @Override + public IntrospectedBeanDefinition introspectBeanDefinition(String beanName) { + return Mockito.mock(IntrospectedBeanDefinition.class); + } + + @Override + public Environment getEnvironment() { + return environment; + } + } +}