From 7ab7d310da4a8ab2e6df67aa5444ed97093a1fa2 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 15 Jan 2026 09:31:46 +0100 Subject: [PATCH] Polishing. Refactor duplicates, simplify placeholder unwrapping. See #5123 Original pull request: #5124 --- .../repository/aot/AotPlaceholders.java | 33 ++++- .../repository/aot/AotQueryCreator.java | 120 ++++++++++-------- .../repository/aot/AotStringQuery.java | 30 ++--- .../aot/DocumentSerializerUnitTests.java | 3 +- 4 files changed, 105 insertions(+), 81 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java index 0e5c6501b..906816517 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java @@ -53,7 +53,7 @@ class AotPlaceholders { * @param type * @return */ - static Shape geoJson(int index, String type) { + static Placeholder geoJson(int index, String type) { return new GeoJsonPlaceholder(index, type); } @@ -63,7 +63,7 @@ class AotPlaceholders { * @param index zero-based index referring to the bindable method parameter. * @return */ - static Point point(int index) { + static Placeholder point(int index) { return new PointPlaceholder(index); } @@ -73,7 +73,7 @@ class AotPlaceholders { * @param index zero-based index referring to the bindable method parameter. * @return */ - static Shape circle(int index) { + static Placeholder circle(int index) { return new CirclePlaceholder(index); } @@ -83,7 +83,7 @@ class AotPlaceholders { * @param index zero-based index referring to the bindable method parameter. * @return */ - static Shape box(int index) { + static Placeholder box(int index) { return new BoxPlaceholder(index); } @@ -93,7 +93,7 @@ class AotPlaceholders { * @param index zero-based index referring to the bindable method parameter. * @return */ - static Shape sphere(int index) { + static Placeholder sphere(int index) { return new SpherePlaceholder(index); } @@ -103,7 +103,7 @@ class AotPlaceholders { * @param index zero-based index referring to the bindable method parameter. * @return */ - static Shape polygon(int index) { + static Placeholder polygon(int index) { return new PolygonPlaceholder(index); } @@ -140,6 +140,17 @@ class AotPlaceholders { interface Placeholder { String getValue(); + + /** + * Unwrap the current {@link Placeholder} to the given target type if possible. + * + * @param targetType + * @return + * @param + */ + default @Nullable T unwrap(Class targetType) { + return targetType.isInstance(this) ? targetType.cast(this) : null; + } } /** @@ -317,6 +328,16 @@ class AotPlaceholders { record AsListPlaceholder(Placeholder placeholder) implements Placeholder { + @Override + public @Nullable T unwrap(Class targetType) { + + if (targetType.isInstance(placeholder)) { + return targetType.cast(placeholder); + } + + return Placeholder.super.unwrap(targetType); + } + @Override public String toString() { return getValue(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java index d37f78c60..d7b14a4ae 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java @@ -24,6 +24,7 @@ import java.util.regex.Pattern; import org.bson.conversions.Bson; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; + import org.springframework.data.core.TypeInformation; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Range; @@ -61,7 +62,6 @@ import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.Part.IgnoreCaseType; import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.data.repository.query.parser.PartTree; -import org.springframework.util.ClassUtils; import com.mongodb.DBRef; @@ -132,8 +132,8 @@ record AotQueryCreator(MappingContext mappingContext return criteria.raw("$regex", param); } - if (param instanceof AsListPlaceholder asListPlaceholder && !property.isCollectionLike()) { - return super.createContainingCriteria(part, property, criteria, asListPlaceholder.placeholder()); + if (param instanceof AsListPlaceholder asList && !property.isCollectionLike()) { + return super.createContainingCriteria(part, property, criteria, asList.placeholder()); } return super.createContainingCriteria(part, property, criteria, param); @@ -176,10 +176,11 @@ record AotQueryCreator(MappingContext mappingContext @NullUnmarked static class PlaceholderParameterAccessor implements MongoParameterAccessor { - private final List placeholders; + private final List placeholders; @Nullable Part getPartForIndex(PartTree partTree, Parameter parameter) { + if (!parameter.isBindable()) { return null; } @@ -197,62 +198,74 @@ record AotQueryCreator(MappingContext mappingContext public PlaceholderParameterAccessor(PartTree partTree, QueryMethod queryMethod) { - if (queryMethod.getParameters().getNumberOfParameters() == 0) { + Parameters parameters = queryMethod.getParameters(); + if (parameters.getNumberOfParameters() == 0) { placeholders = List.of(); } else { - placeholders = new ArrayList<>(); - Parameters parameters = queryMethod.getParameters(); - + placeholders = new ArrayList<>(parameters.getNumberOfParameters()); for (Parameter parameter : parameters.toList()) { - if (ClassUtils.isAssignable(GeoJson.class, parameter.getType())) { - placeholders.add(parameter.getIndex(), AotPlaceholders.geoJson(parameter.getIndex(), "")); - } else if (ClassUtils.isAssignable(Point.class, parameter.getType())) { - placeholders.add(parameter.getIndex(), AotPlaceholders.point(parameter.getIndex())); - } else if (ClassUtils.isAssignable(Circle.class, parameter.getType())) { - placeholders.add(parameter.getIndex(), AotPlaceholders.circle(parameter.getIndex())); - } else if (ClassUtils.isAssignable(Box.class, parameter.getType())) { - placeholders.add(parameter.getIndex(), AotPlaceholders.box(parameter.getIndex())); - } else if (ClassUtils.isAssignable(Sphere.class, parameter.getType())) { - placeholders.add(parameter.getIndex(), AotPlaceholders.sphere(parameter.getIndex())); - } else if (ClassUtils.isAssignable(Polygon.class, parameter.getType())) { - placeholders.add(parameter.getIndex(), AotPlaceholders.polygon(parameter.getIndex())); - } else if (ClassUtils.isAssignable(Pattern.class, parameter.getType())) { - placeholders.add(parameter.getIndex(), AotPlaceholders.regex(parameter.getIndex(), null)); - } else { - Part partForIndex = getPartForIndex(partTree, parameter); - if (partForIndex != null - && (partForIndex.getType().equals(Type.LIKE) || partForIndex.getType().equals(Type.NOT_LIKE))) { - placeholders - .add(parameter.getIndex(), - AotPlaceholders - .regex(parameter.getIndex(), - partForIndex.shouldIgnoreCase().equals(IgnoreCaseType.ALWAYS) - || partForIndex.shouldIgnoreCase().equals(IgnoreCaseType.WHEN_POSSIBLE) ? "i" - : null)); - } else if (partForIndex != null && (partForIndex.getType().equals(Type.IN) - || partForIndex.getType().equals(Type.NOT_IN) || partForIndex.getType().equals(Type.CONTAINING) - || partForIndex.getType().equals(Type.NOT_CONTAINING))) { - - if (partForIndex.getProperty().isCollection() - && !TypeInformation.of(parameter.getType()).isCollectionLike()) { - if (partForIndex.shouldIgnoreCase().equals(IgnoreCaseType.ALWAYS)) { - placeholders.add(parameter.getIndex(), - AotPlaceholders.asList(AotPlaceholders.regex(parameter.getIndex(), "i"))); - } else { - placeholders.add(parameter.getIndex(), AotPlaceholders.asList(parameter.getIndex())); - } - } else { - placeholders.add(parameter.getIndex(), AotPlaceholders.indexed(parameter.getIndex())); - } - } - else { - placeholders.add(parameter.getIndex(), AotPlaceholders.indexed(parameter.getIndex())); + int index = parameter.getIndex(); + placeholders.add(index, getPlaceholder(index, parameter, partTree)); + } + } + } + + private Placeholder getPlaceholder(int index, Parameter parameter, PartTree partTree) { + + Class type = parameter.getType(); + + if (GeoJson.class.isAssignableFrom(type)) { + return AotPlaceholders.geoJson(index, ""); + } else if (Point.class.isAssignableFrom(type)) { + return AotPlaceholders.point(index); + } else if (Circle.class.isAssignableFrom(type)) { + return AotPlaceholders.circle(index); + } else if (Box.class.isAssignableFrom(type)) { + return AotPlaceholders.box(index); + } else if (Sphere.class.isAssignableFrom(type)) { + return AotPlaceholders.sphere(index); + } else if (Polygon.class.isAssignableFrom(type)) { + return AotPlaceholders.polygon(index); + } else if (Pattern.class.isAssignableFrom(type)) { + return AotPlaceholders.regex(index, null); + } + + Part partForIndex = getPartForIndex(partTree, parameter); + if (partForIndex != null) { + + IgnoreCaseType ignoreCaseType = partForIndex.shouldIgnoreCase(); + if (isLike(partForIndex.getType())) { + + boolean ignoreCase = !ignoreCaseType.equals(IgnoreCaseType.NEVER); + return AotPlaceholders.regex(index, ignoreCase ? "i" : null); + } + + if (isContaining(partForIndex.getType())) { + + if (partForIndex.getProperty().isCollection() && !TypeInformation.of(type).isCollectionLike()) { + if (ignoreCaseType.equals(IgnoreCaseType.ALWAYS)) { + return AotPlaceholders.asList(AotPlaceholders.regex(index, "i")); + } else { + return AotPlaceholders.asList(index); } } + + return AotPlaceholders.indexed(index); } } + + return AotPlaceholders.indexed(index); + } + + private static boolean isContaining(Part.Type type) { + return type.equals(Type.IN) || type.equals(Type.NOT_IN) // + || type.equals(Type.CONTAINING) || type.equals(Type.NOT_CONTAINING); + } + + private static boolean isLike(Part.Type type) { + return type.equals(Type.LIKE) || type.equals(Type.NOT_LIKE); } @Override @@ -322,8 +335,7 @@ record AotQueryCreator(MappingContext mappingContext @Override public @Nullable Object getBindableValue(int index) { - return placeholders.get(index) instanceof Placeholder placeholder ? placeholder.getValue() - : placeholders.get(index); + return placeholders.get(index).getValue(); } @Override @@ -337,7 +349,7 @@ record AotQueryCreator(MappingContext mappingContext return ((List) placeholders).iterator(); } - public List getPlaceholders() { + public List getPlaceholders() { return placeholders; } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotStringQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotStringQuery.java index b5c87011e..81b2bb35d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotStringQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotStringQuery.java @@ -22,12 +22,12 @@ import java.util.Set; import org.bson.Document; import org.jspecify.annotations.Nullable; + import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.Field; import org.springframework.data.mongodb.core.query.Meta; import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.repository.aot.AotPlaceholders.AsListPlaceholder; import org.springframework.data.mongodb.repository.aot.AotPlaceholders.RegexPlaceholder; import org.springframework.util.StringUtils; @@ -47,9 +47,9 @@ class AotStringQuery extends Query { private @Nullable String sort; private @Nullable String fields; - private List placeholders = new ArrayList<>(); + private List placeholders = new ArrayList<>(); - public AotStringQuery(Query query, List placeholders) { + public AotStringQuery(Query query, List placeholders) { this.delegate = query; this.placeholders = placeholders; } @@ -79,34 +79,24 @@ class AotStringQuery extends Query { } boolean isRegexPlaceholderAt(int index) { - if (this.placeholders.isEmpty()) { - return false; - } - - return obtainAndPotentiallyUnwrapPlaceholder(index) instanceof RegexPlaceholder; + return getRegexPlaceholder(index) != null; } @Nullable String getRegexOptions(int index) { - if (this.placeholders.isEmpty()) { - return null; - } - Object placeholderValue = obtainAndPotentiallyUnwrapPlaceholder(index); - return placeholderValue instanceof RegexPlaceholder rgp ? rgp.regexOptions() : null; + RegexPlaceholder placeholder = getRegexPlaceholder(index); + return placeholder != null ? placeholder.regexOptions() : null; } - @Nullable Object obtainAndPotentiallyUnwrapPlaceholder(int index) { + @Nullable + RegexPlaceholder getRegexPlaceholder(int index) { - if (this.placeholders.isEmpty()) { + if (index >= this.placeholders.size()) { return null; } - Object placeholerValue = this.placeholders.get(index); - if (placeholerValue instanceof AsListPlaceholder asListPlaceholder) { - placeholerValue = asListPlaceholder.placeholder(); - } - return placeholerValue; + return this.placeholders.get(index).unwrap(RegexPlaceholder.class); } @Override diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/aot/DocumentSerializerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/aot/DocumentSerializerUnitTests.java index 449f1d671..7f6f17d60 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/aot/DocumentSerializerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/aot/DocumentSerializerUnitTests.java @@ -22,6 +22,7 @@ import java.util.List; import org.bson.Document; import org.junit.jupiter.api.Test; +import org.springframework.data.geo.Shape; import org.springframework.data.mongodb.core.query.GeoCommand; /** @@ -35,7 +36,7 @@ class DocumentSerializerUnitTests { void writesGeoCommandToJson() { Document source = new Document(); - source.append("foo", new GeoCommand(AotPlaceholders.box(1))); + source.append("foo", new GeoCommand((Shape) AotPlaceholders.box(1))); String jsonString = DocumentSerializer.toJson(source);