From db7f782ca63e2623c3e12d0844dd69fd40883e51 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Mon, 29 Dec 2014 09:19:29 +0100 Subject: [PATCH] DATAMONGO-1127 - Add support for geoNear queries with distance information. We now support geoNear queries in Aggregations. Exposed GeoNearOperation factory method in Aggregation. Introduced new distanceField property to NearQuery since it is required for geoNear queries in Aggregations. Original pull request: #261. --- .../mongodb/core/aggregation/Aggregation.java | 16 +++++++++- .../core/aggregation/GeoNearOperation.java | 28 +++++++++++++--- .../core/aggregation/GroupOperation.java | 5 ++- .../core/aggregation/LimitOperation.java | 7 ++-- .../core/aggregation/MatchOperation.java | 8 +++-- .../core/aggregation/ProjectionOperation.java | 11 ++++--- .../core/aggregation/SkipOperation.java | 5 ++- .../core/aggregation/SortOperation.java | 5 ++- .../core/aggregation/UnwindOperation.java | 5 ++- .../core/aggregation/AggregationTests.java | 32 +++++++++++++++++-- .../GeoNearOperationUnitTests.java | 13 ++++++-- 11 files changed, 113 insertions(+), 22 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java index a9cf63535..870c59344 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-2015 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. @@ -27,6 +27,7 @@ import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedFi import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField; import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.SerializationUtils; import org.springframework.util.Assert; @@ -291,6 +292,19 @@ public class Aggregation { return Fields.from(field(name, target)); } + /** + * Creates a new {@link GeoNearOperation} instance from the given {@link NearQuery} and the{@code distanceField}. The + * {@code distanceField} defines output field that contains the calculated distance. + * + * @param query must not be {@literal null}. + * @param distanceField must not be {@literal null} or empty. + * @return + * @since 1.7 + */ + public static GeoNearOperation geoNear(NearQuery query, String distanceField) { + return new GeoNearOperation(query, distanceField); + } + /** * Returns a new {@link AggregationOptions.Builder}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperation.java index d30a73727..29afc03f8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2015 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. @@ -22,17 +22,33 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; /** + * Represents a {@code geoNear} aggregation operation. + *

