Browse Source

DATADOC-68 - Support for geo-near queries at repositories.

pull/1/head
Oliver Gierke 15 years ago
parent
commit
ae26f4fea1
  1. 63
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoPage.java
  2. 64
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/AbstractMongoQuery.java
  3. 8
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ConvertingParameterAccessor.java
  4. 8
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoParameterAccessor.java
  5. 121
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoParameters.java
  6. 51
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoParametersParameterAccessor.java
  7. 18
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoQueryCreator.java
  8. 44
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoQueryMethod.java
  9. 24
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Near.java
  10. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/PartTreeMongoQuery.java
  11. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/DistanceUnitTests.java
  12. 14
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
  13. 18
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoParametersParameterAccessorUnitTests.java
  14. 68
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoParametersUnitTests.java
  15. 59
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoQueryCreatorUnitTests.java
  16. 102
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoQueryMethodUnitTests.java
  17. 4
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
  18. 8
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/StubParameterAccessor.java
  19. 60
      src/docbkx/reference/mongo-repositories.xml

63
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoPage.java

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
/*
* Copyright 2011 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
*
* http://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.core.geo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
/**
* Custom {@link Page} to carry the average distance retrieved from the {@link GeoResults} the {@link GeoPage} is set up
* from.
*
* @author Oliver Gierke
*/
public class GeoPage<T> extends PageImpl<GeoResult<T>> {
private static final long serialVersionUID = 23421312312412L;
private final Distance averageDistance;
/**
* Creates a new {@link GeoPage} from the given {@link GeoResults}.
*
* @param content must not be {@literal null}.
*/
public GeoPage(GeoResults<T> results) {
super(results.getContent());
this.averageDistance = results.getAverageDistance();
}
/**
* Creates a new {@link GeoPage} from the given {@link GeoResults}, {@link Pageable} and total.
*
* @param results must not be {@literal null}.
* @param pageable must not be {@literal null}.
* @param total
*/
public GeoPage(GeoResults<T> results, Pageable pageable, long total) {
super(results.getContent(), pageable, total);
this.averageDistance = results.getAverageDistance();
}
/**
* Returns the average distance of the underlying results.
*
* @return the averageDistance
*/
public Distance getAverageDistance() {
return averageDistance;
}
}

64
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/AbstractMongoQuery.java

