diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoBlocks.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoBlocks.java index 1f550d814..d811ee9a5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoBlocks.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoBlocks.java @@ -18,10 +18,13 @@ package org.springframework.data.mongodb.aot.generated; import java.lang.reflect.Parameter; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.bson.Document; + +import org.springframework.core.annotation.MergedAnnotation; import org.springframework.data.mongodb.BindableMongoExpression; import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery; import org.springframework.data.mongodb.core.ExecutableRemoveOperation.ExecutableRemove; @@ -33,7 +36,8 @@ import org.springframework.data.mongodb.repository.query.MongoQueryExecution.Del import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecutionX.Type; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagedExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.SlicedExecution; -import org.springframework.data.repository.aot.generate.AotRepositoryMethodGenerationContext; +import org.springframework.data.mongodb.repository.query.MongoQueryMethod; +import org.springframework.data.repository.aot.generate.AotQueryMethodGenerationContext; import org.springframework.javapoet.ClassName; import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock.Builder; @@ -50,25 +54,29 @@ public class MongoBlocks { private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)"); - static QueryBlockBuilder queryBlockBuilder(AotRepositoryMethodGenerationContext context) { - return new QueryBlockBuilder(context); + static QueryBlockBuilder queryBlockBuilder(AotQueryMethodGenerationContext context, MongoQueryMethod queryMethod) { + return new QueryBlockBuilder(context, queryMethod); } - static QueryExecutionBlockBuilder queryExecutionBlockBuilder(AotRepositoryMethodGenerationContext context) { - return new QueryExecutionBlockBuilder(context); + static QueryExecutionBlockBuilder queryExecutionBlockBuilder(AotQueryMethodGenerationContext context, + MongoQueryMethod queryMethod) { + return new QueryExecutionBlockBuilder(context, queryMethod); } - static DeleteExecutionBuilder deleteExecutionBlockBuilder(AotRepositoryMethodGenerationContext context) { - return new DeleteExecutionBuilder(context); + static DeleteExecutionBuilder deleteExecutionBlockBuilder(AotQueryMethodGenerationContext context, + MongoQueryMethod queryMethod) { + return new DeleteExecutionBuilder(context, queryMethod); } static class DeleteExecutionBuilder { - AotRepositoryMethodGenerationContext context; + private final AotQueryMethodGenerationContext context; + private final MongoQueryMethod queryMethod; String queryVariableName; - public DeleteExecutionBuilder(AotRepositoryMethodGenerationContext context) { + public DeleteExecutionBuilder(AotQueryMethodGenerationContext context, MongoQueryMethod queryMethod) { this.context = context; + this.queryMethod = queryMethod; } public DeleteExecutionBuilder referencing(String queryVariableName) { @@ -85,15 +93,16 @@ public class MongoBlocks { && !ObjectUtils.nullSafeEquals(TypeName.get(context.getRepositoryInformation().getDomainType()), context.getActualReturnType()); - Object actualReturnType = isProjecting ? context.getActualReturnType() + Object actualReturnType = isProjecting ? context.getActualReturnType().getType() : context.getRepositoryInformation().getDomainType(); builder.add("\n"); - builder.addStatement("$T<$T> remover = $L.remove($T.class)", ExecutableRemove.class, actualReturnType, + builder.addStatement("$T<$T> remover = $L.remove($T.class)", ExecutableRemove.class, + context.getRepositoryInformation().getDomainType(), mongoOpsRef, context.getRepositoryInformation().getDomainType()); Type type = Type.FIND_AND_REMOVE_ALL; - if (context.returnsSingleValue()) { + if (!queryMethod.isCollectionQuery()) { if (!ClassUtils.isPrimitiveOrWrapper(context.getMethod().getReturnType())) { type = Type.FIND_AND_REMOVE_ONE; } else { @@ -103,7 +112,7 @@ public class MongoBlocks { actualReturnType = ClassUtils.isPrimitiveOrWrapper(context.getMethod().getReturnType()) ? ClassName.get(context.getMethod().getReturnType()) - : context.returnsSingleValue() ? actualReturnType : context.getReturnType(); + : queryMethod.isCollectionQuery() ? context.getReturnTypeName() : actualReturnType; builder.addStatement("return ($T) new $T(remover, $T.$L).execute($L)", actualReturnType, DeleteExecutionX.class, DeleteExecutionX.Type.class, type.name(), queryVariableName); @@ -114,11 +123,14 @@ public class MongoBlocks { static class QueryExecutionBlockBuilder { - AotRepositoryMethodGenerationContext context; + private final AotQueryMethodGenerationContext context; + private final MongoQueryMethod queryMethod; private String queryVariableName; + private boolean count, exists; - public QueryExecutionBlockBuilder(AotRepositoryMethodGenerationContext context) { + public QueryExecutionBlockBuilder(AotQueryMethodGenerationContext context, MongoQueryMethod queryMethod) { this.context = context; + this.queryMethod = queryMethod; } QueryExecutionBlockBuilder referencing(String queryVariableName) { @@ -127,16 +139,24 @@ public class MongoBlocks { return this; } + QueryExecutionBlockBuilder count(boolean count) { + this.count = count; + return this; + } + + QueryExecutionBlockBuilder exists(boolean exists) { + this.exists = exists; + return this; + } + CodeBlock build() { String mongoOpsRef = context.fieldNameOf(MongoOperations.class); Builder builder = CodeBlock.builder(); - boolean isProjecting = context.getActualReturnType() != null - && !ObjectUtils.nullSafeEquals(TypeName.get(context.getRepositoryInformation().getDomainType()), - context.getActualReturnType()); - Object actualReturnType = isProjecting ? context.getActualReturnType() + boolean isProjecting = context.getReturnedType().isProjecting(); + Object actualReturnType = isProjecting ? context.getActualReturnType().getType() : context.getRepositoryInformation().getDomainType(); builder.add("\n"); @@ -150,24 +170,23 @@ public class MongoBlocks { context.getRepositoryInformation().getDomainType()); } - String terminatingMethod = "all()"; - if (context.returnsSingleValue()) { + String terminatingMethod; - if (context.returnsOptionalValue()) { - terminatingMethod = "one()"; - } else if (context.isCountMethod()) { - terminatingMethod = "count()"; - } else if (context.isExistsMethod()) { - terminatingMethod = "exists()"; - } else { - terminatingMethod = "oneValue()"; - } + if (queryMethod.isCollectionQuery() || queryMethod.isPageQuery() || queryMethod.isSliceQuery()) { + terminatingMethod = "all()"; + } else if (count) { + terminatingMethod = "count()"; + + } else if (exists) { + terminatingMethod = "exists()"; + } else { + terminatingMethod = Optional.class.isAssignableFrom(context.getReturnType().toClass()) ? "one()" : "oneValue()"; } - if (context.returnsPage()) { + if (queryMethod.isPageQuery()) { builder.addStatement("return new $T(finder, $L).execute($L)", PagedExecution.class, context.getPageableParameterName(), queryVariableName); - } else if (context.returnsSlice()) { + } else if (queryMethod.isSliceQuery()) { builder.addStatement("return new $T(finder, $L).execute($L)", SlicedExecution.class, context.getPageableParameterName(), queryVariableName); } else { @@ -181,12 +200,14 @@ public class MongoBlocks { static class QueryBlockBuilder { - AotRepositoryMethodGenerationContext context; + private final AotQueryMethodGenerationContext context; + private final MongoQueryMethod queryMethod; + StringQuery source; List arguments; private String queryVariableName; - public QueryBlockBuilder(AotRepositoryMethodGenerationContext context) { + public QueryBlockBuilder(AotQueryMethodGenerationContext context, MongoQueryMethod queryMethod) { this.context = context; this.arguments = Arrays.stream(context.getMethod().getParameters()).map(Parameter::getName) .collect(Collectors.toList()); @@ -194,6 +215,7 @@ public class MongoBlocks { // ParametersSource parametersSource = ParametersSource.of(repositoryInformation, metadata.getRepositoryMethod()); // this.argumentSource = new MongoParameters(parametersSource, false); + this.queryMethod = queryMethod; } public QueryBlockBuilder filter(StringQuery query) { @@ -239,17 +261,20 @@ public class MongoBlocks { } String pageableParameter = context.getPageableParameterName(); - if (StringUtils.hasText(pageableParameter) && !context.returnsPage() && !context.returnsSlice()) { + if (StringUtils.hasText(pageableParameter) && !queryMethod.isPageQuery() && !queryMethod.isSliceQuery()) { builder.addStatement("$L.with($L)", queryVariableName, pageableParameter); } - String hint = context.annotationValue(Hint.class, "value"); + MergedAnnotation hintAnnotation = context.getAnnotation(Hint.class); + String hint = hintAnnotation.isPresent() ? hintAnnotation.getString("value") : null; if (StringUtils.hasText(hint)) { builder.addStatement("$L.withHint($S)", queryVariableName, hint); } - String readPreference = context.annotationValue(ReadPreference.class, "value"); + MergedAnnotation readPreferenceAnnotation = context.getAnnotation(ReadPreference.class); + String readPreference = readPreferenceAnnotation.isPresent() ? readPreferenceAnnotation.getString("value") : null; + if (StringUtils.hasText(readPreference)) { builder.addStatement("$L.withReadPreference($T.valueOf($S))", queryVariableName, com.mongodb.ReadPreference.class, readPreference); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoRepositoryContributor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoRepositoryContributor.java index d42afd61b..2ad12cfba 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoRepositoryContributor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoRepositoryContributor.java @@ -15,20 +15,27 @@ */ package org.springframework.data.mongodb.aot.generated; +import java.lang.reflect.Method; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; + import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.data.mongodb.aot.generated.MongoBlocks.QueryBlockBuilder; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.repository.Aggregation; import org.springframework.data.mongodb.repository.Query; +import org.springframework.data.mongodb.repository.query.MongoQueryMethod; +import org.springframework.data.repository.aot.generate.AotQueryMethodGenerationContext; import org.springframework.data.repository.aot.generate.AotRepositoryConstructorBuilder; -import org.springframework.data.repository.aot.generate.AotRepositoryMethodBuilder; -import org.springframework.data.repository.aot.generate.AotRepositoryMethodGenerationContext; +import org.springframework.data.repository.aot.generate.MethodContributor; import org.springframework.data.repository.aot.generate.RepositoryContributor; import org.springframework.data.repository.config.AotRepositoryContext; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.parser.PartTree; -import org.springframework.javapoet.MethodSpec.Builder; +import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.TypeName; import org.springframework.util.StringUtils; @@ -38,11 +45,13 @@ import org.springframework.util.StringUtils; */ public class MongoRepositoryContributor extends RepositoryContributor { - private AotQueryCreator queryCreator; + private final AotQueryCreator queryCreator; + private final MongoMappingContext mappingContext; public MongoRepositoryContributor(AotRepositoryContext repositoryContext) { super(repositoryContext); this.queryCreator = new AotQueryCreator(); + this.mappingContext = new MongoMappingContext(); } @Override @@ -51,36 +60,43 @@ public class MongoRepositoryContributor extends RepositoryContributor { } @Override - protected AotRepositoryMethodBuilder contributeRepositoryMethod( - AotRepositoryMethodGenerationContext generationContext) { - - // TODO: do not generate stuff for spel expressions + protected @Nullable MethodContributor contributeQueryMethod(Method method, + RepositoryInformation repositoryInformation) { - if (AnnotatedElementUtils.hasAnnotation(generationContext.getMethod(), Aggregation.class)) { + if (AnnotatedElementUtils.hasAnnotation(method, Aggregation.class)) { return null; } - { - Query queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(generationContext.getMethod(), Query.class); - if (queryAnnotation != null) { - if (StringUtils.hasText(queryAnnotation.value()) - && Pattern.compile("[\\?:][#$]\\{.*\\}").matcher(queryAnnotation.value()).find()) { - return null; - } + + Query queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Query.class); + if (queryAnnotation != null) { + if (StringUtils.hasText(queryAnnotation.value()) + && Pattern.compile("[\\?:][#$]\\{.*\\}").matcher(queryAnnotation.value()).find()) { + return null; } } - // so the rest should work - return new AotRepositoryMethodBuilder(generationContext).customize((context, body) -> { + MongoQueryMethod queryMethod = new MongoQueryMethod(method, repositoryInformation, getProjectionFactory(), + mappingContext); + + return MethodContributor.forQueryMethod(queryMethod).contribute(context -> { + CodeBlock.Builder builder = CodeBlock.builder(); - Query queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(context.getMethod(), Query.class); + boolean count, delete, exists; StringQuery query; if (queryAnnotation != null && StringUtils.hasText(queryAnnotation.value())) { query = new StringQuery(queryAnnotation.value()); + count = queryAnnotation.count(); + delete = queryAnnotation.delete(); + exists = queryAnnotation.exists(); } else { PartTree partTree = new PartTree(context.getMethod().getName(), context.getRepositoryInformation().getDomainType()); query = queryCreator.createQuery(partTree, context.getMethod().getParameterCount()); + count = partTree.isCountProjection(); + delete = partTree.isDelete(); + exists = partTree.isExistsProjection(); + } if (queryAnnotation != null && StringUtils.hasText(queryAnnotation.sort())) { @@ -90,29 +106,31 @@ public class MongoRepositoryContributor extends RepositoryContributor { query.fields(queryAnnotation.fields()); } - writeStringQuery(context, body, query); + writeStringQuery(context, builder, count, delete, exists, query, queryMethod); + + return builder.build(); }); } - private static void writeStringQuery(AotRepositoryMethodGenerationContext context, Builder body, StringQuery query) { + private static void writeStringQuery(AotQueryMethodGenerationContext context, CodeBlock.Builder body, boolean count, + boolean delete, boolean exists, StringQuery query, MongoQueryMethod queryMethod) { - body.addCode(context.codeBlocks().logDebug("invoking [%s]".formatted(context.getMethod().getName()))); - QueryBlockBuilder queryBlockBuilder = MongoBlocks.queryBlockBuilder(context).filter(query); + body.add(context.codeBlocks().logDebug("invoking [%s]".formatted(context.getMethod().getName()))); + QueryBlockBuilder queryBlockBuilder = MongoBlocks.queryBlockBuilder(context, queryMethod).filter(query); - if (context.isDeleteMethod()) { + if (delete) { String deleteQueryVariableName = "deleteQuery"; - body.addCode(queryBlockBuilder.usingQueryVariableName(deleteQueryVariableName).build()); - body.addCode(MongoBlocks.deleteExecutionBlockBuilder(context).referencing(deleteQueryVariableName).build()); + body.add(queryBlockBuilder.usingQueryVariableName(deleteQueryVariableName).build()); + body.add( + MongoBlocks.deleteExecutionBlockBuilder(context, queryMethod).referencing(deleteQueryVariableName).build()); } else { String filterQueryVariableName = "filterQuery"; - body.addCode(queryBlockBuilder.usingQueryVariableName(filterQueryVariableName).build()); - body.addCode(MongoBlocks.queryExecutionBlockBuilder(context).referencing(filterQueryVariableName).build()); + body.add(queryBlockBuilder.usingQueryVariableName(filterQueryVariableName).build()); + body.add(MongoBlocks.queryExecutionBlockBuilder(context, queryMethod).exists(exists).count(count) + .referencing(filterQueryVariableName).build()); } } - private static void userAnnotatedQuery(AotRepositoryMethodGenerationContext context, Builder body, Query query) { - writeStringQuery(context, body, new StringQuery(query.value())); - } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 2d073869a..24c3c2f59 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -45,6 +45,7 @@ import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.CollectionFactory; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.DefaultConversionService; @@ -2223,6 +2224,11 @@ public class MappingMongoConverter extends AbstractMongoConverter public TypeDescriptor toTypeDescriptor() { return delegate.toTypeDescriptor(); } + + @Override + public ResolvableType toResolvableType() { + return delegate.toResolvableType(); + } } /**