Browse Source

Adopt `MongoParameters` and `MongoParameter` to reflect the actual parameter type when using generics.

Closes #4579
pull/4589/head
Mark Paluch 2 years ago
parent
commit
a4fcbb15bf
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 82
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java
  2. 5
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java
  3. 5
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryMethod.java
  4. 31
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java

82
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.mongodb.repository.query.MongoParameters.MongoParameter;
import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersSource;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -49,38 +50,48 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
private final @Nullable Integer nearIndex; private final @Nullable Integer nearIndex;
private final @Nullable Integer collationIndex; private final @Nullable Integer collationIndex;
private final int updateIndex; private final int updateIndex;
private final TypeInformation<?> domainType;
/** /**
* Creates a new {@link MongoParameters} instance from the given {@link Method} and {@link MongoQueryMethod}. * 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 * @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<Class<?>> parameterTypes = Arrays.asList(method.getParameterTypes()); List<Class<?>> parameterTypes = Arrays.asList(method.getParameterTypes());
this.domainType = parametersSource.getDomainTypeInformation();
this.fullTextIndex = parameterTypes.indexOf(TextCriteria.class); this.fullTextIndex = parameterTypes.indexOf(TextCriteria.class);
TypeInformation<?> declaringClassInfo = TypeInformation.of(method.getDeclaringClass()); TypeInformation<?> declaringClassInfo = TypeInformation.of(parametersSource.getContainingClass());
List<TypeInformation<?>> parameterTypeInfo = declaringClassInfo.getParameterTypes(method); List<TypeInformation<?>> parameterTypeInfo = declaringClassInfo.getParameterTypes(method);
this.rangeIndex = getTypeIndex(parameterTypeInfo, Range.class, Distance.class); this.rangeIndex = getTypeIndex(parameterTypeInfo, Range.class, Distance.class);
this.maxDistanceIndex = this.rangeIndex == -1 ? getTypeIndex(parameterTypeInfo, Distance.class, null) : -1; this.maxDistanceIndex = this.rangeIndex == -1 ? getTypeIndex(parameterTypeInfo, Distance.class, null) : -1;
this.collationIndex = getTypeIndex(parameterTypeInfo, Collation.class, null); this.collationIndex = getTypeIndex(parameterTypeInfo, Collation.class, null);
this.updateIndex = QueryUtils.indexOfAssignableParameter(UpdateDefinition.class, parameterTypes); this.updateIndex = QueryUtils.indexOfAssignableParameter(UpdateDefinition.class, parameterTypes);
this.nearIndex = nearIndex.nearIndex;
int index = findNearIndexInParameters(method);
if (index == -1 && isGeoNearMethod) {
index = getNearIndex(parameterTypes);
}
this.nearIndex = index;
} }
private MongoParameters(List<MongoParameter> parameters, int maxDistanceIndex, @Nullable Integer nearIndex, private MongoParameters(List<MongoParameter> 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); super(parameters);
@ -90,9 +101,25 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
this.rangeIndex = rangeIndex; this.rangeIndex = rangeIndex;
this.collationIndex = collationIndex; this.collationIndex = collationIndex;
this.updateIndex = updateIndex; this.updateIndex = updateIndex;
this.domainType = domainType;
} }
private int getNearIndex(List<Class<?>> 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<Class<?>> parameterTypes) {
for (Class<?> reference : Arrays.asList(Point.class, double[].class)) { for (Class<?> reference : Arrays.asList(Point.class, double[].class)) {
@ -112,15 +139,18 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
return -1; return -1;
} }
private int findNearIndexInParameters(Method method) { static int findNearIndexInParameters(Method method) {
int index = -1; int index = -1;
for (java.lang.reflect.Parameter p : method.getParameters()) { for (java.lang.reflect.Parameter p : method.getParameters()) {
MongoParameter param = createParameter(MethodParameter.forParameter(p)); MethodParameter methodParameter = MethodParameter.forParameter(p);
if (param.isManuallyAnnotatedNearParameter()) {
if ((Point.class.isAssignableFrom(methodParameter.getParameterType())
|| methodParameter.getParameterType().equals(double[].class))
&& methodParameter.hasParameterAnnotation(Near.class)) {
if (index == -1) { if (index == -1) {
index = param.getIndex(); index = methodParameter.getParameterIndex();
} else { } else {
throw new IllegalStateException( throw new IllegalStateException(
String.format("Found multiple @Near annotations ond method %s; Only one allowed", method)); String.format("Found multiple @Near annotations ond method %s; Only one allowed", method));
@ -131,11 +161,6 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
return index; return index;
} }
@Override
protected MongoParameter createParameter(MethodParameter parameter) {
return new MongoParameter(parameter);
}
public int getDistanceRangeIndex() { public int getDistanceRangeIndex() {
return -1; return -1;
} }
@ -197,6 +222,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
/** /**
* Returns the index of the {@link UpdateDefinition} parameter or -1 if not present. * Returns the index of the {@link UpdateDefinition} parameter or -1 if not present.
*
* @return -1 if not present. * @return -1 if not present.
* @since 3.4 * @since 3.4
*/ */
@ -207,7 +233,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
@Override @Override
protected MongoParameters createFrom(List<MongoParameter> parameters) { protected MongoParameters createFrom(List<MongoParameter> parameters) {
return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex, this.rangeIndex, 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<TypeInformation<?>> parameterTypes, Class<?> type, @Nullable Class<?> componentType) { private int getTypeIndex(List<TypeInformation<?>> parameterTypes, Class<?> type, @Nullable Class<?> componentType) {
@ -234,18 +260,21 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
* *
* @author Oliver Gierke * @author Oliver Gierke
*/ */
class MongoParameter extends Parameter { static class MongoParameter extends Parameter {
private final MethodParameter parameter; private final MethodParameter parameter;
private final @Nullable Integer nearIndex;
/** /**
* Creates a new {@link MongoParameter}. * Creates a new {@link MongoParameter}.
* *
* @param parameter must not be {@literal null}. * @param parameter must not be {@literal null}.
* @param domainType must not be {@literal null}.
*/ */
MongoParameter(MethodParameter parameter) { MongoParameter(MethodParameter parameter, TypeInformation<?> domainType, @Nullable Integer nearIndex) {
super(parameter); super(parameter, domainType);
this.parameter = parameter; this.parameter = parameter;
this.nearIndex = nearIndex;
if (!isPoint() && hasNearAnnotation()) { if (!isPoint() && hasNearAnnotation()) {
throw new IllegalArgumentException("Near annotation is only allowed at Point parameter"); throw new IllegalArgumentException("Near annotation is only allowed at Point parameter");
@ -259,7 +288,6 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
} }
private boolean isNearParameter() { private boolean isNearParameter() {
Integer nearIndex = MongoParameters.this.nearIndex;
return nearIndex != null && nearIndex.equals(getIndex()); return nearIndex != null && nearIndex.equals(getIndex());
} }

5
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.mongodb.util.BsonUtils;
import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata; 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.repository.query.QueryMethod;
import org.springframework.data.util.Lazy; import org.springframework.data.util.Lazy;
import org.springframework.data.util.ReactiveWrappers; import org.springframework.data.util.ReactiveWrappers;
@ -94,8 +95,8 @@ public class MongoQueryMethod extends QueryMethod {
} }
@Override @Override
protected MongoParameters createParameters(Method method) { protected MongoParameters createParameters(ParametersSource parametersSource) {
return new MongoParameters(method, isGeoNearQuery(method)); return new MongoParameters(parametersSource, isGeoNearQuery(parametersSource.getMethod()));
} }
/** /**

5
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.mongodb.repository.query.MongoParameters.MongoParameter;
import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata; 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.repository.util.ReactiveWrapperConverters;
import org.springframework.data.util.Lazy; import org.springframework.data.util.Lazy;
import org.springframework.data.util.ReactiveWrappers; import org.springframework.data.util.ReactiveWrappers;
@ -71,8 +72,8 @@ public class ReactiveMongoQueryMethod extends MongoQueryMethod {
} }
@Override @Override
protected MongoParameters createParameters(Method method) { protected MongoParameters createParameters(ParametersSource parametersSource) {
return new MongoParameters(method, isGeoNearQuery(method)); return new MongoParameters(parametersSource, isGeoNearQuery(parametersSource.getMethod()));
} }
@Override @Override

31
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.Near;
import org.springframework.data.mongodb.repository.Person; import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.ParametersSource;
/** /**
* Unit tests for {@link MongoParameters}. * Unit tests for {@link MongoParameters}.
@ -52,7 +53,7 @@ class MongoParametersUnitTests {
void discoversDistanceParameter() throws NoSuchMethodException, SecurityException { void discoversDistanceParameter() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); 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.getNumberOfParameters()).isEqualTo(2);
assertThat(parameters.getMaxDistanceIndex()).isEqualTo(1); assertThat(parameters.getMaxDistanceIndex()).isEqualTo(1);
@ -67,7 +68,7 @@ class MongoParametersUnitTests {
@Test @Test
void doesNotConsiderPointAsNearForSimpleQuery() throws Exception { void doesNotConsiderPointAsNearForSimpleQuery() throws Exception {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); 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); assertThat(parameters.getNearIndex()).isEqualTo(-1);
} }
@ -77,7 +78,7 @@ class MongoParametersUnitTests {
Method method = PersonRepository.class.getMethod("findByLocationNearAndOtherLocation", Point.class, Point.class); 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 @Test
@ -85,21 +86,21 @@ class MongoParametersUnitTests {
Method method = PersonRepository.class.getMethod("invalidDoubleArrays", double[].class, double[].class); 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 @Test
void doesNotRejectMultiplePointsForSimpleQueryMethod() throws Exception { void doesNotRejectMultiplePointsForSimpleQueryMethod() throws Exception {
Method method = PersonRepository.class.getMethod("someOtherMethod", Point.class, Point.class); Method method = PersonRepository.class.getMethod("someOtherMethod", Point.class, Point.class);
new MongoParameters(method, false); new MongoParameters(ParametersSource.of(method), false);
} }
@Test @Test
void findsAnnotatedPointForGeoNearQuery() throws Exception { void findsAnnotatedPointForGeoNearQuery() throws Exception {
Method method = PersonRepository.class.getMethod("findByOtherLocationAndLocationNear", Point.class, Point.class); 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(); assertThat(parameters.getNearIndex()).isOne();
} }
@ -107,7 +108,7 @@ class MongoParametersUnitTests {
void findsAnnotatedDoubleArrayForGeoNearQuery() throws Exception { void findsAnnotatedDoubleArrayForGeoNearQuery() throws Exception {
Method method = PersonRepository.class.getMethod("validDoubleArrays", double[].class, double[].class); 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(); assertThat(parameters.getNearIndex()).isOne();
} }
@ -115,7 +116,7 @@ class MongoParametersUnitTests {
void shouldFindTextCriteriaAtItsIndex() throws SecurityException, NoSuchMethodException { void shouldFindTextCriteriaAtItsIndex() throws SecurityException, NoSuchMethodException {
Method method = PersonRepository.class.getMethod("findByNameAndText", String.class, TextCriteria.class); 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(); assertThat(parameters.getFullTextParameterIndex()).isOne();
} }
@ -123,7 +124,7 @@ class MongoParametersUnitTests {
void shouldTreatTextCriteriaParameterAsSpecialParameter() throws SecurityException, NoSuchMethodException { void shouldTreatTextCriteriaParameterAsSpecialParameter() throws SecurityException, NoSuchMethodException {
Method method = PersonRepository.class.getMethod("findByNameAndText", String.class, TextCriteria.class); 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(); assertThat(parameters.getParameter(parameters.getFullTextParameterIndex()).isSpecialParameter()).isTrue();
} }
@ -131,7 +132,7 @@ class MongoParametersUnitTests {
void shouldFindMinAndMaxDistanceParameters() throws NoSuchMethodException, SecurityException { void shouldFindMinAndMaxDistanceParameters() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Range.class); 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.getRangeIndex()).isOne();
assertThat(parameters.getMaxDistanceIndex()).isEqualTo(-1); assertThat(parameters.getMaxDistanceIndex()).isEqualTo(-1);
@ -141,7 +142,7 @@ class MongoParametersUnitTests {
void shouldNotHaveMinDistanceIfOnlyOneDistanceParameterPresent() throws NoSuchMethodException, SecurityException { void shouldNotHaveMinDistanceIfOnlyOneDistanceParameterPresent() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); 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.getRangeIndex()).isEqualTo(-1);
assertThat(parameters.getMaxDistanceIndex()).isOne(); assertThat(parameters.getMaxDistanceIndex()).isOne();
@ -151,7 +152,7 @@ class MongoParametersUnitTests {
void shouldReturnMinusOneIfCollationParameterDoesNotExist() throws NoSuchMethodException, SecurityException { void shouldReturnMinusOneIfCollationParameterDoesNotExist() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); 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); assertThat(parameters.getCollationParameterIndex()).isEqualTo(-1);
} }
@ -160,7 +161,7 @@ class MongoParametersUnitTests {
void shouldReturnIndexOfCollationParameterIfExists() throws NoSuchMethodException, SecurityException { void shouldReturnIndexOfCollationParameterIfExists() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByText", String.class, Collation.class); 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(); assertThat(parameters.getCollationParameterIndex()).isOne();
} }
@ -169,7 +170,7 @@ class MongoParametersUnitTests {
void shouldReturnIndexUpdateIfExists() throws NoSuchMethodException, SecurityException { void shouldReturnIndexUpdateIfExists() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findAndModifyByFirstname", String.class, UpdateDefinition.class, Pageable.class); 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(); assertThat(parameters.getUpdateIndex()).isOne();
} }
@ -178,7 +179,7 @@ class MongoParametersUnitTests {
void shouldReturnInvalidIndexIfUpdateDoesNotExist() throws NoSuchMethodException, SecurityException { void shouldReturnInvalidIndexIfUpdateDoesNotExist() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("someOtherMethod", Point.class, Point.class); 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); assertThat(parameters.getUpdateIndex()).isEqualTo(-1);
} }

Loading…
Cancel
Save