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 a9cf635350..870c59344e 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.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 static Fields bind(String name, String target) { 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 d30a73727b..29afc03f8e 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.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 GeoNearOperation(NearQuery nearQuery) { */ @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 11d285ec73..02efbdc2d2 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 @@ /** * 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 22614dfb58..b56a59e01d 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.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 6d5669aa7b..eb86fb1e5c 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.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 5212fc1d75..1b7cc323f4 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.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 99c2dda20c..67d598134e 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 @@ /** * 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 b405c4309b..0b6f6dee2e 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 @@ /** * 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 5410f79f32..883cb8a8c4 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 @@ /** * 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 cc5be660fc..8c448543ce 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.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 @@ private void cleanDb() { mongoTemplate.dropCollection(User.class); mongoTemplate.dropCollection(Person.class); mongoTemplate.dropCollection(Reservation.class); + mongoTemplate.dropCollection(Venue.class); } /** @@ -1018,6 +1022,30 @@ public void shouldRetrieveDateTimeFragementsCorrectly() throws Exception { 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 16d4e145c6..95e2f13a73 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.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)); } }