Skip to content

Commit 34ad67e

Browse files
Introduce reusable code fragments for generating code.
1 parent e832450 commit 34ad67e

File tree

14 files changed

+1110
-505
lines changed

14 files changed

+1110
-505
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AggregationBlocks.java

Lines changed: 287 additions & 276 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.repository.aot;
17+
18+
import org.springframework.javapoet.CodeBlock;
19+
import org.springframework.javapoet.CodeBlock.Builder;
20+
21+
/**
22+
* @author Christoph Strobl
23+
*/
24+
public class BuilderStyleSnippet implements Snippet {
25+
26+
private final String targetVariableName;
27+
private final String methodName;
28+
private final Snippet argumentValue;
29+
30+
BuilderStyleSnippet(String targetVariableName, String methodName, Snippet argumentValue) {
31+
this.targetVariableName = targetVariableName;
32+
this.methodName = methodName;
33+
this.argumentValue = argumentValue;
34+
}
35+
36+
@Override
37+
public CodeBlock code() {
38+
39+
Builder builder = CodeBlock.builder();
40+
builder.add("$1L = $1L.$2L($3L);\n", targetVariableName, methodName, argumentValue.code());
41+
return builder.build();
42+
}
43+
44+
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/DeleteBlocks.java

Lines changed: 65 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.Optional;
1919

2020
import org.jspecify.annotations.NullUnmarked;
21+
import org.springframework.core.ResolvableType;
2122
import org.springframework.data.mongodb.core.ExecutableRemoveOperation.ExecutableRemove;
2223
import org.springframework.data.mongodb.core.MongoOperations;
2324
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecution;
@@ -35,66 +36,68 @@
3536
*/
3637
class DeleteBlocks {
3738

38-
@NullUnmarked
39-
static class DeleteExecutionCodeBlockBuilder {
40-
41-
private final AotQueryMethodGenerationContext context;
42-
private final MongoQueryMethod queryMethod;
43-
private String queryVariableName;
44-
45-
DeleteExecutionCodeBlockBuilder(AotQueryMethodGenerationContext context, MongoQueryMethod queryMethod) {
46-
47-
this.context = context;
48-
this.queryMethod = queryMethod;
49-
}
50-
51-
DeleteExecutionCodeBlockBuilder referencing(String queryVariableName) {
52-
53-
this.queryVariableName = queryVariableName;
54-
return this;
55-
}
56-
57-
CodeBlock build() {
58-
59-
String mongoOpsRef = context.fieldNameOf(MongoOperations.class);
60-
Builder builder = CodeBlock.builder();
61-
62-
Class<?> domainType = context.getRepositoryInformation().getDomainType();
63-
boolean isProjecting = context.getActualReturnType() != null
64-
&& !ObjectUtils.nullSafeEquals(TypeName.get(domainType), context.getActualReturnType());
65-
66-
Object actualReturnType = isProjecting ? context.getActualReturnType().getType() : domainType;
67-
68-
builder.add("\n");
69-
builder.addStatement("$1T<$2T> $3L = $4L.remove($2T.class)", ExecutableRemove.class, domainType,
70-
context.localVariable("remover"), mongoOpsRef);
71-
72-
DeleteExecution.Type type = DeleteExecution.Type.FIND_AND_REMOVE_ALL;
73-
if (!queryMethod.isCollectionQuery()) {
74-
if (!ClassUtils.isPrimitiveOrWrapper(context.getMethod().getReturnType())) {
75-
type = DeleteExecution.Type.FIND_AND_REMOVE_ONE;
76-
} else {
77-
type = DeleteExecution.Type.ALL;
78-
}
79-
}
80-
81-
actualReturnType = ClassUtils.isPrimitiveOrWrapper(context.getMethod().getReturnType())
82-
? TypeName.get(context.getMethod().getReturnType())
83-
: queryMethod.isCollectionQuery() ? context.getReturnTypeName() : actualReturnType;
84-
85-
if (ClassUtils.isVoidType(context.getMethod().getReturnType())) {
86-
builder.addStatement("new $T($L, $T.$L).execute($L)", DeleteExecution.class, context.localVariable("remover"),
87-
DeleteExecution.Type.class, type.name(), queryVariableName);
88-
} else if (context.getMethod().getReturnType() == Optional.class) {
89-
builder.addStatement("return $T.ofNullable(($T) new $T($L, $T.$L).execute($L))", Optional.class,
90-
actualReturnType, DeleteExecution.class, context.localVariable("remover"), DeleteExecution.Type.class,
91-
type.name(), queryVariableName);
92-
} else {
93-
builder.addStatement("return ($T) new $T($L, $T.$L).execute($L)", actualReturnType, DeleteExecution.class,
94-
context.localVariable("remover"), DeleteExecution.Type.class, type.name(), queryVariableName);
95-
}
96-
97-
return builder.build();
98-
}
99-
}
39+
@NullUnmarked
40+
static class DeleteExecutionCodeBlockBuilder {
41+
42+
private final AotQueryMethodGenerationContext context;
43+
private final MongoQueryMethod queryMethod;
44+
private String queryVariableName;
45+
46+
DeleteExecutionCodeBlockBuilder(AotQueryMethodGenerationContext context, MongoQueryMethod queryMethod) {
47+
48+
this.context = context;
49+
this.queryMethod = queryMethod;
50+
}
51+
52+
DeleteExecutionCodeBlockBuilder referencing(String queryVariableName) {
53+
54+
this.queryVariableName = queryVariableName;
55+
return this;
56+
}
57+
58+
CodeBlock build() {
59+
60+
String mongoOpsRef = context.fieldNameOf(MongoOperations.class);
61+
Builder builder = CodeBlock.builder();
62+
63+
Class<?> domainType = context.getRepositoryInformation().getDomainType();
64+
boolean isProjecting = context.getActualReturnType() != null
65+
&& !ObjectUtils.nullSafeEquals(TypeName.get(domainType), context.getActualReturnType());
66+
67+
Object actualReturnType = isProjecting ? context.getActualReturnType().getType() : domainType;
68+
69+
builder.add("\n");
70+
VariableSnippet remover = Snippet.declare(builder)
71+
.variable(ResolvableType.forClassWithGenerics(ExecutableRemove.class, domainType),
72+
context.localVariable("remover"))
73+
.as("$L.remove($T.class)", mongoOpsRef, domainType);
74+
75+
DeleteExecution.Type type = DeleteExecution.Type.FIND_AND_REMOVE_ALL;
76+
if (!queryMethod.isCollectionQuery()) {
77+
if (!ClassUtils.isPrimitiveOrWrapper(context.getMethod().getReturnType())) {
78+
type = DeleteExecution.Type.FIND_AND_REMOVE_ONE;
79+
} else {
80+
type = DeleteExecution.Type.ALL;
81+
}
82+
}
83+
84+
actualReturnType = ClassUtils.isPrimitiveOrWrapper(context.getMethod().getReturnType())
85+
? TypeName.get(context.getMethod().getReturnType())
86+
: queryMethod.isCollectionQuery() ? context.getReturnTypeName() : actualReturnType;
87+
88+
if (ClassUtils.isVoidType(context.getMethod().getReturnType())) {
89+
builder.addStatement("new $T($L, $T.$L).execute($L)", DeleteExecution.class, remover.getVariableName(),
90+
DeleteExecution.Type.class, type.name(), queryVariableName);
91+
} else if (context.getMethod().getReturnType() == Optional.class) {
92+
builder.addStatement("return $T.ofNullable(($T) new $T($L, $T.$L).execute($L))", Optional.class,
93+
actualReturnType, DeleteExecution.class, remover.getVariableName(), DeleteExecution.Type.class, type.name(),
94+
queryVariableName);
95+
} else {
96+
builder.addStatement("return ($T) new $T($L, $T.$L).execute($L)", actualReturnType, DeleteExecution.class,
97+
context.localVariable("remover"), DeleteExecution.Type.class, type.name(), queryVariableName);
98+
}
99+
100+
return builder.build();
101+
}
102+
}
100103
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.repository.aot;
17+
18+
import org.springframework.javapoet.CodeBlock;
19+
20+
/**
21+
* @author Christoph Strobl
22+
* @since 5.0
23+
*/
24+
class ExpressionSnippet implements Snippet {
25+
26+
private final CodeBlock block;
27+
private final boolean requiresEvaluation;
28+
29+
public ExpressionSnippet(CodeBlock block) {
30+
this(block, false);
31+
}
32+
33+
public ExpressionSnippet(Snippet block) {
34+
this(block.code(), block instanceof ExpressionSnippet eb && eb.requiresEvaluation());
35+
}
36+
37+
public ExpressionSnippet(CodeBlock block, boolean requiresEvaluation) {
38+
this.block = block;
39+
this.requiresEvaluation = requiresEvaluation;
40+
}
41+
42+
public static ExpressionSnippet empty() {
43+
return new ExpressionSnippet(CodeBlock.builder().build());
44+
}
45+
46+
public boolean requiresEvaluation() {
47+
return requiresEvaluation;
48+
}
49+
50+
public CodeBlock code() {
51+
return block;
52+
}
53+
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/GeoBlocks.java

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -50,38 +50,37 @@ CodeBlock build() {
5050
CodeBlock.Builder builder = CodeBlock.builder();
5151
builder.add("\n");
5252

53-
String locationParameterName = context.getParameterName(queryMethod.getParameters().getNearIndex());
54-
55-
builder.addStatement("$1T $2L = $1T.near($3L)", NearQuery.class, variableName, locationParameterName);
53+
VariableSnippet query = Snippet.declare(builder).variable(NearQuery.class, variableName).as("$T.near($L)",
54+
NearQuery.class, context.getParameterName(queryMethod.getParameters().getNearIndex()));
5655

5756
if (queryMethod.getParameters().getRangeIndex() != -1) {
5857

59-
String rangeParametername = context.getParameterName(queryMethod.getParameters().getRangeIndex());
60-
String minVarName = context.localVariable("min");
61-
String maxVarName = context.localVariable("max");
58+
String rangeParameter = context.getParameterName(queryMethod.getParameters().getRangeIndex());
6259

63-
builder.beginControlFlow("if($L.getLowerBound().isBounded())", rangeParametername);
64-
builder.addStatement("$1T $2L = $3L.getLowerBound().getValue().get()", Distance.class, minVarName,
65-
rangeParametername);
66-
builder.addStatement("$1L.minDistance($2L).in($2L.getMetric())", variableName, minVarName);
60+
builder.beginControlFlow("if($L.getLowerBound().isBounded())", rangeParameter);
61+
VariableSnippet min = Snippet.declare(builder).variable(Distance.class, context.localVariable("min"))
62+
.as("$L.getLowerBound().getValue().get()", rangeParameter);
63+
builder.addStatement("$1L.minDistance($2L).in($2L.getMetric())", query.getVariableName(),
64+
min.getVariableName());
6765
builder.endControlFlow();
6866

69-
builder.beginControlFlow("if($L.getUpperBound().isBounded())", rangeParametername);
70-
builder.addStatement("$1T $2L = $3L.getUpperBound().getValue().get()", Distance.class, maxVarName,
71-
rangeParametername);
72-
builder.addStatement("$1L.maxDistance($2L).in($2L.getMetric())", variableName, maxVarName);
67+
builder.beginControlFlow("if($L.getUpperBound().isBounded())", rangeParameter);
68+
VariableSnippet max = Snippet.declare(builder).variable(Distance.class, context.localVariable("max"))
69+
.as("$L.getUpperBound().getValue().get()", rangeParameter);
70+
builder.addStatement("$1L.maxDistance($2L).in($2L.getMetric())", query.getVariableName(),
71+
max.getVariableName());
7372
builder.endControlFlow();
7473
} else {
7574

76-
String distanceParametername = context.getParameterName(queryMethod.getParameters().getMaxDistanceIndex());
77-
builder.addStatement("$1L.maxDistance($2L).in($2L.getMetric())", variableName, distanceParametername);
75+
String distanceParameter = context.getParameterName(queryMethod.getParameters().getMaxDistanceIndex());
76+
builder.addStatement("$1L.maxDistance($2L).in($2L.getMetric())", query.code(), distanceParameter);
7877
}
7978

8079
if (context.getPageableParameterName() != null) {
81-
builder.addStatement("$L.with($L)", variableName, context.getPageableParameterName());
80+
builder.addStatement("$L.with($L)", query.code(), context.getPageableParameterName());
8281
}
8382

84-
MongoCodeBlocks.appendReadPreference(context, builder, variableName);
83+
MongoCodeBlocks.appendReadPreference(context, builder, query.getVariableName());
8584

8685
return builder.build();
8786
}
@@ -115,29 +114,29 @@ CodeBlock build() {
115114
CodeBlock.Builder builder = CodeBlock.builder();
116115
builder.add("\n");
117116

118-
String executorVar = context.localVariable("nearFinder");
119-
builder.addStatement("var $L = $L.query($T.class).near($L)", executorVar,
120-
context.fieldNameOf(MongoOperations.class), context.getRepositoryInformation().getDomainType(),
121-
queryVariableName);
117+
VariableSnippet queryExecutor = Snippet.declare(builder).variable(context.localVariable("nearFinder")).as(
118+
"$L.query($T.class).near($L)", context.fieldNameOf(MongoOperations.class),
119+
context.getRepositoryInformation().getDomainType(), queryVariableName);
122120

123121
if (ClassUtils.isAssignable(GeoPage.class, context.getReturnType().getRawClass())) {
124122

125-
String geoResultVar = context.localVariable("geoResult");
126-
builder.addStatement("var $L = $L.all()", geoResultVar, executorVar);
123+
VariableSnippet geoResult = Snippet.declare(builder).variable(context.localVariable("geoResult")).as("$L.all()",
124+
queryExecutor.getVariableName());
127125

128126
builder.beginControlFlow("if($L.isUnpaged())", context.getPageableParameterName());
129-
builder.addStatement("return new $T<>($L)", GeoPage.class, geoResultVar);
127+
builder.addStatement("return new $T<>($L)", GeoPage.class, geoResult.getVariableName());
130128
builder.endControlFlow();
131129

132-
String pageVar = context.localVariable("resultPage");
133-
builder.addStatement("var $L = $T.getPage($L.getContent(), $L, () -> $L.count())", pageVar,
134-
PageableExecutionUtils.class, geoResultVar, context.getPageableParameterName(), executorVar);
135-
builder.addStatement("return new $T<>($L, $L, $L.getTotalElements())", GeoPage.class, geoResultVar,
136-
context.getPageableParameterName(), pageVar);
130+
VariableSnippet resultPage = Snippet.declare(builder).variable(context.localVariable("resultPage")).as(
131+
"$T.getPage($L.getContent(), $L, () -> $L.count())", PageableExecutionUtils.class,
132+
geoResult.getVariableName(), context.getPageableParameterName(), queryExecutor.getVariableName());
133+
134+
builder.addStatement("return new $T<>($L, $L, $L.getTotalElements())", GeoPage.class,
135+
geoResult.getVariableName(), context.getPageableParameterName(), resultPage.getVariableName());
137136
} else if (ClassUtils.isAssignable(GeoResults.class, context.getReturnType().getRawClass())) {
138-
builder.addStatement("return $L.all()", executorVar);
137+
builder.addStatement("return $L.all()", queryExecutor.getVariableName());
139138
} else {
140-
builder.addStatement("return $L.all().getContent()", executorVar);
139+
builder.addStatement("return $L.all().getContent()", queryExecutor.getVariableName());
141140
}
142141
return builder.build();
143142
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoCodeBlocks.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,25 @@ static GeoNearExecutionCodeBlockBuilder geoNearExecutionBlockBuilder(AotQueryMet
170170
return new GeoNearExecutionCodeBlockBuilder(context, queryMethod);
171171
}
172172

173+
static CodeBlock asDocument(String source, Map<String, CodeBlock> arguments) {
174+
175+
Builder builder = CodeBlock.builder();
176+
if (!StringUtils.hasText(source)) {
177+
builder.add("new $T()", Document.class);
178+
} else if (!containsPlaceholder(source)) {
179+
builder.add("$T.parse($S)", Document.class, source);
180+
} else {
181+
builder.add("bindParameters($S, ", source);
182+
if (containsNamedPlaceholder(source)) {
183+
builder.add(renderArgumentMap(arguments));
184+
} else {
185+
builder.add(renderArgumentArray(arguments));
186+
}
187+
builder.add(");\n");
188+
}
189+
return builder.build();
190+
}
191+
173192
static CodeBlock renderExpressionToDocument(@Nullable String source, String variableName,
174193
Map<String, CodeBlock> arguments) {
175194

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoRepositoryContributor.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,10 @@ protected void customizeConstructor(AotRepositoryConstructorBuilder constructorB
111111
AnnotatedElementUtils.findMergedAnnotation(method, Query.class), method);
112112

113113
if (queryMethod.isSearchQuery() || method.isAnnotationPresent(VectorSearch.class)) {
114-
return searchMethodContributor(queryMethod, new SearchInteraction(query.getQuery()));
114+
115+
VectorSearch vectorSearch = AnnotatedElementUtils.findMergedAnnotation(method, VectorSearch.class);
116+
return searchMethodContributor(queryMethod, new SearchInteraction(getRepositoryInformation().getDomainType(),
117+
vectorSearch, query.getQuery(), queryMethod.getParameters()));
115118
}
116119

117120
if (queryMethod.isGeoNearQuery() || (queryMethod.getParameters().getMaxDistanceIndex() != -1
@@ -233,8 +236,9 @@ static MethodContributor<MongoQueryMethod> searchMethodContributor(MongoQueryMet
233236

234237
String variableName = "search";
235238

236-
builder.add(new VectorSearchBocks.VectorSearchQueryCodeBlockBuilder(context, queryMethod)
237-
.usingVariableName(variableName).withFilter(interaction.getFilter()).build());
239+
builder.add(
240+
new VectorSearchBocks.VectorSearchQueryCodeBlockBuilder(context, queryMethod, interaction.getSearchPath())
241+
.usingVariableName(variableName).withFilter(interaction.getFilter()).build());
238242

239243
return builder.build();
240244
});

0 commit comments

Comments
 (0)