From a4fcbb15bf8473fcb9af632c6342db2ae5ab1ac9 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 8 Dec 2023 12:43:02 +0100 Subject: [PATCH] Adopt `MongoParameters` and `MongoParameter` to reflect the actual parameter type when using generics. Closes #4579 --- .../repository/query/MongoParameters.java | 82 +++++++++++++------ .../repository/query/MongoQueryMethod.java | 5 +- .../query/ReactiveMongoQueryMethod.java | 5 +- .../query/MongoParametersUnitTests.java | 31 +++---- 4 files changed, 77 insertions(+), 46 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java index 54807a54d..52bb0a45c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java @@ -30,6 +30,7 @@ import org.springframework.data.mongodb.repository.Near; import org.springframework.data.mongodb.repository.query.MongoParameters.MongoParameter; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.ParametersSource; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; @@ -49,38 +50,48 @@ public class MongoParameters extends Parameters private final @Nullable Integer nearIndex; private final @Nullable Integer collationIndex; private final int updateIndex; + private final TypeInformation domainType; /** * Creates a new {@link MongoParameters} instance from the given {@link Method} and {@link MongoQueryMethod}. * - * @param method must not be {@literal null}. + * @param parametersSource must not be {@literal null}. * @param isGeoNearMethod indicate if this is a geo spatial query method */ - public MongoParameters(Method method, boolean isGeoNearMethod) { + public MongoParameters(ParametersSource parametersSource, boolean isGeoNearMethod) { + this(parametersSource, new NearIndex(parametersSource, isGeoNearMethod)); + } + + /** + * Creates a new {@link MongoParameters} instance from the given {@link Method} and {@link MongoQueryMethod}. + * + * @param parametersSource must not be {@literal null}. + * @param nearIndex the near parameter index. + */ + private MongoParameters(ParametersSource parametersSource, NearIndex nearIndex) { - super(method); + super(parametersSource, methodParameter -> new MongoParameter(methodParameter, + parametersSource.getDomainTypeInformation(), nearIndex.nearIndex)); + + Method method = parametersSource.getMethod(); List> parameterTypes = Arrays.asList(method.getParameterTypes()); + this.domainType = parametersSource.getDomainTypeInformation(); this.fullTextIndex = parameterTypes.indexOf(TextCriteria.class); - TypeInformation declaringClassInfo = TypeInformation.of(method.getDeclaringClass()); + TypeInformation declaringClassInfo = TypeInformation.of(parametersSource.getContainingClass()); List> parameterTypeInfo = declaringClassInfo.getParameterTypes(method); this.rangeIndex = getTypeIndex(parameterTypeInfo, Range.class, Distance.class); this.maxDistanceIndex = this.rangeIndex == -1 ? getTypeIndex(parameterTypeInfo, Distance.class, null) : -1; this.collationIndex = getTypeIndex(parameterTypeInfo, Collation.class, null); this.updateIndex = QueryUtils.indexOfAssignableParameter(UpdateDefinition.class, parameterTypes); - - int index = findNearIndexInParameters(method); - if (index == -1 && isGeoNearMethod) { - index = getNearIndex(parameterTypes); - } - - this.nearIndex = index; + this.nearIndex = nearIndex.nearIndex; } private MongoParameters(List parameters, int maxDistanceIndex, @Nullable Integer nearIndex, - @Nullable Integer fullTextIndex, int rangeIndex, @Nullable Integer collationIndex, int updateIndex) { + @Nullable Integer fullTextIndex, int rangeIndex, @Nullable Integer collationIndex, int updateIndex, + TypeInformation domainType) { super(parameters); @@ -90,9 +101,25 @@ public class MongoParameters extends Parameters this.rangeIndex = rangeIndex; this.collationIndex = collationIndex; this.updateIndex = updateIndex; + this.domainType = domainType; } - private int getNearIndex(List> parameterTypes) { + static class NearIndex { + + private final @Nullable Integer nearIndex; + + public NearIndex(ParametersSource parametersSource, boolean isGeoNearMethod) { + + int index = findNearIndexInParameters(parametersSource.getMethod()); + if (index == -1 && isGeoNearMethod) { + index = getNearIndex(Arrays.asList(parametersSource.getMethod().getParameterTypes())); + } + + this.nearIndex = index; + } + } + + private static int getNearIndex(List> parameterTypes) { for (Class reference : Arrays.asList(Point.class, double[].class)) { @@ -112,15 +139,18 @@ public class MongoParameters extends Parameters return -1; } - private int findNearIndexInParameters(Method method) { + static int findNearIndexInParameters(Method method) { int index = -1; for (java.lang.reflect.Parameter p : method.getParameters()) { - MongoParameter param = createParameter(MethodParameter.forParameter(p)); - if (param.isManuallyAnnotatedNearParameter()) { + MethodParameter methodParameter = MethodParameter.forParameter(p); + + if ((Point.class.isAssignableFrom(methodParameter.getParameterType()) + || methodParameter.getParameterType().equals(double[].class)) + && methodParameter.hasParameterAnnotation(Near.class)) { if (index == -1) { - index = param.getIndex(); + index = methodParameter.getParameterIndex(); } else { throw new IllegalStateException( String.format("Found multiple @Near annotations ond method %s; Only one allowed", method)); @@ -131,11 +161,6 @@ public class MongoParameters extends Parameters return index; } - @Override - protected MongoParameter createParameter(MethodParameter parameter) { - return new MongoParameter(parameter); - } - public int getDistanceRangeIndex() { return -1; } @@ -197,6 +222,7 @@ public class MongoParameters extends Parameters /** * Returns the index of the {@link UpdateDefinition} parameter or -1 if not present. + * * @return -1 if not present. * @since 3.4 */ @@ -207,7 +233,7 @@ public class MongoParameters extends Parameters @Override protected MongoParameters createFrom(List parameters) { return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex, this.rangeIndex, - this.collationIndex, this.updateIndex); + this.collationIndex, this.updateIndex, this.domainType); } private int getTypeIndex(List> parameterTypes, Class type, @Nullable Class componentType) { @@ -234,18 +260,21 @@ public class MongoParameters extends Parameters * * @author Oliver Gierke */ - class MongoParameter extends Parameter { + static class MongoParameter extends Parameter { private final MethodParameter parameter; + private final @Nullable Integer nearIndex; /** * Creates a new {@link MongoParameter}. * * @param parameter must not be {@literal null}. + * @param domainType must not be {@literal null}. */ - MongoParameter(MethodParameter parameter) { - super(parameter); + MongoParameter(MethodParameter parameter, TypeInformation domainType, @Nullable Integer nearIndex) { + super(parameter, domainType); this.parameter = parameter; + this.nearIndex = nearIndex; if (!isPoint() && hasNearAnnotation()) { throw new IllegalArgumentException("Near annotation is only allowed at Point parameter"); @@ -259,7 +288,6 @@ public class MongoParameters extends Parameters } private boolean isNearParameter() { - Integer nearIndex = MongoParameters.this.nearIndex; return nearIndex != null && nearIndex.equals(getIndex()); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java index d9b89b78a..020badd19 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java @@ -42,6 +42,7 @@ import org.springframework.data.mongodb.repository.Update; import org.springframework.data.mongodb.util.BsonUtils; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.query.ParametersSource; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.util.Lazy; import org.springframework.data.util.ReactiveWrappers; @@ -94,8 +95,8 @@ public class MongoQueryMethod extends QueryMethod { } @Override - protected MongoParameters createParameters(Method method) { - return new MongoParameters(method, isGeoNearQuery(method)); + protected MongoParameters createParameters(ParametersSource parametersSource) { + return new MongoParameters(parametersSource, isGeoNearQuery(parametersSource.getMethod())); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryMethod.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryMethod.java index d727d27d5..e56034050 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryMethod.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryMethod.java @@ -31,6 +31,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.repository.query.MongoParameters.MongoParameter; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.query.ParametersSource; import org.springframework.data.repository.util.ReactiveWrapperConverters; import org.springframework.data.util.Lazy; import org.springframework.data.util.ReactiveWrappers; @@ -71,8 +72,8 @@ public class ReactiveMongoQueryMethod extends MongoQueryMethod { } @Override - protected MongoParameters createParameters(Method method) { - return new MongoParameters(method, isGeoNearQuery(method)); + protected MongoParameters createParameters(ParametersSource parametersSource) { + return new MongoParameters(parametersSource, isGeoNearQuery(parametersSource.getMethod())); } @Override diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java index af31872d0..3afdbd47b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java @@ -36,6 +36,7 @@ import org.springframework.data.mongodb.core.query.UpdateDefinition; import org.springframework.data.mongodb.repository.Near; import org.springframework.data.mongodb.repository.Person; import org.springframework.data.repository.query.Parameter; +import org.springframework.data.repository.query.ParametersSource; /** * Unit tests for {@link MongoParameters}. @@ -52,7 +53,7 @@ class MongoParametersUnitTests { void discoversDistanceParameter() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); - MongoParameters parameters = new MongoParameters(method, false); + MongoParameters parameters = new MongoParameters(ParametersSource.of(method), false); assertThat(parameters.getNumberOfParameters()).isEqualTo(2); assertThat(parameters.getMaxDistanceIndex()).isEqualTo(1); @@ -67,7 +68,7 @@ class MongoParametersUnitTests { @Test void doesNotConsiderPointAsNearForSimpleQuery() throws Exception { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); - MongoParameters parameters = new MongoParameters(method, false); + MongoParameters parameters = new MongoParameters(ParametersSource.of(method), false); assertThat(parameters.getNearIndex()).isEqualTo(-1); } @@ -77,7 +78,7 @@ class MongoParametersUnitTests { Method method = PersonRepository.class.getMethod("findByLocationNearAndOtherLocation", Point.class, Point.class); - assertThatIllegalStateException().isThrownBy(() -> new MongoParameters(method, true)); + assertThatIllegalStateException().isThrownBy(() -> new MongoParameters(ParametersSource.of(method), true)); } @Test @@ -85,21 +86,21 @@ class MongoParametersUnitTests { Method method = PersonRepository.class.getMethod("invalidDoubleArrays", double[].class, double[].class); - assertThatIllegalStateException().isThrownBy(() -> new MongoParameters(method, true)); + assertThatIllegalStateException().isThrownBy(() -> new MongoParameters(ParametersSource.of(method), true)); } @Test void doesNotRejectMultiplePointsForSimpleQueryMethod() throws Exception { Method method = PersonRepository.class.getMethod("someOtherMethod", Point.class, Point.class); - new MongoParameters(method, false); + new MongoParameters(ParametersSource.of(method), false); } @Test void findsAnnotatedPointForGeoNearQuery() throws Exception { Method method = PersonRepository.class.getMethod("findByOtherLocationAndLocationNear", Point.class, Point.class); - MongoParameters parameters = new MongoParameters(method, true); + MongoParameters parameters = new MongoParameters(ParametersSource.of(method), true); assertThat(parameters.getNearIndex()).isOne(); } @@ -107,7 +108,7 @@ class MongoParametersUnitTests { void findsAnnotatedDoubleArrayForGeoNearQuery() throws Exception { Method method = PersonRepository.class.getMethod("validDoubleArrays", double[].class, double[].class); - MongoParameters parameters = new MongoParameters(method, true); + MongoParameters parameters = new MongoParameters(ParametersSource.of(method), true); assertThat(parameters.getNearIndex()).isOne(); } @@ -115,7 +116,7 @@ class MongoParametersUnitTests { void shouldFindTextCriteriaAtItsIndex() throws SecurityException, NoSuchMethodException { Method method = PersonRepository.class.getMethod("findByNameAndText", String.class, TextCriteria.class); - MongoParameters parameters = new MongoParameters(method, false); + MongoParameters parameters = new MongoParameters(ParametersSource.of(method), false); assertThat(parameters.getFullTextParameterIndex()).isOne(); } @@ -123,7 +124,7 @@ class MongoParametersUnitTests { void shouldTreatTextCriteriaParameterAsSpecialParameter() throws SecurityException, NoSuchMethodException { Method method = PersonRepository.class.getMethod("findByNameAndText", String.class, TextCriteria.class); - MongoParameters parameters = new MongoParameters(method, false); + MongoParameters parameters = new MongoParameters(ParametersSource.of(method), false); assertThat(parameters.getParameter(parameters.getFullTextParameterIndex()).isSpecialParameter()).isTrue(); } @@ -131,7 +132,7 @@ class MongoParametersUnitTests { void shouldFindMinAndMaxDistanceParameters() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Range.class); - MongoParameters parameters = new MongoParameters(method, false); + MongoParameters parameters = new MongoParameters(ParametersSource.of(method), false); assertThat(parameters.getRangeIndex()).isOne(); assertThat(parameters.getMaxDistanceIndex()).isEqualTo(-1); @@ -141,7 +142,7 @@ class MongoParametersUnitTests { void shouldNotHaveMinDistanceIfOnlyOneDistanceParameterPresent() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); - MongoParameters parameters = new MongoParameters(method, false); + MongoParameters parameters = new MongoParameters(ParametersSource.of(method), false); assertThat(parameters.getRangeIndex()).isEqualTo(-1); assertThat(parameters.getMaxDistanceIndex()).isOne(); @@ -151,7 +152,7 @@ class MongoParametersUnitTests { void shouldReturnMinusOneIfCollationParameterDoesNotExist() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); - MongoParameters parameters = new MongoParameters(method, false); + MongoParameters parameters = new MongoParameters(ParametersSource.of(method), false); assertThat(parameters.getCollationParameterIndex()).isEqualTo(-1); } @@ -160,7 +161,7 @@ class MongoParametersUnitTests { void shouldReturnIndexOfCollationParameterIfExists() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByText", String.class, Collation.class); - MongoParameters parameters = new MongoParameters(method, false); + MongoParameters parameters = new MongoParameters(ParametersSource.of(method), false); assertThat(parameters.getCollationParameterIndex()).isOne(); } @@ -169,7 +170,7 @@ class MongoParametersUnitTests { void shouldReturnIndexUpdateIfExists() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findAndModifyByFirstname", String.class, UpdateDefinition.class, Pageable.class); - MongoParameters parameters = new MongoParameters(method, false); + MongoParameters parameters = new MongoParameters(ParametersSource.of(method), false); assertThat(parameters.getUpdateIndex()).isOne(); } @@ -178,7 +179,7 @@ class MongoParametersUnitTests { void shouldReturnInvalidIndexIfUpdateDoesNotExist() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("someOtherMethod", Point.class, Point.class); - MongoParameters parameters = new MongoParameters(method, false); + MongoParameters parameters = new MongoParameters(ParametersSource.of(method), false); assertThat(parameters.getUpdateIndex()).isEqualTo(-1); }