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.
This commit is contained in:
@@ -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<Object>() {
|
||||
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<WriteResult>() {
|
||||
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<WriteResult>() {
|
||||
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<Void>() {
|
||||
public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
|
||||
@@ -983,7 +983,7 @@ public class MongoTemplate implements MongoOperations, ApplicationEventPublisher
|
||||
protected <T> List<T> doFind(String collectionName, DBObject query, DBObject fields, Class<T> 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 <T> List<T> doFind(String collectionName, DBObject query, DBObject fields, Class<T> targetClass, MongoReader<T> 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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<Ma
|
||||
Assert.notNull(mongoTemplate);
|
||||
Assert.notNull(mappingContext);
|
||||
this.mongoTemplate = mongoTemplate;
|
||||
|
||||
|
||||
for (MongoPersistentEntity<?> entity : mappingContext.getPersistentEntities()) {
|
||||
checkForIndexes(entity);
|
||||
}
|
||||
@@ -114,29 +116,27 @@ public class MongoPersistentEntityIndexCreator implements ApplicationListener<Ma
|
||||
}
|
||||
}
|
||||
}
|
||||
String collection = index.collection();
|
||||
if ("".equals(collection)) {
|
||||
collection = entity.getCollection();
|
||||
}
|
||||
String collection = StringUtils.hasText(index.collection()) ? index.collection() : entity.getCollection();
|
||||
ensureIndex(collection, name, null, index.direction(), index.unique(), index.dropDups(), index.sparse());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Created property index " + index);
|
||||
}
|
||||
} else if (field.isAnnotationPresent(GeoSpatialIndexed.class)) {
|
||||
GeoSpatialIndexed index = field.getAnnotation(GeoSpatialIndexed.class);
|
||||
String name = index.name();
|
||||
if ("".equals(name)) {
|
||||
name = field.getName();
|
||||
}
|
||||
String collection = index.collection();
|
||||
if ("".equals(collection)) {
|
||||
collection = entity.getCollection();
|
||||
}
|
||||
ensureGeoIndex(collection, name, index.min(), index.max(), index.bits());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Created geo index " + index);
|
||||
}
|
||||
}
|
||||
} else if (field.isAnnotationPresent(GeoSpatialIndexed.class)) {
|
||||
|
||||
GeoSpatialIndexed index = field.getAnnotation(GeoSpatialIndexed.class);
|
||||
|
||||
GeospatialIndex indexObject = new GeospatialIndex(StringUtils.hasText(index.name()) ? index.name() : field
|
||||
.getName());
|
||||
indexObject.withMin(index.min()).withMax(index.max());
|
||||
|
||||
String collection = StringUtils.hasText(index.collection()) ? index.collection() : entity.getCollection();
|
||||
mongoTemplate.ensureIndex(collection, indexObject);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(String.format("Created %s for entity %s in collection %s! ", indexObject, entity.getType(),
|
||||
collection));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -172,32 +172,4 @@ public class MongoPersistentEntityIndexCreator implements ApplicationListener<Ma
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void ensureGeoIndex(String collection,
|
||||
final String name,
|
||||
final int min,
|
||||
final int max,
|
||||
final int bits) {
|
||||
mongoTemplate.execute(collection, new CollectionCallback<Object>() {
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Object> list = new LinkedList<Object>();
|
||||
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<Object> list = new LinkedList<Object>();
|
||||
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<double[]> list = new LinkedList<double[]>();
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ import org.springframework.data.repository.query.ParameterAccessor;
|
||||
*/
|
||||
public class ConvertingParameterAccessor implements ParameterAccessor {
|
||||
|
||||
// private static final Set<Class<?>> TYPES_NOT_TO_CONVERT = new HashSet<Class<?>>(Arrays.asList(Circle.class, Box.class))
|
||||
|
||||
private final MongoWriter<Object> 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<Object> {
|
||||
private class ConvertingIterator implements PotentiallyConvertingIterator {
|
||||
|
||||
private final Iterator<Object> 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<Object> {
|
||||
|
||||
/**
|
||||
* Returns the next element which has already been converted.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Object nextConverted();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Query, Query> {
|
||||
protected Query create(Part part, Iterator<Object> 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<Query, Query> {
|
||||
Iterator<Object> 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<Query, Query> {
|
||||
* @param parameters
|
||||
* @return
|
||||
*/
|
||||
private Criteria from(Type type, Criteria criteria,
|
||||
Iterator<Object> 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<Query, Query> {
|
||||
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 <T>
|
||||
* @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> T nextAs(Iterator<Object> iterator, Class<T> type) {
|
||||
Object parameter = iterator.next();
|
||||
if (parameter.getClass().isAssignableFrom(type)) {
|
||||
return (T) parameter;
|
||||
}
|
||||
|
||||
private Object[] nextAsArray(Iterator<Object> 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<Query, Query> {
|
||||
String regex = source.replaceAll("\\*", ".*");
|
||||
return Pattern.compile(regex);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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<T extends MongoRepository<S, ID>, S, ID
|
||||
*/
|
||||
private static class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTreeMongoQuery> {
|
||||
|
||||
private static final Set<Type> GEOSPATIAL_TYPES = new HashSet<Part.Type>(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<T extends MongoRepository<S, ID>, 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<T extends MongoRepository<S, ID>, 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) {
|
||||
|
||||
@@ -160,7 +160,7 @@ public class SimpleMongoRepository<T, ID extends Serializable> implements Paging
|
||||
*/
|
||||
public void deleteAll() {
|
||||
|
||||
template.dropCollection(entityInformation.getCollectionName());
|
||||
template.remove(entityInformation.getCollectionName(), new Query());
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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<Person> 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<Person> 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<Person> result = repository.findByLocationWithin(box);
|
||||
assertThat(result.size(), is(1));
|
||||
assertThat(result, hasItem(dave));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findsPagedPeopleByPredicate() throws Exception {
|
||||
|
||||
@@ -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<Address> 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
|
||||
|
||||
@@ -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<Person, String>, Query
|
||||
List<Person> findByLastnameLikeAndAgeBetween(String lastname, int from, int to);
|
||||
|
||||
List<Person> findByAgeOrLastnameLikeAndFirstnameLike(int age, String lastname, String firstname);
|
||||
|
||||
List<Person> findByLocationNear(Point point);
|
||||
|
||||
List<Person> findByLocationWithin(Circle circle);
|
||||
|
||||
List<Person> findByLocationWithin(Box box);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
<import resource="classpath:infrastructure.xml"/>
|
||||
<bean id="mongoTemplate" class="org.springframework.data.document.mongodb.MongoTemplate">
|
||||
<constructor-arg>
|
||||
<bean id="mongo" class="org.springframework.data.document.mongodb.MongoFactoryBean">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="port" value="27017"/>
|
||||
</bean>
|
||||
</constructor-arg>
|
||||
<constructor-arg value="repositories" />
|
||||
<constructor-arg>
|
||||
<bean id="mongoConverter" class="org.springframework.data.document.mongodb.convert.MappingMongoConverter">
|
||||
<constructor-arg ref="mappingContext" />
|
||||
</bean>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<bean id="mappingContext" class="org.springframework.data.document.mongodb.mapping.MongoMappingContext" />
|
||||
|
||||
<bean class="org.springframework.data.document.mongodb.mapping.MongoPersistentEntityIndexCreator">
|
||||
<constructor-arg ref="mappingContext" />
|
||||
<constructor-arg ref="mongoTemplate" />
|
||||
</bean>
|
||||
|
||||
<bean class="org.springframework.data.document.mongodb.repository.MongoRepositoryFactoryBean">
|
||||
<property name="template" ref="mongoTemplate"/>
|
||||
<property name="mappingContext" ref="mappingContext" />
|
||||
<property name="repositoryInterface" value="org.springframework.data.document.mongodb.repository.PersonRepository"/>
|
||||
</bean>
|
||||
|
||||
<bean id="mappingContext" class="org.springframework.data.document.mongodb.mapping.MongoMappingContext" />
|
||||
|
||||
<bean id="mongoConverter" class="org.springframework.data.document.mongodb.convert.MappingMongoConverter">
|
||||
<constructor-arg ref="mappingContext" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<bean id="mongoTemplate" class="org.springframework.data.document.mongodb.MongoTemplate">
|
||||
<constructor-arg ref="mongo" />
|
||||
<constructor-arg value="database" />
|
||||
<constructor-arg value="repositories" />
|
||||
<constructor-arg>
|
||||
<mongo:mapping-converter />
|
||||
</constructor-arg>
|
||||
|
||||
Reference in New Issue
Block a user