@ -23,9 +23,15 @@ import org.springframework.data.domain.PageImpl; @@ -23,9 +23,15 @@ import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.CollectionCallback;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.GeoResult;
import org.springframework.data.mongodb.core.geo.GeoResults;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import com.mongodb.DBCollection;
@ -72,10 +78,12 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { @@ -72,10 +78,12 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
*/
public Object execute(Object[] parameters) {
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(method.getParameters(), parameters);
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(method, parameters);
Query query = createQuery(new ConvertingParameterAccessor(template.getConverter(), accessor));
if (method.isCollectionQuery()) {
if (method.isGeoNearQuery()) {
return new GeoNearExecution(accessor).execute(query);
} else if (method.isCollectionQuery()) {
return new CollectionExecution().execute(query);
} else if (method.isPageQuery()) {
return new PagedExecution(accessor.getPageable()).execute(query);
@ -146,10 +154,9 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { @@ -146,10 +154,9 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.mongodb.repository.MongoQuery.Execution #execute(com.mongodb.DBObject)
*/
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query)
*/
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
Object execute(Query query) {
@ -193,4 +200,49 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { @@ -193,4 +200,49 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
return template.findOne(query, entityInformation.getJavaType());
}
}
/**
* {@link Execution} to execute geo-near queries.
*
* @author Oliver Gierke
*/
class GeoNearExecution extends Execution {
private final MongoParameterAccessor accessor;
public GeoNearExecution(MongoParameterAccessor accessor) {
this.accessor = accessor;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query)
*/
@Override
Object execute(Query query) {
Point nearLocation = accessor.getGeoNearLocation();
NearQuery nearQuery = NearQuery.near(nearLocation);
if (query != null) {
nearQuery.query(query);
}
Distance maxDistance = accessor.getMaxDistance();
if (maxDistance != null) {
nearQuery.maxDistance(maxDistance);
}
MongoEntityInformation<?,?> entityInformation = method.getEntityInformation();
GeoResults<?> results = template.geoNear(nearQuery, entityInformation.getJavaType(), entityInformation.getCollectionName());
return isListOfGeoResult() ? results.getContent() : results;
}
private boolean isListOfGeoResult() {
TypeInformation<?> returnType = method.getReturnType();
return returnType.getType().equals(List.class) && GeoResult.class.equals(returnType.getComponentType());
}
}
}

8
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ConvertingParameterAccessor.java

@ -23,6 +23,7 @@ import org.springframework.data.mongodb.core.convert.MongoWriter; @@ -23,6 +23,7 @@ import org.springframework.data.mongodb.core.convert.MongoWriter;
import org.springframework.data.mongodb.core.convert.TypeMapper;
import org.springframework.data.mongodb.core.convert.TypeMapperProvider;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.data.repository.query.ParameterAccessor;
import com.mongodb.BasicDBList;
@ -90,6 +91,13 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { @@ -90,6 +91,13 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor {
public Distance getMaxDistance() {
return delegate.getMaxDistance();
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.MongoParameterAccessor#getGeoNearLocation()
*/
public Point getGeoNearLocation() {
return delegate.getGeoNearLocation();
}
/**
* Converts the given value with the underlying {@link MongoWriter}.

8
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoParameterAccessor.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.data.mongodb.repository;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.data.repository.query.ParameterAccessor;
/**
@ -32,4 +33,11 @@ public interface MongoParameterAccessor extends ParameterAccessor { @@ -32,4 +33,11 @@ public interface MongoParameterAccessor extends ParameterAccessor {
* at all or the given value for it was {@literal null}.
*/
Distance getMaxDistance();
/**
* Returns the {@link Point} to use for a geo-near query.
*
* @return
*/
Point getGeoNearLocation();
}

121
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoParameters.java

@ -17,9 +17,11 @@ package org.springframework.data.mongodb.repository; @@ -17,9 +17,11 @@ package org.springframework.data.mongodb.repository;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.MethodParameter;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
@ -30,40 +32,107 @@ import org.springframework.data.repository.query.Parameters; @@ -30,40 +32,107 @@ import org.springframework.data.repository.query.Parameters;
*/
public class MongoParameters extends Parameters {
private int distanceIndex = -1;
public MongoParameters(Method method) {
private final Integer distanceIndex;
private Integer nearIndex;
/**
* Creates a new {@link MongoParameters} instance from the given {@link Method} and {@link MongoQueryMethod}.
*
* @param method must not be {@literal null}.
* @param queryMethod must not be {@literal null}.
*/
public MongoParameters(Method method, boolean isGeoNearMethod) {
super(method);
this.distanceIndex = Arrays.asList(method.getParameterTypes()).indexOf(Distance.class);
List<Class<?>> parameterTypes = Arrays.asList(method.getParameterTypes());
this.distanceIndex = parameterTypes.indexOf(Distance.class);
if (this.nearIndex == null && isGeoNearMethod) {
this.nearIndex = getNearIndex(parameterTypes);
} else if (this.nearIndex == null) {
this.nearIndex = -1;
}
}
/* (non-Javadoc)
@SuppressWarnings("unchecked")
private final int getNearIndex(List<Class<?>> parameterTypes) {
for (Class<?> reference : Arrays.asList(Point.class, double[].class)) {
int nearIndex = parameterTypes.indexOf(reference);
if (nearIndex == -1) {
continue;
}
if (nearIndex == parameterTypes.lastIndexOf(reference)) {
return nearIndex;
} else {
throw new IllegalStateException("Multiple Point parameters found but none annotated with @Near!");
}
}
return -1;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.Parameters#createParameter(org.springframework.core.MethodParameter)
*/
@Override
protected Parameter createParameter(MethodParameter parameter) {
return new MongoParameter(parameter, this);
MongoParameter mongoParameter = new MongoParameter(parameter);
// Detect manually annotated @Near Point and reject multiple annotated ones
if (this.nearIndex == null && mongoParameter.isManuallyAnnotatedNearParameter()) {
this.nearIndex = mongoParameter.getIndex();
} else if (mongoParameter.isManuallyAnnotatedNearParameter()) {
throw new IllegalStateException(String.format("Found multiple @Near annotations ond method %s! Only one allowed!", parameter.getMethod().toString()));
}
return mongoParameter;
}
/**
* Returns the index of a {@link Distance} parameter to be used for geo queries.
*
* @return
*/
public int getDistanceIndex() {
return distanceIndex;
}
/**
* Custom {@link Parameter} implementation adding parameters of type {@link Distance} to the special ones.
* Returns the index of the parameter to be used to start a geo-near query from.
*
* @return
*/
public int getNearIndex() {
return nearIndex;
}
/**
* Custom {@link Parameter} implementation adding parameters of type {@link Distance} to the special ones.
*
* @author Oliver Gierke
*/
static class MongoParameter extends Parameter {
class MongoParameter extends Parameter {
private final MethodParameter parameter;
/**
* Creates a new {@link MongoParameter}.
*
* @param parameter
* @param parameters
* @param parameter must not be {@literal null}.
*/
MongoParameter(MethodParameter parameter, Parameters parameters) {
super(parameter, parameters);
MongoParameter(MethodParameter parameter) {
super(parameter);
this.parameter = parameter;
if (!isPoint() && hasNearAnnotation()) {
throw new IllegalArgumentException("Near annotation is only allowed at Point parameter!");
}
}
/*
@ -72,7 +141,25 @@ public class MongoParameters extends Parameters { @@ -72,7 +141,25 @@ public class MongoParameters extends Parameters {
*/
@Override
public boolean isSpecialParameter() {
return super.isSpecialParameter() || getType().equals(Distance.class);
return super.isSpecialParameter() || getType().equals(Distance.class)
|| isNearParameter();
}
private boolean isNearParameter() {
Integer nearIndex = MongoParameters.this.nearIndex;
return nearIndex != null && nearIndex.equals(getIndex());
}
private boolean isManuallyAnnotatedNearParameter() {
return isPoint() && hasNearAnnotation();
}
private boolean isPoint() {
return getType().equals(Point.class) || getType().equals(double[].class);
}
private boolean hasNearAnnotation() {
return parameter.getParameterAnnotation(Near.class) != null;
}
}
}

51
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoParametersParameterAccessor.java

@ -16,24 +16,27 @@ @@ -16,24 +16,27 @@
package org.springframework.data.mongodb.repository;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.data.repository.query.ParametersParameterAccessor;
/**
* Mongo-specific {@link ParametersParameterAccessor} to allow access to the {@link Distance} parameter.
*
*
* @author Oliver Gierke
*/
public class MongoParametersParameterAccessor extends ParametersParameterAccessor implements MongoParameterAccessor {
private final MongoParameters parameters;
private final MongoQueryMethod method;
/**
* @param parameters
* @param values
* Creates a new {@link MongoParametersParameterAccessor}.
*
* @param method must not be {@literal null}.
* @param values must not be {@@iteral null}.
*/
public MongoParametersParameterAccessor(MongoParameters parameters, Object[] values) {
super(parameters, values);
this.parameters = parameters;
public MongoParametersParameterAccessor(MongoQueryMethod method, Object[] values) {
super(method.getParameters(), values);
this.method = method;
}
/*
@ -41,7 +44,37 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso @@ -41,7 +44,37 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso
* @see org.springframework.data.mongodb.repository.MongoParameterAccessor#getMaxDistance()
*/
public Distance getMaxDistance() {
int index = parameters.getDistanceIndex();
int index = method.getParameters().getDistanceIndex();
return index == -1 ? null : (Distance) getValue(index);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.MongoParameterAccessor#getGeoNearLocation()
*/
public Point getGeoNearLocation() {
int nearIndex = method.getParameters().getNearIndex();
if (nearIndex == -1) {
return null;
}
Object value = getValue(nearIndex);
if (value == null) {
return null;
}
if (value instanceof double[]) {
double[] typedValue = (double[]) value;
if (typedValue.length != 2) {
throw new IllegalArgumentException("The given double[] must have exactly 2 elements!");
} else {
return new Point(typedValue[0], typedValue[1]);
}
}
return (Point) value;
}
}

18
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoQueryCreator.java

@ -47,18 +47,26 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Query> { @@ -47,18 +47,26 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Query> {
private static final Log LOG = LogFactory.getLog(MongoQueryCreator.class);
private final MongoParameterAccessor accessor;
private final boolean isGeoNearQuery;
public MongoQueryCreator(PartTree tree, ConvertingParameterAccessor accessor) {
this(tree, accessor, false);
}
/**
* Creates a new {@link MongoQueryCreator} from the given {@link PartTree} and {@link ParametersParameterAccessor}.
*
* @param tree
* @param accessor
* @param isGeoNearQuery
*/
public MongoQueryCreator(PartTree tree, ConvertingParameterAccessor accessor) {
public MongoQueryCreator(PartTree tree, ConvertingParameterAccessor accessor, boolean isGeoNearQuery) {
super(tree, accessor);
this.accessor = accessor;
this.isGeoNearQuery = isGeoNearQuery;
}
/*
* (non-Javadoc)
@ -66,6 +74,10 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Query> { @@ -66,6 +74,10 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Query> {
*/
@Override
protected Query create(Part part, Iterator<Object> iterator) {
if (isGeoNearQuery && part.getType().equals(Type.NEAR)) {
return null;
}
Criteria criteria = from(part.getType(), where(part.getProperty().toDotPath()),
(PotentiallyConvertingIterator) iterator);
@ -79,6 +91,10 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Query> { @@ -79,6 +91,10 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Query> {
*/
@Override
protected Query and(Part part, Query base, Iterator<Object> iterator) {
if (base == null) {
return create(part, iterator);
}
Criteria criteria = from(part.getType(), where(part.getProperty().toDotPath()),
(PotentiallyConvertingIterator) iterator);

44
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoQueryMethod.java

@ -16,13 +16,21 @@ @@ -16,13 +16,21 @@
package org.springframework.data.mongodb.repository;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.mongodb.core.geo.GeoPage;
import org.springframework.data.mongodb.core.geo.GeoResult;
import org.springframework.data.mongodb.core.geo.GeoResults;
import org.springframework.data.mongodb.repository.MongoRepositoryFactoryBean.EntityInformationCreator;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.util.ClassUtils;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@ -33,6 +41,10 @@ import org.springframework.util.StringUtils; @@ -33,6 +41,10 @@ import org.springframework.util.StringUtils;
*/
class MongoQueryMethod extends QueryMethod {
@SuppressWarnings("unchecked")
private static final List<Class<?>> GEO_NEAR_RESULTS = Arrays
.asList(GeoResult.class, GeoResults.class, GeoPage.class);
private final Method method;
private final MongoEntityInformation<?, ?> entityInformation;
@ -43,6 +55,7 @@ class MongoQueryMethod extends QueryMethod { @@ -43,6 +55,7 @@ class MongoQueryMethod extends QueryMethod {
*/
public MongoQueryMethod(Method method, RepositoryMetadata metadata, EntityInformationCreator entityInformationCreator) {
super(method, metadata);
Assert.notNull(entityInformationCreator, "EntityInformationCreator must not be null!");
this.method = method;
this.entityInformation = entityInformationCreator.getEntityInformation(ClassUtils.getReturnedDomainClass(method),
getDomainClass());
@ -54,7 +67,7 @@ class MongoQueryMethod extends QueryMethod { @@ -54,7 +67,7 @@ class MongoQueryMethod extends QueryMethod {
*/
@Override
protected Parameters createParameters(Method method) {
return new MongoParameters(method);
return new MongoParameters(method, isGeoNearQuery(method));
}
/**
@ -108,13 +121,40 @@ class MongoQueryMethod extends QueryMethod { @@ -108,13 +121,40 @@ class MongoQueryMethod extends QueryMethod {
return (MongoParameters) super.getParameters();
}
/**
* Returns whether te query is a geoNear query.
*
* @return
*/
public boolean isGeoNearQuery() {
return isGeoNearQuery(this.method);
}
private boolean isGeoNearQuery(Method method) {
if (GEO_NEAR_RESULTS.contains(method.getReturnType())) {
return true;
}
if (Iterable.class.isAssignableFrom(method.getReturnType())) {
TypeInformation<?> from = ClassTypeInformation.fromReturnTypeOf(method);
return GeoResult.class.equals(from.getComponentType().getType());
}
return false;
}
/**
* Returns the {@link Query} annotation that is applied to the method or {@code null} if none available.
*
* @return
*/
private Query getQueryAnnotation() {
return method.getAnnotation(Query.class);
}
TypeInformation<?> getReturnType() {
return ClassTypeInformation.fromReturnTypeOf(method);
}
}

24
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Near.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
package org.springframework.data.mongodb.repository;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import org.springframework.data.mongodb.core.geo.Distance;
/**
* Annotation to be used for disambiguing method parameters that shall be used to trigger geo near queries. By default
* those parameters are found without the need for additional annotation if they are the only parameters of the
* according type (e.g. {@link Point}, {@code double[]}, {@link Distance}).
*
* @author Oliver Gierke
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Near {
}

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/PartTreeMongoQuery.java

@ -29,6 +29,7 @@ import org.springframework.data.repository.query.parser.PartTree; @@ -29,6 +29,7 @@ import org.springframework.data.repository.query.parser.PartTree;
public class PartTreeMongoQuery extends AbstractMongoQuery {
private final PartTree tree;
private final boolean isGeoNearQuery;
/**
* Creates a new {@link PartTreeMongoQuery} from the given {@link QueryMethod} and {@link MongoTemplate}.
@ -40,6 +41,7 @@ public class PartTreeMongoQuery extends AbstractMongoQuery { @@ -40,6 +41,7 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
super(method, template);
this.tree = new PartTree(method.getName(), method.getEntityInformation().getJavaType());
this.isGeoNearQuery = method.isGeoNearQuery();
}
/**
@ -59,7 +61,7 @@ public class PartTreeMongoQuery extends AbstractMongoQuery { @@ -59,7 +61,7 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
@Override
protected Query createQuery(ConvertingParameterAccessor accessor) {
MongoQueryCreator creator = new MongoQueryCreator(tree, accessor);
MongoQueryCreator creator = new MongoQueryCreator(tree, accessor, isGeoNearQuery);
return creator.createQuery();
}
}

2
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/DistanceUnitTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2010-2011 the original author or authors.
* Copyright 2011 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.

14
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java

@ -21,6 +21,9 @@ import org.springframework.data.domain.Sort; @@ -21,6 +21,9 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.geo.Box;
import org.springframework.data.mongodb.core.geo.Circle;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.GeoResults;
import org.springframework.data.mongodb.core.geo.Metrics;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ -317,4 +320,15 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -317,4 +320,15 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
assertThat(result.get(4), is(leroi));
}
@Test
public void executesGeoNearQueryForResultsCorrectly() {
Point point = new Point(-73.99171, 40.738868);
dave.setLocation(point);
repository.save(dave);
GeoResults<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000, Metrics.KILOMETERS));
assertThat(results.getContent().isEmpty(), is(false));
}
}

18
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoParametersParameterAccessorUnitTests.java

@ -25,6 +25,11 @@ import org.junit.Test; @@ -25,6 +25,11 @@ import org.junit.Test;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.Metrics;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.repository.MongoRepositoryFactoryBean.EntityInformationCreator;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
/**
* Unit tests for {@link MongoParametersParameterAccessor}.
@ -34,14 +39,17 @@ import org.springframework.data.mongodb.core.geo.Point; @@ -34,14 +39,17 @@ import org.springframework.data.mongodb.core.geo.Point;
public class MongoParametersParameterAccessorUnitTests {
private static final Distance DISTANCE = new Distance(2.5, Metrics.KILOMETERS);
private static final RepositoryMetadata metadata = new DefaultRepositoryMetadata(PersonRepository.class);
private static final MongoMappingContext context = new MongoMappingContext();
private static final EntityInformationCreator creator = new EntityInformationCreator(context);
@Test
public void returnsNullForDistanceIfNoneAvailable() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class);
MongoParameters parameters = new MongoParameters(method);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, creator);
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(parameters,
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod,
new Object[] { new Point(10, 20) });
assertThat(accessor.getMaxDistance(), is(nullValue()));
}
@ -50,14 +58,14 @@ public class MongoParametersParameterAccessorUnitTests { @@ -50,14 +58,14 @@ public class MongoParametersParameterAccessorUnitTests {
public void returnsDistanceIfAvailable() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class);
MongoParameters parameters = new MongoParameters(method);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, creator);
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(parameters, new Object[] {
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] {
new Point(10, 20), DISTANCE });
assertThat(accessor.getMaxDistance(), is(DISTANCE));
}
interface PersonRepository {
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLocationNear(Point point);

68
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoParametersUnitTests.java

@ -22,36 +22,92 @@ import java.lang.reflect.Method; @@ -22,36 +22,92 @@ import java.lang.reflect.Method;
import java.util.List;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.GeoResults;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.data.mongodb.repository.MongoParameters.MongoParameter;
import org.springframework.data.repository.query.Parameter;
/**
* Unit tests for {@link MongoParameters}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class MongoParametersUnitTests {
@Mock
MongoQueryMethod queryMethod;
@Test
public void discoversDistanceParameter() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class);
MongoParameters parameters = new MongoParameters(method);
MongoParameters parameters = new MongoParameters(method, false);
assertThat(parameters.getNumberOfParameters(), is(2));
assertThat(parameters.getDistanceIndex(), is(1));
assertThat(parameters.getBindableParameters().getNumberOfParameters(), is(1));
MongoParameter parameter = new MongoParameters.MongoParameter(new MethodParameter(method,
parameters.getDistanceIndex()), parameters);
Parameter parameter = parameters.getParameter(1);
assertThat(parameter.isSpecialParameter(), is(true));
assertThat(parameter.isBindable(), is(false));
}
@Test
public void doesNotConsiderPointAsNearForSimpleQuery() throws Exception {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class);
MongoParameters parameters = new MongoParameters(method, false);
assertThat(parameters.getNearIndex(), is(-1));
}
@Test(expected = IllegalStateException.class)
public void rejectsMultiplePointsForGeoNearMethod() throws Exception {
Method method = PersonRepository.class.getMethod("findByLocationNearAndOtherLocation", Point.class, Point.class);
new MongoParameters(method, true);
}
@Test(expected = IllegalStateException.class)
public void rejectsMultipleDoubleArraysForGeoNearMethod() throws Exception {
Method method = PersonRepository.class.getMethod("invalidDoubleArrays", double[].class, double[].class);
new MongoParameters(method, true);
}
@Test
public void doesNotRejectMultiplePointsForSimpleQueryMethod() throws Exception {
Method method = PersonRepository.class.getMethod("someOtherMethod", Point.class, Point.class);
new MongoParameters(method, false);
}
@Test
public void findsAnnotatedPointForGeoNearQuery() throws Exception {
Method method = PersonRepository.class.getMethod("findByOtherLocationAndLocationNear", Point.class, Point.class);
MongoParameters parameters = new MongoParameters(method, true);
assertThat(parameters.getNearIndex(), is(1));
}
@Test
public void findsAnnotatedDoubleArrayForGeoNearQuery() throws Exception {
Method method = PersonRepository.class.getMethod("validDoubleArrays", double[].class, double[].class);
MongoParameters parameters = new MongoParameters(method, true);
assertThat(parameters.getNearIndex(), is(1));
}
interface PersonRepository {
List<Person> findByLocationNear(Point point, Distance distance);
GeoResults<Person> findByLocationNearAndOtherLocation(Point point, Point anotherLocation);
GeoResults<Person> invalidDoubleArrays(double[] first, double[] second);
List<Person> someOtherMethod(Point first, Point second);
GeoResults<Person> findByOtherLocationAndLocationNear(Point point, @Near Point anotherLocation);
GeoResults<Person> validDoubleArrays(double[] first, @Near double[] second);
}
}

59
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoQueryCreatorUnitTests.java

@ -15,12 +15,13 @@ @@ -15,12 +15,13 @@
*/
package org.springframework.data.mongodb.repository;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import static org.hamcrest.CoreMatchers.*;
import static org.springframework.data.mongodb.repository.StubParameterAccessor.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import static org.springframework.data.mongodb.repository.StubParameterAccessor.*;
import java.lang.reflect.Method;
import java.util.List;
@ -38,9 +39,12 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; @@ -38,9 +39,12 @@ import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.Metrics;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.MongoQueryCreator;
import org.springframework.data.mongodb.repository.MongoRepositoryFactoryBean.EntityInformationCreator;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.query.parser.PartTree;
import com.mongodb.BasicDBObject;
@ -103,42 +107,49 @@ public class MongoQueryCreatorUnitTests { @@ -103,42 +107,49 @@ public class MongoQueryCreatorUnitTests {
assertThat(query.getQueryObject(), is(new Query(Criteria.where("firstName").is(null)).getQueryObject()));
}
@Test
public void bindsMetricDistanceParameterToNearSphereCorrectly() throws Exception {
Point point = new Point(10, 20);
Distance distance = new Distance(2.5, Metrics.KILOMETERS);
Query query = query(where("location").nearSphere(point).maxDistance(distance.getNormalizedValue()).and("firstname").is("Dave"));
Query query = query(where("location").nearSphere(point).maxDistance(distance.getNormalizedValue()).and("firstname")
.is("Dave"));
assertBindsDistanceToQuery(point, distance, query);
}
@Test
public void bindsDistanceParameterToNearCorrectly() throws Exception {
Point point = new Point(10, 20);
Distance distance = new Distance(2.5);
Query query = query(where("location").near(point).maxDistance(distance.getNormalizedValue()).and("firstname").is("Dave"));
Query query = query(where("location").near(point).maxDistance(distance.getNormalizedValue()).and("firstname")
.is("Dave"));
assertBindsDistanceToQuery(point, distance, query);
}
private void assertBindsDistanceToQuery(Point point, Distance distance, Query reference) throws Exception {
when(converter.convertToMongoType("Dave")).thenReturn("Dave");
PartTree tree = new PartTree("findByLocationNearAndFirstname", org.springframework.data.mongodb.repository.Person.class);
MongoParameters parameters = new MongoParameters(PersonRepository.class.getMethod("findByLocationNearAndFirstname", Point.class, Distance.class, String.class));
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(parameters, new Object[] { point, distance, "Dave" });
Query query = new MongoQueryCreator(tree, new ConvertingParameterAccessor(converter, accessor)).createQuery();
assertThat(query.getQueryObject(), is(query.getQueryObject()));
when(converter.convertToMongoType("Dave")).thenReturn("Dave");
PartTree tree = new PartTree("findByLocationNearAndFirstname",
org.springframework.data.mongodb.repository.Person.class);
Method method = PersonRepository.class.getMethod("findByLocationNearAndFirstname", Point.class, Distance.class,
String.class);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class),
new EntityInformationCreator(new MongoMappingContext()));
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] { point, distance,
"Dave" });
Query query = new MongoQueryCreator(tree, new ConvertingParameterAccessor(converter, accessor)).createQuery();
assertThat(query.getQueryObject(), is(query.getQueryObject()));
}
interface PersonRepository {
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname);
}
}

102
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoQueryMethodUnitTests.java

@ -15,15 +15,22 @@ @@ -15,15 +15,22 @@
*/
package org.springframework.data.mongodb.repository;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.User;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.GeoPage;
import org.springframework.data.mongodb.core.geo.GeoResult;
import org.springframework.data.mongodb.core.geo.GeoResults;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.repository.MongoRepositoryFactoryBean.EntityInformationCreator;
import org.springframework.data.repository.Repository;
@ -31,13 +38,13 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat @@ -31,13 +38,13 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
/**
* Unit test for {@link MongoQueryMethod}.
*
*
* @author Oliver Gierke
*/
public class MongoQueryMethodUnitTests {
EntityInformationCreator creator;
@Before
public void setUp() {
MongoMappingContext context = new MongoMappingContext();
@ -46,36 +53,91 @@ public class MongoQueryMethodUnitTests { @@ -46,36 +53,91 @@ public class MongoQueryMethodUnitTests {
@Test
public void detectsCollectionFromRepoTypeIfReturnTypeNotAssignable() throws Exception {
Method method = SampleRepository.class.getMethod("method");
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), creator);
MongoEntityInformation<?,?> entityInformation = queryMethod.getEntityInformation();
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
creator);
MongoEntityInformation<?, ?> entityInformation = queryMethod.getEntityInformation();
assertThat(entityInformation.getJavaType(), is(typeCompatibleWith(Address.class)));
assertThat(entityInformation.getCollectionName(), is("contact"));
}
@Test
public void detectsCollectionFromReturnTypeIfReturnTypeAssignable() throws Exception {
Method method = SampleRepository2.class.getMethod("method");
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), creator);
MongoEntityInformation<?,?> entityInformation = queryMethod.getEntityInformation();
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
creator);
MongoEntityInformation<?, ?> entityInformation = queryMethod.getEntityInformation();
assertThat(entityInformation.getJavaType(), is(typeCompatibleWith(Person.class)));
assertThat(entityInformation.getCollectionName(), is("person"));
}
@Test
public void discoversUserAsDomainTypeForGeoPageQueryMethod() throws Exception {
MongoQueryMethod queryMethod = queryMethod("findByLocationNear", Point.class, Distance.class, Pageable.class);
assertThat(queryMethod.isGeoNearQuery(), is(true));
assertThat(queryMethod.isPageQuery(), is(true));
queryMethod = queryMethod("findByFirstname", String.class, Point.class);
assertThat(queryMethod.isGeoNearQuery(), is(true));
assertThat(queryMethod.isPageQuery(), is(false));
assertThat(queryMethod.getEntityInformation().getJavaType(), is(typeCompatibleWith(User.class)));
assertThat(queryMethod("findByEmailAddress", String.class, Point.class).isGeoNearQuery(), is(true));
assertThat(queryMethod("findByFirstname", String.class, Point.class).isGeoNearQuery(), is(true));
assertThat(queryMethod("findByLastname", String.class, Point.class).isGeoNearQuery(), is(true));
}
@Test(expected = IllegalArgumentException.class)
public void rejectsGeoPageQueryWithoutPageable() throws Exception {
queryMethod("findByLocationNear", Point.class, Distance.class);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullEntityCreator() throws Exception {
Method method = PersonRepository.class.getMethod("findByFirstname", String.class, Point.class);
new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class), null);
}
@Test
public void considersMethodReturningGeoPageAsPagingMethod() throws Exception {
MongoQueryMethod method = queryMethod("findByLocationNear", Point.class, Distance.class, Pageable.class);
assertThat(method.isPageQuery(), is(true));
assertThat(method.isCollectionQuery(), is(false));
}
private MongoQueryMethod queryMethod(String name, Class<?>... parameters) throws Exception {
Method method = PersonRepository.class.getMethod(name, parameters);
return new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class), creator);
}
interface PersonRepository extends Repository<User, Long> {
// Misses Pageable
GeoPage<User> findByLocationNear(Point point, Distance distance);
GeoPage<User> findByLocationNear(Point point, Distance distance, Pageable pageable);
GeoResult<User> findByEmailAddress(String lastname, Point location);
GeoResults<User> findByFirstname(String firstname, Point location);
Collection<GeoResult<User>> findByLastname(String lastname, Point location);
}
interface SampleRepository extends Repository<Contact, Long> {
List<Address> method();
}
interface SampleRepository2 extends Repository<Contact, Long> {
List<Person> method();
}
}

4
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

@ -23,6 +23,8 @@ import org.springframework.data.domain.Pageable; @@ -23,6 +23,8 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.geo.Box;
import org.springframework.data.mongodb.core.geo.Circle;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.GeoResults;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
@ -140,4 +142,6 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query @@ -140,4 +142,6 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
List<Person> findBySex(Sex sex);
List<Person> findByNamedQuery(String firstname);
GeoResults<Person> findByLocationNear(Point point, Distance maxDistance);
}

8
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/StubParameterAccessor.java

@ -22,6 +22,7 @@ import org.springframework.data.domain.Pageable; @@ -22,6 +22,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.convert.MongoWriter;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.data.mongodb.repository.ConvertingParameterAccessor;
import org.springframework.data.repository.query.ParameterAccessor;
@ -92,4 +93,11 @@ class StubParameterAccessor implements MongoParameterAccessor { @@ -92,4 +93,11 @@ class StubParameterAccessor implements MongoParameterAccessor {
public Iterator<Object> iterator() {
return Arrays.asList(values).iterator();
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.MongoParameterAccessor#getGeoNearLocation()
*/
public Point getGeoNearLocation() {
return null;
}
}

60
src/docbkx/reference/mongo-repositories.xml

@ -285,6 +285,66 @@ public class PersonRepositoryTests { @@ -285,6 +285,66 @@ public class PersonRepositoryTests {
</tgroup>
</table></para>
<section>
<title>Geo-spatial repository queries</title>
<para>As you've just seen there are a few keywords triggering
geo-spatial operations within a MongoDB query. The <code>Near</code>
keyword allows some further modification. Let's have look at some
examples:</para>
<example>
<title>Advanced <code>Near</code> queries</title>
<programlisting language="java">public interface PersonRepository extends MongoRepository&lt;Person, String&gt;
// { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
List&lt;Person&gt; findByLocationNear(Point location, Distance distance);
}</programlisting>
</example>
<para>Adding a <classname>Distance</classname> parameter to the query
method allows restricting results to those within the given distance. If
the <classname>Distance</classname> was set up containing a
<interfacename>Metric</interfacename> we will transparently use
<code>$nearSphere</code> instead of $code.</para>
<example>
<title>Using <code>Distance</code> with <code>Metrics</code></title>
<programlisting language="java">Point point = new Point(43.7, 48.8);
Distance distance = new Distance(200, Metrics.KILOMETERS);
… = repository.findByLocationNear(point, distance);
// {'location' : {'$nearSphere' : [43.7, 48.8], '$maxDistance' : 0.03135711885774796}}</programlisting>
</example>
<para>As you can see using a <classname>Distance</classname> equipped
with a <interfacename>Metric</interfacename> causes
<code>$nearSphere</code> clause to be added instead of a plain
<code>$near</code>. Beyond that the actual distance gets calculated
according to the <classname>Metrics</classname> used.</para>
<simplesect>
<title>Geo-near queries</title>
<para></para>
<programlisting language="java">public interface PersonRepository extends MongoRepository&lt;Person, String&gt;
// {'geoNear' : 'location', 'near' : [x, y] }
GeoResults&lt;Person&gt; findByLocationNear(Point location);
// No metric: {'geoNear' : 'person', 'near' : [x, y], maxDistance : distance }
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'maxDistance' : distance,
// 'distanceMultiplier' : metric.multiplier, 'spherical' : true }
GeoResults&lt;Person&gt; findByLocationNear(Point location, Distance distance);
// {'geoNear' : 'location', 'near' : [x, y] }
GeoResults&lt;Person&gt; findByLocationNear(Point location);
}</programlisting>
</simplesect>
</section>
<section>
<title>Mongo JSON based query methods and field restriction</title>

Loading…
Cancel
Save