From 40f21833e368b056f9e0f3e17d92b66aef39357d Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 13 Jan 2025 15:36:58 +0100 Subject: [PATCH] just some stuff to see if it works the way we expect it to do --- .../mongodb/aot/generated/MongoBlocks.java | 169 ++++++++++++++++++ .../generated/MongoRepositoryContributor.java | 63 ++----- .../test/java/example/aot/UserRepository.java | 13 ++ 3 files changed, 195 insertions(+), 50 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoBlocks.java 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 new file mode 100644 index 000000000..c34ba4c04 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoBlocks.java @@ -0,0 +1,169 @@ +/* + * 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.mongodb.aot.generated; + +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.data.mongodb.BindableMongoExpression; +import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.BasicQuery; +import org.springframework.data.mongodb.repository.Hint; +import org.springframework.data.mongodb.repository.ReadPreference; +import org.springframework.data.repository.aot.generate.AotRepositoryMethodBuilder.MethodGenerationMetadata; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.javapoet.CodeBlock; +import org.springframework.javapoet.CodeBlock.Builder; +import org.springframework.javapoet.TypeName; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * @author Christoph Strobl + */ +public class MongoBlocks { + + public static QueryBlockBuilder queryBlockBuilder(RepositoryInformation repositoryInformation, + MethodGenerationMetadata metadata) { + return new QueryBlockBuilder(repositoryInformation, metadata); + } + + public static QueryExecutionBlockBuilder queryExecutionBlockBuilder(RepositoryInformation repositoryInformation, + MethodGenerationMetadata metadata) { + return new QueryExecutionBlockBuilder(repositoryInformation, metadata); + } + + static class QueryExecutionBlockBuilder { + + RepositoryInformation repositoryInformation; + MethodGenerationMetadata metadata; + + public QueryExecutionBlockBuilder(RepositoryInformation repositoryInformation, MethodGenerationMetadata metadata) { + this.repositoryInformation = repositoryInformation; + this.metadata = metadata; + } + + CodeBlock build(String queryVariableName) { + + String mongoOpsRef = metadata.fieldNameOf(MongoOperations.class); + + Builder builder = CodeBlock.builder(); + + boolean isProjecting = metadata.getActualReturnType() != null && !ObjectUtils + .nullSafeEquals(TypeName.get(repositoryInformation.getDomainType()), metadata.getActualReturnType()); + Object actualReturnType = isProjecting ? metadata.getActualReturnType() : repositoryInformation.getDomainType(); + + if (isProjecting) { + builder.addStatement("$T<$T> finder = $L.query($T.class).as($T.class).matching($L)", TerminatingFind.class, + actualReturnType, mongoOpsRef, repositoryInformation.getDomainType(), actualReturnType, queryVariableName); + } else { + builder.addStatement("$T<$T> finder = $L.query($T.class).matching($L)", TerminatingFind.class, actualReturnType, + mongoOpsRef, repositoryInformation.getDomainType(), queryVariableName); + } + + String terminatingMethod = "all()"; + if (metadata.returnsSingleValue()) { + if (metadata.returnsOptionalValue()) { + terminatingMethod = "one()"; + } else { + terminatingMethod = "oneValue()"; + } + } + + if (!metadata.returnsPage()) { + builder.addStatement("return finder.$L", terminatingMethod); + } else { + builder.addStatement("return $T.getPage(finder.$L, $L, () -> finder.count())", PageableExecutionUtils.class, terminatingMethod, + metadata.getPageableParameterName()); + } + + return builder.build(); + } + + } + + static class QueryBlockBuilder { + + MethodGenerationMetadata metadata; + String queryString; + List arguments; + // MongoParameters argumentSource; + + public QueryBlockBuilder(RepositoryInformation repositoryInformation, MethodGenerationMetadata metadata) { + this.metadata = metadata; + this.arguments = Arrays.stream(metadata.getRepositoryMethod().getParameters()).map(Parameter::getName) + .collect(Collectors.toList()); + + // ParametersSource parametersSource = ParametersSource.of(repositoryInformation, metadata.getRepositoryMethod()); + // this.argumentSource = new MongoParameters(parametersSource, false); + + } + + public QueryBlockBuilder filter(String filter) { + this.queryString = filter; + return this; + } + + CodeBlock build(String queryVariableName) { + + String mongoOpsRef = metadata.fieldNameOf(MongoOperations.class); + + CodeBlock.Builder builder = CodeBlock.builder(); + + builder.addStatement("$T filter = new $T($S, $L.getConverter(), new $T[]{ $L })", BindableMongoExpression.class, + BindableMongoExpression.class, queryString, mongoOpsRef, Object.class, + StringUtils.collectionToCommaDelimitedString(arguments)); + builder.addStatement("$T $L = new $T(filter.toDocument())", + org.springframework.data.mongodb.core.query.Query.class, queryVariableName, BasicQuery.class); + + String sortParameter = metadata.getSortParameterName(); + if (StringUtils.hasText(sortParameter)) { + builder.addStatement("$L.with($L)", queryVariableName, sortParameter); + } + + String limitParameter = metadata.getLimitParameterName(); + if (StringUtils.hasText(limitParameter)) { + builder.addStatement("$L.limit($L)", queryVariableName, limitParameter); + } + + String pageableParameter = metadata.getPageableParameterName(); + if (StringUtils.hasText(pageableParameter)) { + builder.addStatement("$L.with($L)", queryVariableName, pageableParameter); + } + + String hint = metadata.annotationValue(Hint.class, "value"); + + if (StringUtils.hasText(hint)) { + builder.addStatement("$L.withHint($S)", queryVariableName, hint); + } + + String readPreference = metadata.annotationValue(ReadPreference.class, "value"); + if (StringUtils.hasText(readPreference)) { + builder.addStatement("$L.withReadPreference($T.valueOf($S))", queryVariableName, + com.mongodb.ReadPreference.class, readPreference); + } + + // TODO: all the meta stuff + + return builder.build(); + } + + } +} 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 ea17ffb77..2d209b73d 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,13 +15,10 @@ */ package org.springframework.data.mongodb.aot.generated; -import java.lang.reflect.Parameter; import java.util.Arrays; import java.util.Iterator; -import java.util.stream.Collectors; import java.util.stream.IntStream; -import org.apache.commons.logging.Log; import org.bson.conversions.Bson; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.data.domain.Pageable; @@ -30,13 +27,11 @@ import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.Sort; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; -import org.springframework.data.mongodb.BindableMongoExpression; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.convert.MongoCustomConversions; import org.springframework.data.mongodb.core.convert.MongoWriter; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; -import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.CriteriaDefinition.Placeholder; import org.springframework.data.mongodb.core.query.TextCriteria; @@ -49,6 +44,8 @@ import org.springframework.data.mongodb.util.BsonUtils; import org.springframework.data.repository.aot.generate.AotRepositoryConstructorBuilder; import org.springframework.data.repository.aot.generate.AotRepositoryMethodBuilder; import org.springframework.data.repository.aot.generate.AotRepositoryMethodBuilder.MethodGenerationMetadata; +import org.springframework.data.repository.aot.generate.CodeBlocks; +import org.springframework.data.repository.aot.generate.Contribution; import org.springframework.data.repository.aot.generate.RepositoryContributor; import org.springframework.data.repository.config.AotRepositoryContext; import org.springframework.data.repository.core.RepositoryInformation; @@ -57,8 +54,6 @@ import org.springframework.data.util.TypeInformation; import org.springframework.javapoet.MethodSpec.Builder; import org.springframework.javapoet.TypeName; import org.springframework.lang.Nullable; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; import com.mongodb.DBRef; @@ -78,16 +73,14 @@ public class MongoRepositoryContributor extends RepositoryContributor { } @Override - protected void customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) { + protected Contribution customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) { methodBuilder.customize((repositoryInformation, metadata, body) -> { Query query = AnnotatedElementUtils.findMergedAnnotation(metadata.getRepositoryMethod(), Query.class); if (query != null) { - userAnnotatedQuery(repositoryInformation, metadata, body, query); + userAnnotatedQuery(repositoryInformation, metadata, methodBuilder.codeBlocks(), body, query); } else { - ; - MongoMappingContext mongoMappingContext = new MongoMappingContext(); mongoMappingContext.setSimpleTypeHolder( MongoCustomConversions.create((cfg) -> cfg.useNativeDriverJavaTimeCodecs()).getSimpleTypeHolder()); @@ -195,52 +188,22 @@ public class MongoRepositoryContributor extends RepositoryContributor { org.springframework.data.mongodb.core.query.Query partTreeQuery = queryCreator.createQuery(); StringBuffer buffer = new StringBuffer(); BsonUtils.writeJson(partTreeQuery.getQueryObject()).to(buffer); - writeStringQuery(repositoryInformation, metadata, body, buffer.toString()); + writeStringQuery(repositoryInformation, metadata, methodBuilder.codeBlocks(), body, buffer.toString()); } }); + return Contribution.CODE; } private static void writeStringQuery(RepositoryInformation repositoryInformation, MethodGenerationMetadata metadata, - Builder body, String query) { - - String mongoOpsRef = metadata.fieldNameOf(MongoOperations.class); - String arguments = StringUtils.collectionToCommaDelimitedString(Arrays - .stream(metadata.getRepositoryMethod().getParameters()).map(Parameter::getName).collect(Collectors.toList())); - - body.beginControlFlow("if($L.isDebugEnabled())", metadata.fieldNameOf(Log.class)); - body.addStatement("$L.debug(\"invoking generated [$L] method\")", metadata.fieldNameOf(Log.class), - metadata.getRepositoryMethod().getName()); - body.endControlFlow(); - - body.addStatement("$T filter = new $T($S, $L.getConverter(), new $T[]{ $L })", BindableMongoExpression.class, - BindableMongoExpression.class, query, mongoOpsRef, Object.class, arguments); - body.addStatement("$T query = new $T(filter.toDocument())", org.springframework.data.mongodb.core.query.Query.class, - BasicQuery.class); - - boolean isCollectionType = TypeInformation.fromReturnTypeOf(metadata.getRepositoryMethod()).isCollectionLike(); - String terminatingMethod = isCollectionType ? "all()" : "oneValue()"; - - if (metadata.getActualReturnType() != null && ObjectUtils - .nullSafeEquals(TypeName.get(repositoryInformation.getDomainType()), metadata.getActualReturnType())) { - body.addStatement(""" - return $L.query($T.class) - .matching(query) - .$L""", mongoOpsRef, repositoryInformation.getDomainType(), terminatingMethod); - } else { - - body.addStatement(""" - return $L.query($T.class) - .as($T.class) - .matching(query) - .$L""", mongoOpsRef, repositoryInformation.getDomainType(), - metadata.getActualReturnType() != null ? metadata.getActualReturnType() - : repositoryInformation.getDomainType(), - terminatingMethod); - } + CodeBlocks codeBlocks, Builder body, String query) { + + body.addCode(codeBlocks.logDebug("invoking [%s]".formatted(metadata.getRepositoryMethod().getName()))); + body.addCode(MongoBlocks.queryBlockBuilder(repositoryInformation, metadata).filter(query).build("query")); + body.addCode(MongoBlocks.queryExecutionBlockBuilder(repositoryInformation, metadata).build("query")); } private static void userAnnotatedQuery(RepositoryInformation repositoryInformation, MethodGenerationMetadata metadata, - Builder body, Query query) { - writeStringQuery(repositoryInformation, metadata, body, query.value()); + CodeBlocks codeBlocks, Builder body, Query query) { + writeStringQuery(repositoryInformation, metadata, codeBlocks, body, query.value()); } } diff --git a/spring-data-mongodb/src/test/java/example/aot/UserRepository.java b/spring-data-mongodb/src/test/java/example/aot/UserRepository.java index 441cd3da8..7d13e6637 100644 --- a/spring-data-mongodb/src/test/java/example/aot/UserRepository.java +++ b/spring-data-mongodb/src/test/java/example/aot/UserRepository.java @@ -17,7 +17,12 @@ package example.aot; import java.util.List; +import org.springframework.data.domain.Limit; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.repository.Query; +import org.springframework.data.mongodb.repository.ReadPreference; import org.springframework.data.repository.CrudRepository; /** @@ -30,7 +35,15 @@ public interface UserRepository extends CrudRepository { @Query("{ 'username' : '?0' }") List findAllByAnnotatedQueryWithParameter(String username); + @ReadPreference("secondary") User findByUsername(String username); List findUserByLastnameLike(String lastname); + + List findUserByLastnameStartingWith(String lastname, Pageable page); + List findUserByLastnameStartingWith(String lastname, Sort sort); + List findUserByLastnameStartingWith(String lastname, Limit limit); + List findUserByLastnameStartingWith(String lastname, Sort sort, Limit limit); + + Page findUserByFirstnameStartingWith(String lastname, Pageable page); }