diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 62e2bf8ed..6a74eeb5a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -218,7 +218,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, public MongoTemplate(MongoClient mongoClient, String databaseName) { this(new SimpleMongoDbFactory(mongoClient, databaseName), (MongoConverter) null); } - + /** * Constructor used for a basic template configuration. * @@ -2116,9 +2116,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); Assert.notNull(outputType, "Output type must not be null!"); - AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context; - - return doAggregate(aggregation, collectionName, outputType, rootContext); + return doAggregate(aggregation, collectionName, outputType, prepareAggregationContext(aggregation, context)); } @SuppressWarnings("ConstantConditions") @@ -2128,9 +2126,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, DocumentCallback callback = new UnwrapAndReadDocumentCallback<>(mongoConverter, outputType, collectionName); AggregationOptions options = aggregation.getOptions(); + if (options.isExplain()) { - Document command = aggregation.toDocument(collectionName, context); + Document command = aggregationToCommand(collectionName, aggregation, context); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Executing aggregation: {}", serializeToJsonSafely(command)); @@ -2141,7 +2140,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, .map(callback::doWith).collect(Collectors.toList()), commandResult); } - List pipeline = aggregation.toPipeline(context); + List pipeline = aggregationToPipeline(aggregation, context); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Executing aggregation: {} in collection {}", serializeToJsonSafely(pipeline), collectionName); @@ -2179,9 +2178,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, Assert.notNull(outputType, "Output type must not be null!"); Assert.isTrue(!aggregation.getOptions().isExplain(), "Can't use explain option with streaming!"); - AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context; + AggregationOperationContext rootContext = prepareAggregationContext(aggregation, context); + AggregationOptions options = aggregation.getOptions(); - List pipeline = aggregation.toPipeline(rootContext); + List pipeline = aggregationToPipeline(aggregation, rootContext); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(pipeline), collectionName); @@ -2844,6 +2844,73 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, return fields; } + /** + * Prepare the {@link AggregationOperationContext} for a given aggregation by either returning the context itself it + * is not {@literal null}, create a {@link TypeBasedAggregationOperationContext} if the aggregation contains type + * information (is a {@link TypedAggregation}) or use the {@link Aggregation#DEFAULT_CONTEXT}. + * + * @param aggregation must not be {@literal null}. + * @param context can be {@literal null}. + * @return the root {@link AggregationOperationContext} to use. + */ + private AggregationOperationContext prepareAggregationContext(Aggregation aggregation, + @Nullable AggregationOperationContext context) { + + if (context != null) { + return context; + } + + if (aggregation instanceof TypedAggregation) { + return new TypeBasedAggregationOperationContext(((TypedAggregation) aggregation).getInputType(), mappingContext, + queryMapper); + } + + return Aggregation.DEFAULT_CONTEXT; + } + + /** + * Extract and map the aggregation pipeline. + * + * @param aggregation + * @param context + * @return + */ + private List aggregationToPipeline(Aggregation aggregation, AggregationOperationContext context) { + + if (!ObjectUtils.nullSafeEquals(context, Aggregation.DEFAULT_CONTEXT)) { + return aggregation.toPipeline(context); + } + + return mapAggregationPipeline(aggregation.toPipeline(context)); + } + + /** + * Extract the command and map the aggregation pipeline. + * + * @param aggregation + * @param context + * @return + */ + private Document aggregationToCommand(String collection, Aggregation aggregation, + AggregationOperationContext context) { + + Document command = aggregation.toDocument(collection, context); + + if (!ObjectUtils.nullSafeEquals(context, Aggregation.DEFAULT_CONTEXT)) { + return command; + } + + command.put("pipeline", mapAggregationPipeline(command.get("pipeline", List.class))); + + return command; + } + + private List mapAggregationPipeline(List pipeline) { + + return pipeline.stream().map(val -> queryMapper.getMappedObject(val, Optional.empty())) + .collect(Collectors.toList()); + } + /** * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original * exception if the conversation failed. Thus allows safe re-throwing of the return value. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java index 0ed3e3b11..1caf16777 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java @@ -496,8 +496,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati */ @Override public ReactiveSessionScoped inTransaction() { - return inTransaction(mongoDatabaseFactory - .getSession(ClientSessionOptions.builder().causallyConsistent(true).build())); + return inTransaction( + mongoDatabaseFactory.getSession(ClientSessionOptions.builder().causallyConsistent(true).build())); } /* @@ -969,9 +969,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati Assert.hasText(collectionName, "Collection name must not be null or empty!"); Assert.notNull(outputType, "Output type must not be null!"); - AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context; + AggregationOperationContext rootContext = prepareAggregationContext(aggregation, context); + AggregationOptions options = aggregation.getOptions(); - List pipeline = aggregation.toPipeline(rootContext); + List pipeline = aggregationToPipeline(aggregation, rootContext); Assert.isTrue(!options.isExplain(), "Cannot use explain option with streaming!"); Assert.isNull(options.getCursorBatchSize(), "Cannot use batchSize cursor option with streaming!"); @@ -988,7 +989,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati AggregationOptions options, ReadDocumentCallback readCallback) { AggregatePublisher cursor = collection.aggregate(pipeline, Document.class) - .allowDiskUse(options.isAllowDiskUse()).useCursor(true); + .allowDiskUse(options.isAllowDiskUse()); if (options.getCollation().isPresent()) { cursor = cursor.collation(options.getCollation().map(Collation::toMongoCollation).get()); @@ -2653,6 +2654,47 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati }; } + /** + * Prepare the {@link AggregationOperationContext} for a given aggregation by either returning the context itself it + * is not {@literal null}, create a {@link TypeBasedAggregationOperationContext} if the aggregation contains type + * information (is a {@link TypedAggregation}) or use the {@link Aggregation#DEFAULT_CONTEXT}. + * + * @param aggregation must not be {@literal null}. + * @param context can be {@literal null}. + * @return the root {@link AggregationOperationContext} to use. + */ + private AggregationOperationContext prepareAggregationContext(Aggregation aggregation, + @Nullable AggregationOperationContext context) { + + if (context != null) { + return context; + } + + if (aggregation instanceof TypedAggregation) { + return new TypeBasedAggregationOperationContext(((TypedAggregation) aggregation).getInputType(), mappingContext, + queryMapper); + } + + return Aggregation.DEFAULT_CONTEXT; + } + + /** + * Extract and map the aggregation pipeline. + * + * @param aggregation + * @param context + * @return + */ + private List aggregationToPipeline(Aggregation aggregation, AggregationOperationContext context) { + + if (!ObjectUtils.nullSafeEquals(context, Aggregation.DEFAULT_CONTEXT)) { + return aggregation.toPipeline(context); + } + + return aggregation.toPipeline(context).stream().map(val -> queryMapper.getMappedObject(val, Optional.empty())) + .collect(Collectors.toList()); + } + /** * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original * exception if the conversation failed. Thus allows safe re-throwing of the return value. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java index 92f7284ff..0784af49f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java @@ -41,7 +41,7 @@ class AggregationOperationRenderer { * {@link Document} representation. * * @param operations must not be {@literal null}. - * @param context must not be {@literal null}. + * @param rootContext must not be {@literal null}. * @return the {@link List} of {@link Document}. */ static List toDocument(List operations, AggregationOperationContext rootContext) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java index a70263305..df20fa83c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java @@ -664,10 +664,10 @@ public class ReactiveMongoTemplateTests { @Test // DATAMONGO-1444 public void geoNear() { - List venues = Arrays.asList(new Venue("Penn Station", -73.99408, 40.75057), // - new Venue("10gen Office", -73.99171, 40.738868), // - new Venue("Flatiron Building", -73.988135, 40.741404), // - new Venue("Maplewood, NJ", -74.2713, 40.73137)); + List venues = Arrays.asList(TestEntities.geolocation().pennStation(), // + TestEntities.geolocation().tenGenOffice(), // + TestEntities.geolocation().flatironBuilding(), // + TestEntities.geolocation().maplewoodNJ()); StepVerifier.create(template.insertAll(venues)).expectNextCount(4).verifyComplete(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/TestEntities.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/TestEntities.java new file mode 100644 index 000000000..77933cae3 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/TestEntities.java @@ -0,0 +1,105 @@ +/* + * Copyright 2018 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; + +import java.util.ArrayList; +import java.util.List; + +/** + * A simple collection of grouped test entities used throughout the test suite. + * + * @author Christoph Strobl + */ +public class TestEntities { + + private static final GeoEntities GEO = new GeoEntities(); + + public static GeoEntities geolocation() { + return GEO; + } + + public static class GeoEntities { + + /** + *
+		 * X: -73.99408
+		 * Y: 40.75057
+		 * 
+ * + * @return new {@link Venue} + */ + public Venue pennStation() { + return new Venue("Penn Station", -73.99408, 40.75057); + } + + /** + *
+		 * X: -73.99171
+		 * Y: 40.738868
+		 * 
+ * + * @return new {@link Venue} + */ + + public Venue tenGenOffice() { + return new Venue("10gen Office", -73.99171, 40.738868); + } + + /** + *
+		 * X: -73.988135
+		 * Y: 40.741404
+		 * 
+ * + * @return new {@link Venue} + */ + public Venue flatironBuilding() { + return new Venue("Flatiron Building", -73.988135, 40.741404); + } + + /** + *
+		 * X: -74.2713
+		 * Y: 40.73137
+		 * 
+ * + * @return new {@link Venue} + */ + public Venue maplewoodNJ() { + return new Venue("Maplewood, NJ", -74.2713, 40.73137); + } + + public List newYork() { + + List venues = new ArrayList<>(); + + venues.add(pennStation()); + venues.add(tenGenOffice()); + venues.add(flatironBuilding()); + venues.add(new Venue("Players Club", -73.997812, 40.739128)); + venues.add(new Venue("City Bakery ", -73.992491, 40.738673)); + venues.add(new Venue("Splash Bar", -73.992491, 40.738673)); + venues.add(new Venue("Momofuku Milk Bar", -73.985839, 40.731698)); + venues.add(new Venue("Shake Shack", -73.98820, 40.74164)); + venues.add(new Venue("Penn Station", -73.99408, 40.75057)); + venues.add(new Venue("Empire State Building", -73.98602, 40.74894)); + venues.add(new Venue("Ulaanbaatar, Mongolia", 106.9154, 47.9245)); + venues.add(maplewoodNJ()); + + return venues; + } + } +} 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 5167eb2df..2dccdf7aa 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 @@ -52,10 +52,13 @@ 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.Box; import org.springframework.data.geo.Metrics; +import org.springframework.data.geo.Point; import org.springframework.data.mapping.MappingException; import org.springframework.data.mongodb.core.CollectionCallback; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.TestEntities; import org.springframework.data.mongodb.core.Venue; import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry; import org.springframework.data.mongodb.core.aggregation.BucketAutoOperation.Granularities; @@ -132,6 +135,7 @@ public class AggregationTests { mongoTemplate.dropCollection(Employee.class); mongoTemplate.dropCollection(Art.class); mongoTemplate.dropCollection("personQueryTemp"); + mongoTemplate.dropCollection(Venue.class); } /** @@ -513,7 +517,7 @@ public class AggregationTests { /* //complex mongodb aggregation framework example from https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state - + db.zipcodes.aggregate( { $group: { @@ -1461,9 +1465,8 @@ public class AggregationTests { @Test // DATAMONGO-1127 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.insertAll(Arrays.asList(TestEntities.geolocation().pennStation(), + TestEntities.geolocation().tenGenOffice(), TestEntities.geolocation().flatironBuilding())); mongoTemplate.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location")); @@ -1863,6 +1866,36 @@ public class AggregationTests { assertThat(categorizeByYear, hasSize(3)); } + @Test // DATAMONGO-1986 + public void runMatchOperationCriteriaThroughQueryMapperForTypedAggregation() { + + mongoTemplate.insertAll(TestEntities.geolocation().newYork()); + + Aggregation aggregation = newAggregation(Venue.class, + match(Criteria.where("location") + .within(new Box(new Point(-73.99756, 40.73083), new Point(-73.988135, 40.741404)))), + project("id", "location", "name")); + + AggregationResults groupResults = mongoTemplate.aggregate(aggregation, "newyork", Document.class); + + assertThat(groupResults.getMappedResults().size(), is(4)); + } + + @Test // DATAMONGO-1986 + public void runMatchOperationCriteriaThroughQueryMapperForUntypedAggregation() { + + mongoTemplate.insertAll(TestEntities.geolocation().newYork()); + + Aggregation aggregation = newAggregation( + match(Criteria.where("location") + .within(new Box(new Point(-73.99756, 40.73083), new Point(-73.988135, 40.741404)))), + project("id", "location", "name")); + + AggregationResults groupResults = mongoTemplate.aggregate(aggregation, "newyork", Document.class); + + assertThat(groupResults.getMappedResults().size(), is(4)); + } + private void createUsersWithReferencedPersons() { mongoTemplate.dropCollection(User.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReactiveAggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReactiveAggregationTests.java index 6054489b5..52275e8b1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReactiveAggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReactiveAggregationTests.java @@ -29,7 +29,12 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.geo.Box; +import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; +import org.springframework.data.mongodb.core.TestEntities; +import org.springframework.data.mongodb.core.Venue; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -65,7 +70,8 @@ public class ReactiveAggregationTests { .create(reactiveMongoTemplate.dropCollection(INPUT_COLLECTION) // .then(reactiveMongoTemplate.dropCollection(OUTPUT_COLLECTION)) // .then(reactiveMongoTemplate.dropCollection(Product.class)) // - .then(reactiveMongoTemplate.dropCollection(City.class))) // + .then(reactiveMongoTemplate.dropCollection(City.class)) // + .then(reactiveMongoTemplate.dropCollection(Venue.class))) // .verifyComplete(); } @@ -128,4 +134,34 @@ public class ReactiveAggregationTests { StepVerifier.create(reactiveMongoTemplate.find(new Query(), City.class, OUTPUT_COLLECTION)).expectNextCount(4) .verifyComplete(); } + + @Test // DATAMONGO-1986 + public void runMatchOperationCriteriaThroughQueryMapperForTypedAggregation() { + + reactiveMongoTemplate.insertAll(TestEntities.geolocation().newYork()).as(StepVerifier::create).expectNextCount(12) + .verifyComplete(); + + Aggregation aggregation = newAggregation(Venue.class, + match(Criteria.where("location") + .within(new Box(new Point(-73.99756, 40.73083), new Point(-73.988135, 40.741404)))), + project("id", "location", "name")); + + reactiveMongoTemplate.aggregate(aggregation, "newyork", Document.class).as(StepVerifier::create).expectNextCount(4) + .verifyComplete(); + } + + @Test // DATAMONGO-1986 + public void runMatchOperationCriteriaThroughQueryMapperForUntypedAggregation() { + + reactiveMongoTemplate.insertAll(TestEntities.geolocation().newYork()).as(StepVerifier::create).expectNextCount(12) + .verifyComplete(); + + Aggregation aggregation = newAggregation( + match(Criteria.where("location") + .within(new Box(new Point(-73.99756, 40.73083), new Point(-73.988135, 40.741404)))), + project("id", "location", "name")); + + reactiveMongoTemplate.aggregate(aggregation, "newyork", Document.class).as(StepVerifier::create).expectNextCount(4) + .verifyComplete(); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReactiveAggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReactiveAggregationUnitTests.java index 1a3aa210f..d751f1320 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReactiveAggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReactiveAggregationUnitTests.java @@ -62,7 +62,6 @@ public class ReactiveAggregationUnitTests { when(db.getCollection(eq(INPUT_COLLECTION), any(Class.class))).thenReturn(collection); when(collection.aggregate(anyList(), any(Class.class))).thenReturn(publisher); when(publisher.allowDiskUse(any())).thenReturn(publisher); - when(publisher.useCursor(any())).thenReturn(publisher); when(publisher.collation(any())).thenReturn(publisher); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java index 6bf8b02e2..9b2f9d02c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java @@ -38,13 +38,13 @@ import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; import org.springframework.data.mongodb.config.AbstractMongoConfiguration; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.TestEntities; import org.springframework.data.mongodb.core.Venue; import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import com.mongodb.Mongo; import com.mongodb.MongoClient; import com.mongodb.WriteConcern; @@ -103,19 +103,7 @@ public abstract class AbstractGeoSpatialTests { } protected void addVenues() { - - template.insert(new Venue("Penn Station", -73.99408, 40.75057)); - template.insert(new Venue("10gen Office", -73.99171, 40.738868)); - template.insert(new Venue("Flatiron Building", -73.988135, 40.741404)); - template.insert(new Venue("Players Club", -73.997812, 40.739128)); - template.insert(new Venue("City Bakery ", -73.992491, 40.738673)); - template.insert(new Venue("Splash Bar", -73.992491, 40.738673)); - template.insert(new Venue("Momofuku Milk Bar", -73.985839, 40.731698)); - template.insert(new Venue("Shake Shack", -73.98820, 40.74164)); - template.insert(new Venue("Penn Station", -73.99408, 40.75057)); - template.insert(new Venue("Empire State Building", -73.98602, 40.74894)); - template.insert(new Venue("Ulaanbaatar, Mongolia", 106.9154, 47.9245)); - template.insert(new Venue("Maplewood, NJ", -74.2713, 40.73137)); + template.insertAll(TestEntities.geolocation().newYork()); } @Test