From 7b3d030794f0be3ffb575e5fb4e037568ac12546 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 3 May 2011 16:33:24 +0200 Subject: [PATCH] DATADOC-43 - Implemented Near and Within for repository queries. Refactored spatial domain classes to use each other a bit more. Added assertions to fail fast on invalid usage. Improved Geospatial index creation in MongoPersistentEntityIndexCreator by using the index abstraction instead of manually building the DBObject. Fixed implementation of SimpleMongoRepository.deleteAll() to not drop the collection as this causes indexes to be dropped as well. Skip index creation from query methods for now if we encounter a Near or Within part as we can't build combined queries right now. --- .../data/document/mongodb/MongoTemplate.java | 14 +- .../data/document/mongodb/geo/Box.java | 122 ++++++++---------- .../data/document/mongodb/geo/Circle.java | 23 ++-- .../data/document/mongodb/geo/Point.java | 40 +++--- .../mongodb/index/GeoSpatialIndexed.java | 4 +- .../MongoPersistentEntityIndexCreator.java | 68 +++------- .../data/document/mongodb/query/Criteria.java | 46 +++---- .../mongodb/query/GeospatialIndex.java | 20 ++- .../ConvertingParameterAccessor.java | 31 ++++- .../mongodb/repository/MongoQueryCreator.java | 59 +++++++-- .../MongoRepositoryFactoryBean.java | 10 +- .../repository/SimpleMongoRepository.java | 2 +- ...tractPersonRepositoryIntegrationTests.java | 40 ++++++ .../document/mongodb/repository/Person.java | 23 +++- .../mongodb/repository/PersonRepository.java | 9 ++ .../StringBasedMongoQueryUnitTests.java | 27 +++- ...rsonRepositoryIntegrationTests-context.xml | 28 +++- ...MongoNamespaceIntegrationTests-context.xml | 2 +- 18 files changed, 363 insertions(+), 205 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java index 56bb403ea..fe9630a04 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java @@ -683,7 +683,7 @@ public class MongoTemplate implements MongoOperations, ApplicationEventPublisher } } if (LOGGER.isDebugEnabled()) { - LOGGER.debug("insert DBObject containing fields: " + dbDoc.keySet() + " in collecion: " + collectionName); + LOGGER.debug("insert DBObject containing fields: " + dbDoc.keySet() + " in collection: " + collectionName); } return execute(collectionName, new CollectionCallback() { public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { @@ -783,7 +783,7 @@ public class MongoTemplate implements MongoOperations, ApplicationEventPublisher */ public WriteResult updateFirst(String collectionName, final Query query, final Update update) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("calling update using query: " + query.getQueryObject() + " and update: " + update.getUpdateObject(mongoConverter) + " in collecion: " + collectionName); + LOGGER.debug("calling update using query: " + query.getQueryObject() + " and update: " + update.getUpdateObject(mongoConverter) + " in collection: " + collectionName); } return execute(collectionName, new CollectionCallback() { public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { @@ -813,7 +813,7 @@ public class MongoTemplate implements MongoOperations, ApplicationEventPublisher */ public WriteResult updateMulti(String collectionName, final Query query, final Update update) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("calling updateMulti using query: " + query.getQueryObject() + " and update: " + update.getUpdateObject(mongoConverter) + " in collecion: " + collectionName); + LOGGER.debug("calling updateMulti using query: " + query.getQueryObject() + " and update: " + update.getUpdateObject(mongoConverter) + " in collection: " + collectionName); } return execute(collectionName, new CollectionCallback() { public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { @@ -858,7 +858,7 @@ public class MongoTemplate implements MongoOperations, ApplicationEventPublisher final DBObject queryObject = query.getQueryObject(); final PersistentEntity entity = getPersistentEntity(targetClass); if (LOGGER.isDebugEnabled()) { - LOGGER.debug("remove using query: " + queryObject + " in collecion: " + collectionName); + LOGGER.debug("remove using query: " + queryObject + " in collection: " + collectionName); } execute(collectionName, new CollectionCallback() { public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { @@ -983,7 +983,7 @@ public class MongoTemplate implements MongoOperations, ApplicationEventPublisher protected List doFind(String collectionName, DBObject query, DBObject fields, Class targetClass, CursorPreparer preparer) { PersistentEntity entity = mappingContext.getPersistentEntity(targetClass); if (LOGGER.isDebugEnabled()) { - LOGGER.debug("find using query: " + query + " fields: " + fields + " for class: " + targetClass + " in collecion: " + collectionName); + LOGGER.debug("find using query: " + query + " fields: " + fields + " for class: " + targetClass + " in collection: " + collectionName); } return executeEach(new FindCallback(mapper.getMappedObject(query, entity), fields), preparer, @@ -1005,7 +1005,7 @@ public class MongoTemplate implements MongoOperations, ApplicationEventPublisher */ protected List doFind(String collectionName, DBObject query, DBObject fields, Class targetClass, MongoReader reader) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("find using query: " + query + " fields: " + fields + " for class: " + targetClass + " in collecion: " + collectionName); + LOGGER.debug("find using query: " + query + " fields: " + fields + " for class: " + targetClass + " in collection: " + collectionName); } PersistentEntity entity = mappingContext.getPersistentEntity(targetClass); return executeEach(new FindCallback(mapper.getMappedObject(query, entity), fields), @@ -1048,7 +1048,7 @@ public class MongoTemplate implements MongoOperations, ApplicationEventPublisher readerToUse = this.mongoConverter; } if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findAndRemove using query: " + query + " fields: " + fields + " sort: " + sort + " for class: " + targetClass + " in collecion: " + collectionName); + LOGGER.debug("findAndRemove using query: " + query + " fields: " + fields + " sort: " + sort + " for class: " + targetClass + " in collection: " + collectionName); } PersistentEntity entity = mappingContext.getPersistentEntity(targetClass); return execute(new FindAndRemoveCallback(mapper.getMappedObject(query, entity), fields, sort), diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/geo/Box.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/geo/Box.java index 0035b7663..b51cfb926 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/geo/Box.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/geo/Box.java @@ -15,83 +15,67 @@ */ package org.springframework.data.document.mongodb.geo; +import org.springframework.util.Assert; + /** * Represents a geospatial box value + * * @author Mark Pollack - * + * @author Oliver Gierke */ public class Box { - - private double xmin; - - private double ymin; - - private double xmax; - - private double ymax; - - public Box(Point lowerLeft, Point upperRight) { - xmin = lowerLeft.getX(); - ymin = lowerLeft.getY(); - xmax = upperRight.getX(); - ymax = upperRight.getY(); - } - - public Box(double[] lowerLeft, double[] upperRight) { - xmin = lowerLeft[0]; - ymin = lowerLeft[1]; - xmax = upperRight[0]; - ymax = upperRight[1]; - } - - public Point getLowerLeft() { - return new Point(xmin, ymin); - } - - public Point getUpperRight() { - return new Point(xmax, ymax); - } - @Override - public String toString() { - return "Box [xmin=" + xmin + ", ymin=" + ymin + ", xmax=" + xmax - + ", ymax=" + ymax + "]"; - } + private final Point first; + private final Point second; - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - long temp; - temp = Double.doubleToLongBits(xmax); - result = prime * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(xmin); - result = prime * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(ymax); - result = prime * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(ymin); - result = prime * result + (int) (temp ^ (temp >>> 32)); - return result; - } + public Box(Point lowerLeft, Point upperRight) { + Assert.notNull(lowerLeft); + Assert.notNull(upperRight); + this.first = lowerLeft; + this.second = upperRight; + } - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Box other = (Box) obj; - if (Double.doubleToLongBits(xmax) != Double.doubleToLongBits(other.xmax)) - return false; - if (Double.doubleToLongBits(xmin) != Double.doubleToLongBits(other.xmin)) - return false; - if (Double.doubleToLongBits(ymax) != Double.doubleToLongBits(other.ymax)) - return false; - if (Double.doubleToLongBits(ymin) != Double.doubleToLongBits(other.ymin)) - return false; + public Box(double[] lowerLeft, double[] upperRight) { + Assert.isTrue(lowerLeft.length == 2, "Point array has to have 2 elements!"); + Assert.isTrue(upperRight.length == 2, "Point array has to have 2 elements!"); + this.first = new Point(lowerLeft[0], lowerLeft[1]); + this.second = new Point(upperRight[0], upperRight[1]); + } + + public Point getLowerLeft() { + return first; + } + + public Point getUpperRight() { + return second; + } + + @Override + public String toString() { + return String.format("Box [%s, %s]", first, second); + } + + @Override + public int hashCode() { + + int result = 31; + result += 17 * first.hashCode(); + result += 17 * second.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { return true; } - + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Box that = (Box) obj; + return this.first.equals(that.first) && this.second.equals(that.second); + } } 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 index b9a28cb59..05ddaf7d4 100644 --- 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 @@ -15,24 +15,30 @@ */ package org.springframework.data.document.mongodb.geo; -import java.util.Arrays; +import org.springframework.util.Assert; /** * Represents a geospatial circle value * @author Mark Pollack - * + * @author Oliver Gierke */ public class Circle { - private double[] center; + private Point center; private double radius; - public Circle(double centerX, double centerY, double radius) { - this.center = new double[] { centerX, centerY }; + public Circle(Point center, double radius) { + Assert.notNull(center); + Assert.isTrue(radius >= 0, "Radius must not be negative!"); + this.center = center; this.radius = radius; } + + public Circle(double centerX, double centerY, double radius) { + this(new Point(centerX, centerY), radius); + } - public double[] getCenter() { + public Point getCenter() { return center; } @@ -42,9 +48,6 @@ public class Circle { @Override public String toString() { - return "Circle [center=" + Arrays.toString(center) + ", radius=" + radius - + "]"; + return String.format("Circle [center=%s, radius=%d]", center, radius); } - - } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/geo/Point.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/geo/Point.java index 203b292d3..451bc277a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/geo/Point.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/geo/Point.java @@ -15,23 +15,28 @@ */ package org.springframework.data.document.mongodb.geo; +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.util.Assert; + /** - * Represents a geospatial point value + * Represents a geospatial point value. + * * @author Mark Pollack - * + * @author Oliver Gierke */ public class Point { - private double x; + private final double x; + private final double y; - private double y; - + @PersistenceConstructor public Point(double x, double y) { this.x = x; this.y = y; } public Point(Point point) { + Assert.notNull(point); this.x = point.x; this.y = point.y; } @@ -43,6 +48,10 @@ public class Point { public double getY() { return y; } + + public double[] asArray() { + return new double[] {x, y}; + } @Override public int hashCode() { @@ -58,28 +67,29 @@ public class Point { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } Point other = (Point) obj; if (Double.doubleToLongBits(x) != Double - .doubleToLongBits(other.x)) + .doubleToLongBits(other.x)) { return false; + } if (Double.doubleToLongBits(y) != Double - .doubleToLongBits(other.y)) + .doubleToLongBits(other.y)) { return false; + } return true; } @Override public String toString() { - return "Point [latitude=" + x + ", longitude=" + y + "]"; + return String.format("Point [latitude=%d, longitude=%d]", x, y); } - - - - } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/GeoSpatialIndexed.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/GeoSpatialIndexed.java index fdb067f2d..613983355 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/GeoSpatialIndexed.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/GeoSpatialIndexed.java @@ -49,14 +49,14 @@ public @interface GeoSpatialIndexed { * * @return */ - int min() default 0; + int min() default -180; /** * Maximum value for indexed values. * * @return */ - int max() default 0; + int max() default 180; /** * Bits of precision for boundary calculations. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentEntityIndexCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentEntityIndexCreator.java index e714068fd..6a5ac5b52 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentEntityIndexCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentEntityIndexCreator.java @@ -37,10 +37,12 @@ import org.springframework.data.document.mongodb.index.CompoundIndexes; import org.springframework.data.document.mongodb.index.GeoSpatialIndexed; import org.springframework.data.document.mongodb.index.IndexDirection; import org.springframework.data.document.mongodb.index.Indexed; +import org.springframework.data.document.mongodb.query.GeospatialIndex; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.event.MappingContextEvent; import org.springframework.data.mapping.model.PersistentProperty; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Component that inspects {@link BasicMongoPersistentEntity} instances contained in the given {@link MongoMappingContext} @@ -62,7 +64,7 @@ public class MongoPersistentEntityIndexCreator implements ApplicationListener entity : mappingContext.getPersistentEntities()) { checkForIndexes(entity); } @@ -114,29 +116,27 @@ public class MongoPersistentEntityIndexCreator implements ApplicationListener() { - public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - DBObject defObj = new BasicDBObject(); - defObj.put(name, "2d"); - - DBObject opts = new BasicDBObject(); - // Min - if (min != 0) { - opts.put("min", min); - } - // Max - if (max != 0) { - opts.put("max", max); - } - // Bits - opts.put("bits", bits); - collection.ensureIndex(defObj, opts); - return null; - } - }); - } - } 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 76a89dd83..3a5209064 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 @@ -26,6 +26,7 @@ import org.springframework.data.document.InvalidDocumentStoreApiUsageException; import org.springframework.data.document.mongodb.geo.Box; import org.springframework.data.document.mongodb.geo.Circle; import org.springframework.data.document.mongodb.geo.Point; +import org.springframework.util.Assert; public class Criteria implements CriteriaDefinition { @@ -246,65 +247,66 @@ public class Criteria implements CriteriaDefinition { /** * Creates a geospatial criterion using a $within $center operation - * - * @param circle + * @param circle must not be {@literal null} * @return */ public Criteria withinCenter(Circle circle) { - LinkedList list = new LinkedList(); - list.addLast(circle.getCenter()); + Assert.notNull(circle); + LinkedList list = new LinkedList(); + list.addLast(circle.getCenter().asArray()); list.add(circle.getRadius()); criteria.put("$within", new BasicDBObject("$center", list)); return this; } - + /** - * Creates a geospatial criterion using a $within $center operation. This is only available for Mongo 1.7 and higher. - * - * @param circle + * Creates a geospatial criterion using a $within $center operation. This is only available for Mongo 1.7 and higher. + * @param circle must not be {@literal null} * @return */ public Criteria withinCenterSphere(Circle circle) { - LinkedList list = new LinkedList(); - list.addLast(circle.getCenter()); + Assert.notNull(circle); + LinkedList list = new LinkedList(); + list.addLast(circle.getCenter().asArray()); list.add(circle.getRadius()); criteria.put("$within", new BasicDBObject("$centerSphere", list)); return this; } - + /** * Creates a geospatial criterion using a $within $box operation * * @param circle * @return */ - public Criteria withinBox(Box box) { + public Criteria withinBox(Box box){ + Assert.notNull(box); LinkedList list = new LinkedList(); - list.addLast(new double[] { box.getLowerLeft().getX(), box.getLowerLeft().getY() }); - list.addLast(new double[] { box.getUpperRight().getX(), box.getUpperRight().getY() }); + list.addLast(box.getLowerLeft().asArray()); + list.addLast(box.getUpperRight().asArray()); criteria.put("$within", new BasicDBObject("$box", list)); return this; } - + /** * Creates a geospatial criterion using a $near operation - * - * @param point + * @param point must not be {@literal null} * @return */ public Criteria near(Point point) { - criteria.put("$near", new double[] { point.getX(), point.getY() }); + Assert.notNull(point); + criteria.put("$near", point.asArray()); return this; } /** - * Creates a geospatial criterion using a $nearSphere operation. This is only available for Mongo 1.7 and higher. - * - * @param point + * Creates a geospatial criterion using a $nearSphere operation. This is only available for Mongo 1.7 and higher. + * @param point must not be {@literal null} * @return */ public Criteria nearSphere(Point point) { - criteria.put("$nearSphere", new double[] { point.getX(), point.getY() }); + Assert.notNull(point); + criteria.put("$nearSphere", point.asArray()); return this; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/GeospatialIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/GeospatialIndex.java index e37ad94e8..5a6b27746 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/GeospatialIndex.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/GeospatialIndex.java @@ -30,9 +30,8 @@ public class GeospatialIndex implements IndexDefinition { private Integer min = null; private Integer max = null; - - public GeospatialIndex() { - } + + private Integer bits = null; public GeospatialIndex(String key) { keyField = key; @@ -53,6 +52,11 @@ public class GeospatialIndex implements IndexDefinition { return this; } + public GeospatialIndex withBits(int bits) { + this.bits = Integer.valueOf(bits); + return this; + } + public DBObject getIndexKeys() { DBObject dbo = new BasicDBObject(); dbo.put(keyField, "2d"); @@ -73,7 +77,17 @@ public class GeospatialIndex implements IndexDefinition { if (max != null) { dbo.put("max", max); } + if (bits != null) { + dbo.put("bits", bits); + } return dbo; } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("Geo index: %s - Options: %s", getIndexKeys(), getIndexOptions()); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/ConvertingParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/ConvertingParameterAccessor.java index 259c97b3b..aa55babba 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/ConvertingParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/ConvertingParameterAccessor.java @@ -33,6 +33,8 @@ import org.springframework.data.repository.query.ParameterAccessor; */ public class ConvertingParameterAccessor implements ParameterAccessor { +// private static final Set> TYPES_NOT_TO_CONVERT = new HashSet>(Arrays.asList(Circle.class, Box.class)) + private final MongoWriter writer; private final ParameterAccessor delegate; @@ -88,7 +90,7 @@ public class ConvertingParameterAccessor implements ParameterAccessor { * @return */ private Object getConvertedValue(Object value) { - + DBObject result = new BasicDBObject(); writer.write(value.getClass().isEnum() ? new EnumValueHolder((Enum) value) : new ValueHolder(value), result); return ((DBObject) result.get("value")).get("value"); @@ -99,7 +101,7 @@ public class ConvertingParameterAccessor implements ParameterAccessor { * * @author Oliver Gierke */ - private class ConvertingIterator implements Iterator { + private class ConvertingIterator implements PotentiallyConvertingIterator { private final Iterator delegate; @@ -128,7 +130,15 @@ public class ConvertingParameterAccessor implements ParameterAccessor { */ public Object next() { - return getConvertedValue(delegate.next()); + return delegate.next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.repository.ConvertingParameterAccessor.PotentiallConvertingIterator#nextConverted() + */ + public Object nextConverted() { + + return getConvertedValue(next()); } /* @@ -178,4 +188,19 @@ public class ConvertingParameterAccessor implements ParameterAccessor { return value; } } + + /** + * Custom {@link Iterator} that adds a method to access elements in a converted manner. + * + * @author Oliver Gierke + */ + public interface PotentiallyConvertingIterator extends Iterator { + + /** + * Returns the next element which has already been converted. + * + * @return + */ + Object nextConverted(); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryCreator.java index 23fbbac74..1497821de 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryCreator.java @@ -23,9 +23,13 @@ import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.data.document.mongodb.geo.Box; +import org.springframework.data.document.mongodb.geo.Circle; +import org.springframework.data.document.mongodb.geo.Point; import org.springframework.data.document.mongodb.query.Criteria; import org.springframework.data.document.mongodb.query.CriteriaDefinition; import org.springframework.data.document.mongodb.query.Query; +import org.springframework.data.document.mongodb.repository.ConvertingParameterAccessor.PotentiallyConvertingIterator; import org.springframework.data.domain.Sort; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParametersParameterAccessor; @@ -66,7 +70,7 @@ class MongoQueryCreator extends AbstractQueryCreator { protected Query create(Part part, Iterator iterator) { Criteria criteria = from(part.getType(), - where(part.getProperty().toDotPath()), iterator); + where(part.getProperty().toDotPath()), (PotentiallyConvertingIterator) iterator); return new Query(criteria); } @@ -81,7 +85,7 @@ class MongoQueryCreator extends AbstractQueryCreator { Iterator iterator) { Criteria criteria = from(part.getType(), where(part.getProperty().toDotPath()), - iterator); + (PotentiallyConvertingIterator) iterator); return base.addCriteria(criteria); } @@ -126,17 +130,15 @@ class MongoQueryCreator extends AbstractQueryCreator { * @param parameters * @return */ - private Criteria from(Type type, Criteria criteria, - Iterator parameters) { + private Criteria from(Type type, Criteria criteria, PotentiallyConvertingIterator parameters) { switch (type) { case GREATER_THAN: - return criteria.gt(parameters.next()); + return criteria.gt(parameters.nextConverted()); case LESS_THAN: - return criteria.lt(parameters.next()); + return criteria.lt(parameters.nextConverted()); case BETWEEN: - return criteria.gt(parameters.next()).lt( - parameters.next()); + return criteria.gt(parameters.nextConverted()).lt(parameters.nextConverted()); case IS_NOT_NULL: return criteria.not().is(null); case IS_NULL: @@ -148,18 +150,49 @@ class MongoQueryCreator extends AbstractQueryCreator { case LIKE: String value = parameters.next().toString(); return criteria.is(toLikeRegex(value)); + case NEAR: + return criteria.near(nextAs(parameters, Point.class)); + case WITHIN: + + Object parameter = parameters.next(); + if (parameter instanceof Box) { + return criteria.withinBox((Box) parameter); + } else if (parameter instanceof Circle) { + return criteria.withinCenter((Circle) parameter); + } + throw new IllegalArgumentException("Parameter has to be either Box or Circle!"); case SIMPLE_PROPERTY: - return criteria.is(parameters.next()); + return criteria.is(parameters.nextConverted()); case NEGATING_SIMPLE_PROPERTY: - return criteria.not().is(parameters.next()); + return criteria.not().is(parameters.nextConverted()); } throw new IllegalArgumentException("Unsupported keyword!"); } + /** + * Returns the next element from the given {@link Iterator} expecting it to be of a certain type. + * + * @param + * @param iterator + * @param type + * @throws IllegalArgumentException + * in case the next element in the iterator is not of the given type. + * @return + */ + @SuppressWarnings("unchecked") + private T nextAs(Iterator iterator, Class type) { + Object parameter = iterator.next(); + if (parameter.getClass().isAssignableFrom(type)) { + return (T) parameter; + } - private Object[] nextAsArray(Iterator iterator) { - Object next = iterator.next(); + throw new IllegalArgumentException(String.format("Expected parameter type of %s but got %s!", type, + parameter.getClass())); + } + + private Object[] nextAsArray(PotentiallyConvertingIterator iterator) { + Object next = iterator.nextConverted(); if (next instanceof Collection) { return ((Collection) next).toArray(); @@ -175,6 +208,4 @@ class MongoQueryCreator extends AbstractQueryCreator { String regex = source.replaceAll("\\*", ".*"); return Pattern.compile(regex); } - - } \ No newline at end of file diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepositoryFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepositoryFactoryBean.java index 9fba28980..8f3da0a76 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepositoryFactoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepositoryFactoryBean.java @@ -19,6 +19,9 @@ import static org.springframework.data.querydsl.QueryDslUtils.*; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -34,6 +37,7 @@ import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.data.repository.support.QueryCreationListener; import org.springframework.data.repository.support.RepositoryFactoryBeanSupport; @@ -264,6 +268,7 @@ public class MongoRepositoryFactoryBean, S, ID */ private static class IndexEnsuringQueryCreationListener implements QueryCreationListener { + private static final Set GEOSPATIAL_TYPES = new HashSet(Arrays.asList(Type.NEAR, Type.WITHIN)); private static final Log LOG = LogFactory.getLog(IndexEnsuringQueryCreationListener.class); private final MongoOperations operations; @@ -288,6 +293,9 @@ public class MongoRepositoryFactoryBean, S, ID Sort sort = tree.getSort(); for (Part part : tree.getParts()) { + if (GEOSPATIAL_TYPES.contains(part.getType())) { + return; + } String property = part.getProperty().toDotPath(); Order order = toOrder(sort, property); index.on(property, order); @@ -295,7 +303,7 @@ public class MongoRepositoryFactoryBean, S, ID MongoEntityInformation metadata = query.getQueryMethod().getEntityInformation(); operations.ensureIndex(metadata.getCollectionName(), index); - LOG.debug(String.format("Created index %s!", index.toString())); + LOG.debug(String.format("Created %s!", index)); } private static Order toOrder(Sort sort, String property) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java index 9e29653be..8b1a88b3c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java @@ -160,7 +160,7 @@ public class SimpleMongoRepository implements Paging */ public void deleteAll() { - template.dropCollection(entityInformation.getCollectionName()); + template.remove(entityInformation.getCollectionName(), new Query()); } /* diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 1a9ceab5d..e434991af 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -9,9 +9,13 @@ import java.util.HashSet; import java.util.List; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.document.mongodb.geo.Box; +import org.springframework.data.document.mongodb.geo.Circle; +import org.springframework.data.document.mongodb.geo.Point; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort.Direction; @@ -175,6 +179,42 @@ public abstract class AbstractPersonRepositoryIntegrationTests { assertThat(result.size(), is(1)); assertThat(result, hasItem(dave)); } + + @Test + public void findsPeopleByLocationNear() { + Point point = new Point(-73.99171, 40.738868); + dave.setLocation(point); + repository.save(dave); + + List result = repository.findByLocationNear(point); + assertThat(result.size(), is(1)); + assertThat(result, hasItem(dave)); + } + + @Test + public void findsPeopleByLocationWithinCircle() { + Point point = new Point(-73.99171, 40.738868); + dave.setLocation(point); + repository.save(dave); + + List result = repository.findByLocationWithin(new Circle(-78.99171, 45.738868, 170)); + assertThat(result.size(), is(1)); + assertThat(result, hasItem(dave)); + } + + @Test + @Ignore + public void findsPeopleByLocationWithinBox() { + Point point = new Point(-73.99171, 40.738868); + dave.setLocation(point); + repository.save(dave); + + Box box = new Box(new Point(-78.99171, 35.738868), new Point(-68.99171, 45.738868)); + + List result = repository.findByLocationWithin(box); + assertThat(result.size(), is(1)); + assertThat(result, hasItem(dave)); + } @Test public void findsPagedPeopleByPredicate() throws Exception { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java index e26460bda..427246b6e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java @@ -18,6 +18,9 @@ package org.springframework.data.document.mongodb.repository; import java.util.Set; import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.document.mongodb.geo.Point; +import org.springframework.data.document.mongodb.index.GeoSpatialIndexed; import org.springframework.data.document.mongodb.mapping.Document; @@ -29,10 +32,14 @@ import org.springframework.data.document.mongodb.mapping.Document; @Document public class Person { + @Id private String id; private String firstname; private String lastname; private Integer age; + + @GeoSpatialIndexed + private Point location; private Address address; private Set
shippingAddresses; @@ -52,7 +59,7 @@ public class Person { public Person(String firstname, String lastname, Integer age) { - this.id = ObjectId.get().toString(); + this.id = new ObjectId().toString(); this.firstname = firstname; this.lastname = lastname; this.age = age; @@ -130,6 +137,20 @@ public class Person { this.age = age; } + + /** + * @return the location + */ + public Point getLocation() { + return location; + } + + /** + * @param location the location to set + */ + public void setLocation(Point location) { + this.location = location; + } /** * @return the address diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java index 564a5890c..f0e47c53d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java @@ -18,6 +18,9 @@ package org.springframework.data.document.mongodb.repository; import java.util.Collection; import java.util.List; +import org.springframework.data.document.mongodb.geo.Box; +import org.springframework.data.document.mongodb.geo.Circle; +import org.springframework.data.document.mongodb.geo.Point; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -117,4 +120,10 @@ public interface PersonRepository extends MongoRepository, Query List findByLastnameLikeAndAgeBetween(String lastname, int from, int to); List findByAgeOrLastnameLikeAndFirstnameLike(int age, String lastname, String firstname); + + List findByLocationNear(Point point); + + List findByLocationWithin(Circle circle); + + List findByLocationWithin(Box box); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQueryUnitTests.java index 847300576..f60845145 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQueryUnitTests.java @@ -33,6 +33,9 @@ import org.springframework.data.document.mongodb.query.BasicQuery; import org.springframework.data.document.mongodb.repository.MongoRepositoryFactoryBean.EntityInformationCreator; import org.springframework.data.repository.support.RepositoryMetadata; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + /** * Unit tests for {@link StringBasedMongoQuery}. * @@ -56,7 +59,7 @@ public class StringBasedMongoQueryUnitTests { } @Test - public void testname() throws Exception { + public void bindsSimplePropertyCorrectly() throws Exception { Method method = SampleRepository.class.getMethod("findByLastname", String.class); MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, creator); @@ -68,10 +71,32 @@ public class StringBasedMongoQueryUnitTests { assertThat(query.getQueryObject(), is(reference.getQueryObject())); } + + @Test + public void bindsComplexPropertyCorrectly() throws Exception { + + Method method = SampleRepository.class.getMethod("findByAddress", Address.class); + MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, creator); + StringBasedMongoQuery mongoQuery = new StringBasedMongoQuery(queryMethod, template); + + Address address = new Address("Foo", "0123", "Bar"); + ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, address); + + DBObject dbObject = new BasicDBObject(); + converter.write(address, dbObject); + + org.springframework.data.document.mongodb.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.document.mongodb.query.Query reference = new BasicQuery(new BasicDBObject("address", dbObject)); + + assertThat(query.getQueryObject(), is(reference.getQueryObject())); + } private interface SampleRepository { @Query("{ 'lastname' : ?0 }") Person findByLastname(String lastname); + + @Query("{ 'address' : ?0 }") + Person findByAddress(Address address); } } diff --git a/spring-data-mongodb/src/test/resources/org/springframework/data/document/mongodb/repository/PersonRepositoryIntegrationTests-context.xml b/spring-data-mongodb/src/test/resources/org/springframework/data/document/mongodb/repository/PersonRepositoryIntegrationTests-context.xml index 824b82942..1f8dc51c9 100644 --- a/spring-data-mongodb/src/test/resources/org/springframework/data/document/mongodb/repository/PersonRepositoryIntegrationTests-context.xml +++ b/spring-data-mongodb/src/test/resources/org/springframework/data/document/mongodb/repository/PersonRepositoryIntegrationTests-context.xml @@ -3,18 +3,32 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> - + + + + + + + + + + + + + + + + + + + + + - - - - - - diff --git a/spring-data-mongodb/src/test/resources/org/springframework/data/document/mongodb/repository/config/MongoNamespaceIntegrationTests-context.xml b/spring-data-mongodb/src/test/resources/org/springframework/data/document/mongodb/repository/config/MongoNamespaceIntegrationTests-context.xml index bec490e13..9d0ff16a1 100644 --- a/spring-data-mongodb/src/test/resources/org/springframework/data/document/mongodb/repository/config/MongoNamespaceIntegrationTests-context.xml +++ b/spring-data-mongodb/src/test/resources/org/springframework/data/document/mongodb/repository/config/MongoNamespaceIntegrationTests-context.xml @@ -11,7 +11,7 @@ - +