+ * We recommend to use the static factory method {@link Aggregation#geoNear(NearQuery, String)} instead of creating + * instances of this class directly. + * * @author Thomas Darimont * @since 1.3 */ public class GeoNearOperation implements AggregationOperation { private final NearQuery nearQuery; + private final String distanceField; - public GeoNearOperation(NearQuery nearQuery) { + /** + * Creates a new {@link GeoNearOperation} from the given {@link NearQuery} and the given distance field. The + * {@code distanceField} defines output field that contains the calculated distance. + * + * @param query must not be {@literal null}. + * @param distanceField must not be {@literal null}. + */ + public GeoNearOperation(NearQuery nearQuery, String distanceField) { + + Assert.notNull(nearQuery, "NearQuery must not be null."); + Assert.hasLength(distanceField, "Distance field must not be null or empty."); - Assert.notNull(nearQuery); this.nearQuery = nearQuery; + this.distanceField = distanceField; } /* @@ -41,6 +57,10 @@ public class GeoNearOperation implements AggregationOperation { */ @Override public DBObject toDBObject(AggregationOperationContext context) { - return new BasicDBObject("$geoNear", context.getMappedObject(nearQuery.toDBObject())); + + BasicDBObject command = (BasicDBObject) context.getMappedObject(nearQuery.toDBObject()); + command.put("distanceField", distanceField); + + return new BasicDBObject("$geoNear", command); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java index 11d285ec7..02efbdc2d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-2015 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. @@ -31,6 +31,9 @@ import com.mongodb.DBObject; /** * Encapsulates the aggregation framework {@code $group}-operation. + *

+ * We recommend to use the static factory method {@link Aggregation#group(Fields)} instead of creating instances of this + * class directly. * * @see http://docs.mongodb.org/manual/reference/aggregation/group/#stage._S_group * @author Sebastian Herold diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LimitOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LimitOperation.java index 22614dfb5..b56a59e01 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LimitOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LimitOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-2015 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. @@ -21,7 +21,10 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; /** - * Encapsulates the {@code $limit}-operation + * Encapsulates the {@code $limit}-operation. + *

+ * We recommend to use the static factory method {@link Aggregation#limit(long)} instead of creating instances of this + * class directly. * * @see http://docs.mongodb.org/manual/reference/aggregation/limit/ * @author Thomas Darimont diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java index 6d5669aa7..eb86fb1e5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-2015 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. @@ -22,7 +22,11 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; /** - * Encapsulates the {@code $match}-operation + * Encapsulates the {@code $match}-operation. + *

+ * We recommend to use the static factory method + * {@link Aggregation#match(org.springframework.data.mongodb.core.query.Criteria)} instead of creating instances of this + * class directly. * * @see http://docs.mongodb.org/manual/reference/aggregation/match/ * @author Sebastian Herold diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java index 5212fc1d7..1b7cc323f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-2015 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. @@ -28,10 +28,13 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; /** - * Encapsulates the aggregation framework {@code $project}-operation. Projection of field to be used in an - * {@link Aggregation}. A projection is similar to a {@link Field} inclusion/exclusion but more powerful. It can - * generate new fields, change values of given field etc. + * Encapsulates the aggregation framework {@code $project}-operation. *

+ * Projection of field to be used in an {@link Aggregation}. A projection is similar to a {@link Field} + * inclusion/exclusion but more powerful. It can generate new fields, change values of given field etc. + *

+ * We recommend to use the static factory method {@link Aggregation#project(Fields)} instead of creating instances of + * this class directly. * * @see http://docs.mongodb.org/manual/reference/aggregation/project/ * @author Tobias Trelle diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SkipOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SkipOperation.java index 99c2dda20..67d598134 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SkipOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SkipOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2015 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. @@ -22,6 +22,9 @@ import com.mongodb.DBObject; /** * Encapsulates the aggregation framework {@code $skip}-operation. + *

+ * We recommend to use the static factory method {@link Aggregation#skip(int)} instead of creating instances of this + * class directly. * * @see http://docs.mongodb.org/manual/reference/aggregation/skip/ * @author Thomas Darimont diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortOperation.java index b405c4309..0b6f6dee2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2015 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. @@ -26,6 +26,9 @@ import com.mongodb.DBObject; /** * Encapsulates the aggregation framework {@code $sort}-operation. + *

+ * We recommend to use the static factory method {@link Aggregation#sort(Direction, String...)} instead of creating + * instances of this class directly. * * @see http://docs.mongodb.org/manual/reference/aggregation/sort/#pipe._S_sort * @author Thomas Darimont diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/UnwindOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/UnwindOperation.java index 5410f79f3..883cb8a8c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/UnwindOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/UnwindOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2015 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. @@ -23,6 +23,9 @@ import com.mongodb.DBObject; /** * Encapsulates the aggregation framework {@code $unwind}-operation. + *

+ * We recommend to use the static factory method {@link Aggregation#unwind(String)} instead of creating instances of + * this class directly. * * @see http://docs.mongodb.org/manual/reference/aggregation/unwind/#pipe._S_unwind * @author Thomas Darimont diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index cc5be660f..8c448543c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2015 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. @@ -47,11 +47,14 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.dao.DataAccessException; import org.springframework.data.annotation.Id; import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.geo.Metrics; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.core.CollectionCallback; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.Venue; import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry; -import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.index.GeospatialIndex; +import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.repository.Person; import org.springframework.data.util.Version; @@ -120,6 +123,7 @@ public class AggregationTests { mongoTemplate.dropCollection(User.class); mongoTemplate.dropCollection(Person.class); mongoTemplate.dropCollection(Reservation.class); + mongoTemplate.dropCollection(Venue.class); } /** @@ -1018,6 +1022,30 @@ public class AggregationTests { assertThat(dbo.get("dayOfYearPlus1DayManually"), is((Object) dateTime.plusDays(1).getDayOfYear())); } + /** + * @see DATAMONGO-1127 + */ + @Test + public void shouldSupportGeoNearQueriesForAggregationWithDistanceField() { + + mongoTemplate.insert(new Venue("Penn Station", -73.99408, 40.75057)); + mongoTemplate.insert(new Venue("10gen Office", -73.99171, 40.738868)); + mongoTemplate.insert(new Venue("Flatiron Building", -73.988135, 40.741404)); + + mongoTemplate.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location")); + + NearQuery geoNear = NearQuery.near(-73, 40, Metrics.KILOMETERS).num(10).maxDistance(150); + + Aggregation agg = newAggregation(Aggregation.geoNear(geoNear, "distance")); + AggregationResults result = mongoTemplate.aggregate(agg, Venue.class, DBObject.class); + + assertThat(result.getMappedResults(), hasSize(3)); + + DBObject firstResult = result.getMappedResults().get(0); + assertThat(firstResult.containsField("distance"), is(true)); + assertThat(firstResult.get("distance"), is((Object) 117.620092203928)); + } + private void assertLikeStats(LikeStats like, String id, long count) { assertThat(like, is(notNullValue())); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java index 16d4e145c..95e2f13a7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2015 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. @@ -22,23 +22,30 @@ import org.junit.Test; import org.springframework.data.mongodb.core.DBObjectTestUtils; import org.springframework.data.mongodb.core.query.NearQuery; +import com.mongodb.BasicDBObject; import com.mongodb.DBObject; /** * Unit tests for {@link GeoNearOperation}. * * @author Oliver Gierke + * @author Thomas Darimont */ public class GeoNearOperationUnitTests { + /** + * @see DATAMONGO-1127 + */ @Test public void rendersNearQueryAsAggregationOperation() { NearQuery query = NearQuery.near(10.0, 10.0); - GeoNearOperation operation = new GeoNearOperation(query); + GeoNearOperation operation = new GeoNearOperation(query, "distance"); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject nearClause = DBObjectTestUtils.getAsDBObject(dbObject, "$geoNear"); - assertThat(nearClause, is(query.toDBObject())); + + DBObject expected = (DBObject) new BasicDBObject(query.toDBObject().toMap()).append("distanceField", "distance"); + assertThat(nearClause, is(expected)); } }