Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 8f34f0f

Browse files
christophstroblmp911de
authored andcommitted
Avoid attempts to override AOT generated query method metadata.
Prior to this change regenerating repository instances for eg. test execution caused trouble when trying to override existing json metadata files. We now back off in case of existing files and added an explicit config flag for users to opt out of having the metadata file being present in the target resources. Original pull request: #3355 Closes #3354
1 parent 9c2e518 commit 8f34f0f

File tree

4 files changed

+136
-21
lines changed

4 files changed

+136
-21
lines changed

‎src/main/java/org/springframework/data/aot/AotContext.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
public interface AotContext extends EnvironmentCapable {
5555

5656
String GENERATED_REPOSITORIES_ENABLED = "spring.aot.repositories.enabled";
57+
String GENERATED_REPOSITORIES_JSON_ENABLED = "spring.aot.repositories.metadata.enabled";
5758

5859
/**
5960
* Create an {@link AotContext} backed by the given {@link BeanFactory}.
@@ -116,6 +117,19 @@ default boolean isGeneratedRepositoriesEnabled(@Nullable String moduleName) {
116117
return environment.getProperty(modulePropertyName, Boolean.class, true);
117118
}
118119

120+
/**
121+
* Checks if repository metadata file writing is enabled by checking environment variables for general
122+
* enablement ({@link #GENERATED_REPOSITORIES_JSON_ENABLED})
123+
* <p>
124+
* Unset properties are considered being {@literal true}.
125+
*
126+
* @return indicator if repository metadata should be written
127+
* @since 5.0
128+
*/
129+
default boolean isGeneratedRepositoriesMetadataEnabled() {
130+
return getEnvironment().getProperty(GENERATED_REPOSITORIES_JSON_ENABLED, Boolean.class, true);
131+
}
132+
119133
/**
120134
* Returns a reference to the {@link ConfigurableListableBeanFactory} backing this {@link AotContext}.
121135
*

‎src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
*/
1616
package org.springframework.data.repository.aot.generate;
1717

18+
import java.io.ByteArrayInputStream;
1819
import java.lang.reflect.Method;
20+
import java.nio.charset.StandardCharsets;
1921
import java.util.Collections;
2022

2123
import org.apache.commons.logging.Log;
2224
import org.apache.commons.logging.LogFactory;
2325
import org.jspecify.annotations.Nullable;
24-
2526
import org.springframework.aot.generate.GeneratedClass;
27+
import org.springframework.aot.generate.GeneratedFiles.Kind;
2628
import org.springframework.aot.generate.GeneratedTypeReference;
2729
import org.springframework.aot.generate.GenerationContext;
2830
import org.springframework.aot.hint.MemberCategory;
@@ -49,6 +51,7 @@ public class RepositoryContributor {
4951
private static final Log logger = LogFactory.getLog(RepositoryContributor.class);
5052
private static final String FEATURE_NAME = "AotRepository";
5153

54+
private final AotRepositoryContext repositoryContext;
5255
private final AotRepositoryCreator creator;
5356
private @Nullable TypeReference contributedTypeName;
5457

@@ -59,6 +62,7 @@ public class RepositoryContributor {
5962
*/
6063
public RepositoryContributor(AotRepositoryContext repositoryContext) {
6164

65+
this.repositoryContext = repositoryContext;
6266
creator = AotRepositoryCreator.forRepository(repositoryContext.getRepositoryInformation(),
6367
repositoryContext.getModuleName(), createProjectionFactory());
6468
}
@@ -139,20 +143,18 @@ public final void contribute(GenerationContext generationContext) {
139143

140144
// write out the content
141145
AotBundle aotBundle = creator.create(targetTypeSpec);
142-
String repositoryJson;
143-
try {
144-
repositoryJson = aotBundle.metadata().get().toJson().toString(2);
145-
} catch (JSONException e) {
146-
throw new RuntimeException(e);
147146

148-
}
147+
String repositoryJson = generateJsonMetadata(aotBundle);
148+
149149
if (logger.isTraceEnabled()) {
150150

151-
logger.trace("""
152-
------ AOT Repository.json: %s ------
153-
%s
154-
-------------------
155-
""".formatted(aotBundle.repositoryJsonFileName(), repositoryJson));
151+
if (repositoryContext.isGeneratedRepositoriesMetadataEnabled()) {
152+
logger.trace("""
153+
------ AOT Repository.json: %s ------
154+
%s
155+
-------------------
156+
""".formatted(aotBundle.repositoryJsonFileName(), repositoryJson));
157+
}
156158

157159
TypeSpec typeSpec = targetTypeSpec.build();
158160
JavaFile javaFile = JavaFile.builder(creator.packageName(), typeSpec).build();
@@ -164,7 +166,14 @@ public final void contribute(GenerationContext generationContext) {
164166
""".formatted(typeSpec.name(), javaFile));
165167
}
166168

167-
generationContext.getGeneratedFiles().addResourceFile(aotBundle.repositoryJsonFileName(), repositoryJson);
169+
if (repositoryContext.isGeneratedRepositoriesMetadataEnabled()) {
170+
generationContext.getGeneratedFiles().handleFile(Kind.RESOURCE, aotBundle.repositoryJsonFileName(),
171+
fileHandler -> {
172+
if (!fileHandler.exists()) {
173+
fileHandler.create(() -> new ByteArrayInputStream(repositoryJson.getBytes(StandardCharsets.UTF_8)));
174+
}
175+
});
176+
}
168177
});
169178

170179
// generate native runtime hints
@@ -176,6 +185,20 @@ public final void contribute(GenerationContext generationContext) {
176185
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
177186
}
178187

188+
private String generateJsonMetadata(AotBundle aotBundle) {
189+
190+
String repositoryJson = "";
191+
192+
if (repositoryContext.isGeneratedRepositoriesMetadataEnabled()) {
193+
try {
194+
repositoryJson = aotBundle.metadata().get().toJson().toString(2);
195+
} catch (JSONException e) {
196+
throw new RuntimeException(e);
197+
}
198+
}
199+
return repositoryJson;
200+
}
201+
179202
/**
180203
* Customization hook for store implementations to customize class after building the entire class.
181204
*/

‎src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,15 @@
2121
import java.util.Set;
2222

2323
import org.jspecify.annotations.Nullable;
24-
2524
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2625
import org.springframework.core.annotation.MergedAnnotation;
27-
import org.springframework.core.env.Environment;
2826
import org.springframework.core.io.ClassPathResource;
2927
import org.springframework.core.test.tools.ClassFile;
3028
import org.springframework.data.repository.config.AotRepositoryContext;
3129
import org.springframework.data.repository.config.RepositoryConfigurationSource;
3230
import org.springframework.data.repository.core.RepositoryInformation;
3331
import org.springframework.data.repository.core.support.RepositoryComposition;
32+
import org.springframework.mock.env.MockEnvironment;
3433

3534
/**
3635
* Dummy {@link AotRepositoryContext} used to simulate module specific repository implementation.
@@ -40,6 +39,7 @@
4039
class DummyModuleAotRepositoryContext implements AotRepositoryContext {
4140

4241
private final StubRepositoryInformation repositoryInformation;
42+
private final MockEnvironment environment = new MockEnvironment();
4343

4444
public DummyModuleAotRepositoryContext(Class<?> repositoryInterface, @Nullable RepositoryComposition composition) {
4545
this.repositoryInformation = new StubRepositoryInformation(repositoryInterface, composition);
@@ -61,8 +61,8 @@ public ConfigurableListableBeanFactory getBeanFactory() {
6161
}
6262

6363
@Override
64-
public Environment getEnvironment() {
65-
return null;
64+
public MockEnvironment getEnvironment() {
65+
return environment;
6666
}
6767

6868
@Override

‎src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
*/
1616
package org.springframework.data.repository.aot.generate;
1717

18-
import static org.assertj.core.api.Assertions.*;
19-
import static org.mockito.ArgumentMatchers.*;
20-
import static org.mockito.Mockito.*;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.mockito.ArgumentMatchers.any;
20+
import static org.mockito.ArgumentMatchers.argThat;
21+
import static org.mockito.Mockito.mock;
22+
import static org.mockito.Mockito.when;
2123

2224
import example.UserRepository;
2325
import example.UserRepositoryExtension;
@@ -33,6 +35,7 @@
3335
import org.jspecify.annotations.Nullable;
3436
import org.junit.jupiter.api.Test;
3537
import org.springframework.aot.test.generate.TestGenerationContext;
38+
import org.springframework.core.test.tools.ResourceFile;
3639
import org.springframework.core.test.tools.TestCompiler;
3740
import org.springframework.data.aot.CodeContributionAssert;
3841
import org.springframework.data.repository.CrudRepository;
@@ -137,6 +140,82 @@ void writesCapturedQueryMetadataToResources() {
137140
new CodeContributionAssert(generationContext).contributesReflectionFor(expectedTypeName);
138141
}
139142

143+
@Test // GH-3354
144+
void doesNotWriteCapturedQueryMetadataToResourcesIfDisabled() {
145+
146+
DummyModuleAotRepositoryContext aotContext = new DummyModuleAotRepositoryContext(UserRepository.class, null);
147+
aotContext.getEnvironment().setProperty("spring.aot.repositories.metadata.enabled", "false");
148+
149+
RepositoryContributor repositoryContributor = new RepositoryContributor(aotContext) {
150+
151+
@Override
152+
protected @Nullable MethodContributor<? extends QueryMethod> contributeQueryMethod(Method method) {
153+
154+
return MethodContributor
155+
.forQueryMethod(
156+
new QueryMethod(method, getRepositoryInformation(), getProjectionFactory(), DefaultParameters::new))
157+
.withMetadata(() -> Map.of("filter", "FILTER(%s > 1ドル)".formatted(method.getName()), "project",
158+
Arrays.stream(method.getParameters()).map(Parameter::getName).toList()))
159+
.contribute(context -> {
160+
161+
CodeBlock.Builder builder = CodeBlock.builder();
162+
if (!ClassUtils.isVoidType(method.getReturnType())) {
163+
builder.addStatement("return null");
164+
}
165+
166+
return builder.build();
167+
});
168+
}
169+
};
170+
171+
TestGenerationContext generationContext = new TestGenerationContext(UserRepository.class);
172+
repositoryContributor.contribute(generationContext);
173+
generationContext.writeGeneratedContent();
174+
175+
TestCompiler.forSystem().with(generationContext).compile(compiled -> {
176+
assertThat(compiled.getResourceFiles()).isEmpty();
177+
});
178+
}
179+
180+
@Test // GH-3354
181+
void doesNotWriteCapturedQueryMetadataToResourcesIfAlreadyExists() {
182+
183+
DummyModuleAotRepositoryContext aotContext = new DummyModuleAotRepositoryContext(UserRepository.class, null);
184+
185+
RepositoryContributor repositoryContributor = new RepositoryContributor(aotContext) {
186+
187+
@Override
188+
protected @Nullable MethodContributor<? extends QueryMethod> contributeQueryMethod(Method method) {
189+
190+
return MethodContributor
191+
.forQueryMethod(
192+
new QueryMethod(method, getRepositoryInformation(), getProjectionFactory(), DefaultParameters::new))
193+
.withMetadata(() -> Map.of("filter", "FILTER(%s > 1ドル)".formatted(method.getName()), "project",
194+
Arrays.stream(method.getParameters()).map(Parameter::getName).toList()))
195+
.contribute(context -> {
196+
197+
CodeBlock.Builder builder = CodeBlock.builder();
198+
if (!ClassUtils.isVoidType(method.getReturnType())) {
199+
builder.addStatement("return null");
200+
}
201+
202+
return builder.build();
203+
});
204+
}
205+
};
206+
207+
TestGenerationContext generationContext = new TestGenerationContext(UserRepository.class);
208+
repositoryContributor.contribute(generationContext);
209+
generationContext.writeGeneratedContent();
210+
211+
ResourceFile rf = ResourceFile.of(UserRepository.class.getName().replace('.', '/') + ".json",
212+
"But you're untouchable, burning brighter than the sun");
213+
TestCompiler.forSystem().with(generationContext).withResources(rf).compile(compiled -> {
214+
String content = compiled.getResourceFile().getContent();
215+
assertThat(content).contains("you're untouchable").doesNotContain("FILTER(doSomething > 1ドル)");
216+
});
217+
}
218+
140219
@Test // GH-3279
141220
void callsMethodContributionForQueryMethod() {
142221

@@ -175,7 +254,6 @@ void doesNotContributeBaseClassMethods() {
175254
contributor.contribute(testGenerationContext);
176255
testGenerationContext.writeGeneratedContent();
177256

178-
179257
contributor.verifyContributedMethods().isNotEmpty().doesNotContainKey("findByFirstname");
180258
}
181259

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /