From 7e774428e44e8c4035f7d8146d36dc3cb4739046 Mon Sep 17 00:00:00 2001 From: Mark Pollack Date: Thu, 31 Mar 2011 18:46:08 -0400 Subject: [PATCH] DATADOC-67 - Criteria API to support keywords for geo search --- .../data/document/mongodb/MongoAdmin.java | 8 +- .../data/document/mongodb/MongoDbUtils.java | 4 +- .../mongodb/MongoExceptionTranslator.java | 6 ++ .../convert/MappingMongoConverter.java | 2 +- .../data/document/mongodb/geo/Circle.java | 50 +++++++++++ .../data/document/mongodb/query/Criteria.java | 19 ++++ .../document/mongodb/GeoSpatialTests.java | 89 +++++++++++++++++-- .../data/document/mongodb/Venue.java | 56 ++++++++++++ 8 files changed, 222 insertions(+), 12 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/geo/Circle.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/Venue.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoAdmin.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoAdmin.java index 4b20e5c76..e516eee87 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoAdmin.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoAdmin.java @@ -49,7 +49,7 @@ public class MongoAdmin implements MongoAdminOperations { */ @ManagedOperation public void dropDatabase(String databaseName) { - mongo.getDB(databaseName).dropDatabase(); + getDB(databaseName).dropDatabase(); } /* (non-Javadoc) @@ -57,7 +57,7 @@ public class MongoAdmin implements MongoAdminOperations { */ @ManagedOperation public void createDatabase(String databaseName) { - mongo.getDB(databaseName); + getDB(databaseName); } /* (non-Javadoc) @@ -65,7 +65,7 @@ public class MongoAdmin implements MongoAdminOperations { */ @ManagedOperation public String getDatabaseStats(String databaseName) { - return mongo.getDB("testAdminDb").getStats().toString(); + return getDB(databaseName).getStats().toString(); } /** @@ -88,7 +88,7 @@ public class MongoAdmin implements MongoAdminOperations { } - public DB getDb(String databaseName) { + DB getDB(String databaseName) { return MongoDbUtils.getDB(mongo, databaseName, username, password == null ? null : password.toCharArray()); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoDbUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoDbUtils.java index d690ecc4e..0a2e89859 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoDbUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoDbUtils.java @@ -92,9 +92,9 @@ public abstract class MongoDbUtils { LOGGER.debug("Opening Mongo DB"); DB db = mongo.getDB(databaseName); - boolean creadentialsGiven = username != null && password != null; + boolean credentialsGiven = username != null && password != null; - if (creadentialsGiven && !db.authenticate(username, password)) { + if (credentialsGiven && !db.authenticate(username, password)) { throw new CannotGetMongoDbConnectionException("Failed to authenticate with Mongo using the given credentials"); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoExceptionTranslator.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoExceptionTranslator.java index ecf9caad4..46b94dab8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoExceptionTranslator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoExceptionTranslator.java @@ -18,9 +18,12 @@ package org.springframework.data.document.mongodb; import com.mongodb.MongoException; import com.mongodb.MongoException.DuplicateKey; import com.mongodb.MongoException.Network; +import com.mongodb.MongoInternalException; + import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.document.UncategorizedDocumentStoreException; @@ -55,6 +58,9 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator if (ex instanceof MongoException) { return new UncategorizedDocumentStoreException(ex.getMessage(), ex); } + if (ex instanceof MongoInternalException) { + return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex); + } // If we get here, we have an exception that resulted from user code, // rather than the persistence provider, so we return null to indicate diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java index 18ecd6e81..be66de1ee 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java @@ -239,7 +239,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext // Set the ID PersistentProperty idProperty = entity.getIdProperty(); - if (dbo.containsField("_id") || null != idProperty) { + if (dbo.containsField("_id") && null != idProperty) { Object idObj = dbo.get("_id"); try { MappingBeanHelper.setProperty(instance, idProperty, idObj, useFieldAccessOnly); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/geo/Circle.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/geo/Circle.java new file mode 100644 index 000000000..b9a28cb59 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/geo/Circle.java @@ -0,0 +1,50 @@ +/* + * Copyright 2010-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.document.mongodb.geo; + +import java.util.Arrays; + +/** + * Represents a geospatial circle value + * @author Mark Pollack + * + */ +public class Circle { + + private double[] center; + private double radius; + + public Circle(double centerX, double centerY, double radius) { + this.center = new double[] { centerX, centerY }; + this.radius = radius; + } + + public double[] getCenter() { + return center; + } + + public double getRadius() { + return radius; + } + + @Override + public String toString() { + return "Circle [center=" + Arrays.toString(center) + ", radius=" + radius + + "]"; + } + + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Criteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Criteria.java index bdc4787ba..347aab417 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Criteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Criteria.java @@ -17,11 +17,13 @@ package org.springframework.data.document.mongodb.query; import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import org.springframework.data.document.InvalidDocumentStoreApiUsageException; +import org.springframework.data.document.mongodb.geo.Circle; public class Criteria implements CriteriaDefinition { @@ -225,6 +227,21 @@ public class Criteria implements CriteriaDefinition { criteria.put("$regex", re); return this; } + + + /** + * Creates a geospatial criterion using a $ + * @param circle + * @return + */ + public Criteria within(Circle circle) { + LinkedList list = new LinkedList(); + list.addLast(circle.getCenter()); + list.add(circle.getRadius()); + //BasicDBObject dbo = new BasicDBObject("$within", new BasicDBObject("$center", list)); + criteria.put("$within", new BasicDBObject("$center", list)); + return this; + } /** * Creates a criterion using the $elemMatch operator @@ -244,6 +261,8 @@ public class Criteria implements CriteriaDefinition { public void or(List queries) { criteria.put("$or", queries); } + + public String getKey() { return this.key; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/GeoSpatialTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/GeoSpatialTests.java index 0a7b9bc84..948ae04c9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/GeoSpatialTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/GeoSpatialTests.java @@ -1,30 +1,109 @@ +/* + * Copyright 2010-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.document.mongodb; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; + +import java.util.LinkedList; +import java.util.List; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.data.document.mongodb.geo.Circle; +import org.springframework.data.document.mongodb.query.Criteria; +import org.springframework.data.document.mongodb.query.GeospatialIndex; +import org.springframework.data.document.mongodb.query.Query; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import com.mongodb.BasicDBObject; +import com.mongodb.DBCollection; +import com.mongodb.DBObject; +import com.mongodb.MongoException; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:geospatial.xml") public class GeoSpatialTests { - @Autowired MongoTemplate template; @Before public void setUp() { template.dropCollection(template.getDefaultCollectionName()); + template.ensureIndex(new GeospatialIndex("location")); + addVenues(); + } + + private void addVenues() { + // Data taken from https://github.com/deftlabs/mongo-java-geospatial-example + 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("Washington Square Park", -73.99756, 40.73083)); + template.insert(new Venue("Ulaanbaatar, Mongolia", 106.9154, 47.9245)); + template.insert(new Venue("Maplewood, NJ", -74.2713, 40.73137)); + } + + @Test + public void withinCircle() { + + Circle circle = new Circle(-73.99171, 40.738868, 0.01); + List venues = template.find(new Query(Criteria.where("location").within(circle)), Venue.class); + assertThat(venues.size(), equalTo(8)); + } + + @Test + public void indexCreated() { + assertThat(template, notNullValue()); + Venue foundVenue = template.findOne( + new Query(Criteria.where("name").is("Penn Station")), Venue.class); + assertThat(foundVenue, notNullValue()); + List venues = template.getCollection(Venue.class); + assertThat(venues.size(), equalTo(13)); + + List indexInfo = getIndexInfo(); + assertThat(indexInfo.size(), equalTo(2)); + assertThat(indexInfo.get(1).get("name").toString(), equalTo("location_2d")); + assertThat(indexInfo.get(1).get("ns").toString(), + equalTo("geospatial.newyork")); + } + + // TODO move to MongoAdmin + public List getIndexInfo() { + return template.execute(new CollectionCallback>() { + + public List doInCollection(DBCollection collection) + throws MongoException, DataAccessException { + return collection.getIndexInfo(); + } + }); } - @Test - public void geoIndex() { - assertThat(template, notNullValue()); - } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/Venue.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/Venue.java new file mode 100644 index 000000000..c27c7c2cf --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/Venue.java @@ -0,0 +1,56 @@ +/* + * Copyright 2010-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.document.mongodb; + +import java.util.Arrays; + +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.document.mongodb.mapping.Document; + +@Document +public class Venue { + + private String id; + private String name; + private Double[] location; + + @PersistenceConstructor + Venue(String name, Double[] location) { + super(); + this.name = name; + this.location = location; + } + + public Venue(String name, double x, double y) { + super(); + this.name = name; + this.location = new Double[] { x, y }; + } + + public String getName() { + return name; + } + + public Double[] getLocation() { + return location; + } + + @Override + public String toString() { + return "Venue [id=" + id + ", name=" + name + ", location=" + + Arrays.toString(location) + "]"; + } +}