diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java index c6c395f1c..8b7f0f1b4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,9 +30,18 @@ import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; import org.springframework.data.geo.Shape; +import org.springframework.data.mongodb.core.geo.GeoJson; +import org.springframework.data.mongodb.core.geo.GeoJsonGeometryCollection; +import org.springframework.data.mongodb.core.geo.GeoJsonLineString; +import org.springframework.data.mongodb.core.geo.GeoJsonMultiLineString; +import org.springframework.data.mongodb.core.geo.GeoJsonMultiPoint; +import org.springframework.data.mongodb.core.geo.GeoJsonMultiPolygon; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; +import org.springframework.data.mongodb.core.geo.GeoJsonPolygon; import org.springframework.data.mongodb.core.geo.Sphere; import org.springframework.data.mongodb.core.query.GeoCommand; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; @@ -43,6 +52,7 @@ import com.mongodb.DBObject; * * @author Thomas Darimont * @author Oliver Gierke + * @author Christoph Strobl * @since 1.5 */ abstract class GeoConverters { @@ -69,7 +79,17 @@ abstract class GeoConverters { , DbObjectToSphereConverter.INSTANCE // , DbObjectToPointConverter.INSTANCE // , PointToDbObjectConverter.INSTANCE // - , GeoCommandToDbObjectConverter.INSTANCE); + , GeoCommandToDbObjectConverter.INSTANCE // + , GeoJsonToDbObjectConverter.INSTANCE // + , GeoJsonPointToDbObjectConverter.INSTANCE // + , GeoJsonPolygonToDbObjectConverter.INSTANCE // + , DbObjectToGeoJsonPointConverter.INSTANCE // + , DbObjectToGeoJsonPolygonConverter.INSTANCE // + , DbObjectToGeoJsonLineStringConverter.INSTANCE // + , DbObjectToGeoJsonMultiLineStringConverter.INSTANCE // + , DbObjectToGeoJsonMultiPointConverter.INSTANCE // + , DbObjectToGeoJsonMultiPolygonConverter.INSTANCE // + , DbObjectToGeoJsonGeometryCollectionConverter.INSTANCE); } /** @@ -79,7 +99,7 @@ abstract class GeoConverters { * @since 1.5 */ @ReadingConverter - public static enum DbObjectToPointConverter implements Converter { + static enum DbObjectToPointConverter implements Converter { INSTANCE; @@ -102,7 +122,7 @@ abstract class GeoConverters { * @author Thomas Darimont * @since 1.5 */ - public static enum PointToDbObjectConverter implements Converter { + static enum PointToDbObjectConverter implements Converter { INSTANCE; @@ -123,7 +143,7 @@ abstract class GeoConverters { * @since 1.5 */ @WritingConverter - public static enum BoxToDbObjectConverter implements Converter { + static enum BoxToDbObjectConverter implements Converter { INSTANCE; @@ -152,7 +172,7 @@ abstract class GeoConverters { * @since 1.5 */ @ReadingConverter - public static enum DbObjectToBoxConverter implements Converter { + static enum DbObjectToBoxConverter implements Converter { INSTANCE; @@ -180,7 +200,7 @@ abstract class GeoConverters { * @author Thomas Darimont * @since 1.5 */ - public static enum CircleToDbObjectConverter implements Converter { + static enum CircleToDbObjectConverter implements Converter { INSTANCE; @@ -210,7 +230,7 @@ abstract class GeoConverters { * @since 1.5 */ @ReadingConverter - public static enum DbObjectToCircleConverter implements Converter { + static enum DbObjectToCircleConverter implements Converter { INSTANCE; @@ -251,7 +271,7 @@ abstract class GeoConverters { * @author Thomas Darimont * @since 1.5 */ - public static enum SphereToDbObjectConverter implements Converter { + static enum SphereToDbObjectConverter implements Converter { INSTANCE; @@ -281,7 +301,7 @@ abstract class GeoConverters { * @since 1.5 */ @ReadingConverter - public static enum DbObjectToSphereConverter implements Converter { + static enum DbObjectToSphereConverter implements Converter { INSTANCE; @@ -322,7 +342,7 @@ abstract class GeoConverters { * @author Thomas Darimont * @since 1.5 */ - public static enum PolygonToDbObjectConverter implements Converter { + static enum PolygonToDbObjectConverter implements Converter { INSTANCE; @@ -357,7 +377,7 @@ abstract class GeoConverters { * @since 1.5 */ @ReadingConverter - public static enum DbObjectToPolygonConverter implements Converter { + static enum DbObjectToPolygonConverter implements Converter { INSTANCE; @@ -392,7 +412,7 @@ abstract class GeoConverters { * @author Thomas Darimont * @since 1.5 */ - public static enum GeoCommandToDbObjectConverter implements Converter { + static enum GeoCommandToDbObjectConverter implements Converter { INSTANCE; @@ -411,6 +431,10 @@ abstract class GeoConverters { Shape shape = source.getShape(); + if (shape instanceof GeoJson) { + return GeoJsonToDbObjectConverter.INSTANCE.convert((GeoJson) shape); + } + if (shape instanceof Box) { argument.add(toList(((Box) shape).getFirst())); @@ -442,7 +466,317 @@ abstract class GeoConverters { } } + /** + * @author Christoph Strobl + * @since 1.7 + */ + @SuppressWarnings("rawtypes") + static enum GeoJsonToDbObjectConverter implements Converter { + INSTANCE; + + @Override + public DBObject convert(GeoJson source) { + + if (source == null) { + return null; + } + + DBObject dbo = new BasicDBObject("type", source.getType()); + + if (source instanceof GeoJsonGeometryCollection) { + BasicDBList dbl = new BasicDBList(); + for (GeoJson geometry : ((GeoJsonGeometryCollection) source).getCoordinates()) { + dbl.add(convert(geometry)); + } + dbo.put("geometries", dbl); + } else { + dbo.put("coordinates", convertIfNecessarry(source.getCoordinates())); + } + + return dbo; + } + + private Object convertIfNecessarry(Object candidate) { + + if (candidate instanceof GeoJson) { + return convertIfNecessarry(((GeoJson) candidate).getCoordinates()); + } + + if (candidate instanceof Iterable) { + BasicDBList dbl = new BasicDBList(); + for (Object element : (Iterable) candidate) { + dbl.add(convertIfNecessarry(element)); + } + return dbl; + } + + if (candidate instanceof Point) { + return toList((Point) candidate); + } + + return candidate; + } + } + + /** + * @author Christoph Strobl + * @since 1.7 + */ + static enum GeoJsonPointToDbObjectConverter implements Converter { + INSTANCE; + + @Override + public DBObject convert(GeoJsonPoint source) { + return GeoJsonToDbObjectConverter.INSTANCE.convert(source); + } + } + + /** + * @author Christoph Strobl + * @since 1.7 + */ + static enum GeoJsonPolygonToDbObjectConverter implements Converter { + INSTANCE; + + @Override + public DBObject convert(GeoJsonPolygon source) { + return GeoJsonToDbObjectConverter.INSTANCE.convert(source); + } + + } + + /** + * @author Christoph Strobl + * @since 1.7 + */ + static enum DbObjectToGeoJsonPointConverter implements Converter { + INSTANCE; + + @Override + public GeoJsonPoint convert(DBObject source) { + + if (source == null) { + return null; + } + + Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "Point"), + String.format("Cannot convert type '%s' to Point.", source.get("type"))); + + List dbl = (List) source.get("coordinates"); + return new GeoJsonPoint(dbl.get(0).doubleValue(), dbl.get(1).doubleValue()); + } + } + + /** + * @author Christoph Strobl + * @since 1.7 + */ + static enum DbObjectToGeoJsonPolygonConverter implements Converter { + INSTANCE; + + @SuppressWarnings("rawtypes") + @Override + public GeoJsonPolygon convert(DBObject source) { + + if (source == null) { + return null; + } + + Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "Polygon"), + String.format("Cannot convert type '%s' to Polygon.", source.get("type"))); + + return toGeoJsonPolygon((BasicDBList) source.get("coordinates")); + } + } + + /** + * @author Christoph Strobl + * @since 1.7 + */ + static enum DbObjectToGeoJsonMultiPolygonConverter implements Converter { + INSTANCE; + + @Override + public GeoJsonMultiPolygon convert(DBObject source) { + + if (source == null) { + return null; + } + + Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "MultiPolygon"), + String.format("Cannot convert type '%s' to MultiPolygon.", source.get("type"))); + + BasicDBList dbl = (BasicDBList) source.get("coordinates"); + List polygones = new ArrayList(); + for (Object polygon : dbl) { + polygones.add(toGeoJsonPolygon((BasicDBList) polygon)); + } + return new GeoJsonMultiPolygon(polygones); + + } + } + + /** + * @author Christoph Strobl + * @since 1.7 + */ + static enum DbObjectToGeoJsonLineStringConverter implements Converter { + INSTANCE; + + @Override + public GeoJsonLineString convert(DBObject source) { + + if (source == null) { + return null; + } + + Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "LineString"), + String.format("Cannot convert type '%s' to LineString.", source.get("type"))); + + BasicDBList cords = (BasicDBList) source.get("coordinates"); + return new GeoJsonLineString(toListOfPoint(cords)); + } + } + + /** + * @author Christoph Strobl + * @since 1.7 + */ + static enum DbObjectToGeoJsonMultiPointConverter implements Converter { + INSTANCE; + + @Override + public GeoJsonMultiPoint convert(DBObject source) { + + if (source == null) { + return null; + } + + Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "MultiPoint"), + String.format("Cannot convert type '%s' to MultiPoint.", source.get("type"))); + + BasicDBList cords = (BasicDBList) source.get("coordinates"); + return new GeoJsonMultiPoint(toListOfPoint(cords)); + } + } + + /** + * @author Christoph Strobl + * @since 1.7 + */ + static enum DbObjectToGeoJsonMultiLineStringConverter implements Converter { + INSTANCE; + + @Override + public GeoJsonMultiLineString convert(DBObject source) { + + if (source == null) { + return null; + } + + Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "MultiLineString"), + String.format("Cannot convert type '%s' to MultiLineString.", source.get("type"))); + + List lines = new ArrayList(); + BasicDBList cords = (BasicDBList) source.get("coordinates"); + for (Object line : cords) { + lines.add(new GeoJsonLineString(toListOfPoint((BasicDBList) line))); + } + return new GeoJsonMultiLineString(lines); + } + } + + /** + * @author Christoph Strobl + * @since 1.7 + */ + static enum DbObjectToGeoJsonGeometryCollectionConverter implements Converter { + INSTANCE; + + @Override + public GeoJsonGeometryCollection convert(DBObject source) { + + if (source == null) { + return null; + } + + Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "GeometryCollection"), + String.format("Cannot convert type '%s' to GeometryCollection.", source.get("type"))); + + List> geometries = new ArrayList>(); + for (Object o : (List) source.get("geometries")) { + geometries.add(convertGeometries((DBObject) o)); + } + return new GeoJsonGeometryCollection(geometries); + + } + + private GeoJson convertGeometries(DBObject source) { + + Object type = source.get("type"); + if (ObjectUtils.nullSafeEquals(type, "Point")) { + return DbObjectToGeoJsonPointConverter.INSTANCE.convert(source); + } + + if (ObjectUtils.nullSafeEquals(type, "MultiPoint")) { + return DbObjectToGeoJsonMultiPointConverter.INSTANCE.convert(source); + } + + if (ObjectUtils.nullSafeEquals(type, "LineString")) { + return DbObjectToGeoJsonLineStringConverter.INSTANCE.convert(source); + } + + if (ObjectUtils.nullSafeEquals(type, "MultiLineString")) { + return DbObjectToGeoJsonMultiLineStringConverter.INSTANCE.convert(source); + } + + if (ObjectUtils.nullSafeEquals(type, "Polygon")) { + return DbObjectToGeoJsonPolygonConverter.INSTANCE.convert(source); + } + if (ObjectUtils.nullSafeEquals(type, "MultiPolygon")) { + return DbObjectToGeoJsonMultiPolygonConverter.INSTANCE.convert(source); + } + + throw new IllegalArgumentException(String.format("Cannot convert unknown GeoJson type %s", type)); + } + } + static List toList(Point point) { return Arrays.asList(point.getX(), point.getY()); } + + /** + * Converts a coordinate pairs nested in in {@link BasicDBList} into {@link GeoJsonPoint}s. + * + * @param listOfCoordinatePairs + * @return + * @since 1.7 + */ + @SuppressWarnings("unchecked") + static List toListOfPoint(BasicDBList listOfCoordinatePairs) { + + List points = new ArrayList(); + for (Object point : listOfCoordinatePairs) { + + Assert.isInstanceOf(List.class, point); + + List coordinatesList = (List) point; + points.add(new GeoJsonPoint(coordinatesList.get(0).doubleValue(), coordinatesList.get(1).doubleValue())); + } + return points; + } + + /** + * Converts a coordinate pairs nested in in {@link BasicDBList} into {@link GeoJsonPolygon}. + * + * @param dbl + * @return + * @since 1.7 + */ + static GeoJsonPolygon toGeoJsonPolygon(BasicDBList dbl) { + + List outer = toListOfPoint((BasicDBList) dbl.get(0)); + + return new GeoJsonPolygon(outer); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJson.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJson.java new file mode 100644 index 000000000..92e4ae2ef --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJson.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.geo; + +/** + * Interface definition for structures defined in GeoJSON ({@link http://geojson.org/}) format. + * + * @author Christoph Strobl + * @since 1.7 + */ +public interface GeoJson> { + + /** + * String value representing the type of the {@link GeoJson} object. + * + * @return never {@literal null}. + * @see http://geojson.org/geojson-spec.html#geojson-objects + */ + String getType(); + + /** + * The value of the coordinates member is always an {@link Iterable}. The structure for the elements within is + * determined by {@link #getType()} of geometry. + * + * @return never {@literal null}. + * @see http://geojson.org/geojson-spec.html#geometry-objects + */ + T getCoordinates(); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonGeometryCollection.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonGeometryCollection.java new file mode 100644 index 000000000..1b98f312b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonGeometryCollection.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.geo; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Defines a {@link GeoJsonGeometryCollection} that consists of a {@link List} of {@link GeoJson} objects. + * + * @author Christoph Strobl + * @since 1.7 + * @see http://geojson.org/geojson-spec.html#geometry-collection + */ +public class GeoJsonGeometryCollection implements GeoJson>> { + + private static final String TYPE = "GeometryCollection"; + private final List> geometries = new ArrayList>(); + + /** + * @param geometries + */ + public GeoJsonGeometryCollection(List> geometries) { + + Assert.notNull(geometries); + this.geometries.addAll(geometries); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJson#getType() + */ + @Override + public String getType() { + return TYPE; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJson#getCoordinates() + */ + @Override + public Iterable> getCoordinates() { + return Collections.unmodifiableList(this.geometries); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.geometries); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof GeoJsonGeometryCollection)) { + return false; + } + GeoJsonGeometryCollection other = (GeoJsonGeometryCollection) obj; + return ObjectUtils.nullSafeEquals(this.geometries, other.geometries); + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonLineString.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonLineString.java new file mode 100644 index 000000000..98a8b9901 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonLineString.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.geo; + +import java.util.List; + +import org.springframework.data.geo.Point; + +/** + * {@link GeoJsonLineString} is defined as list of at least 2 {@link Point}s. + * + * @author Christoph Strobl + * @since 1.7 + * @see http://geojson.org/geojson-spec.html#linestring + */ +public class GeoJsonLineString extends GeoJsonMultiPoint { + + private static final String TYPE = "LineString"; + + /** + * @param points must not be {@literal null} and have at least 2 entries. + */ + public GeoJsonLineString(List points) { + super(points); + } + + /** + * @param p0 must not be {@literal null} + * @param p1 must not be {@literal null} + * @param others can be {@literal null} + */ + public GeoJsonLineString(Point p0, Point p1, Point... others) { + super(p0, p1, others); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJsonMultiPoint#getType() + */ + @Override + public String getType() { + return TYPE; + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiLineString.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiLineString.java new file mode 100644 index 000000000..1bc11d4c2 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiLineString.java @@ -0,0 +1,106 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.geo; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.geo.Point; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * {@link GeoJsonMultiLineString} is defined as list of {@link GeoJsonLineString}s. + * + * @author Christoph Strobl + * @since 1.7 + * @see http://geojson.org/geojson-spec.html#multilinestring + */ +public class GeoJsonMultiLineString implements GeoJson> { + + private static final String TYPE = "MultiLineString"; + private List coordinates = new ArrayList(); + + /** + * Creates new {@link GeoJsonMultiLineString}. + * + * @param lines must not be {@literal null}. + */ + @SuppressWarnings("unchecked") + public GeoJsonMultiLineString(List... lines) { + + Assert.notEmpty(lines, "Lines for MultiLineString must not be null!"); + for (List line : lines) { + this.coordinates.add(new GeoJsonLineString(line)); + } + } + + /** + * @param lines must not be {@literal null}. + */ + public GeoJsonMultiLineString(List lines) { + + Assert.notNull(lines, "Lines for MultiLineString must not be null!"); + this.coordinates.addAll(lines); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJson#getType() + */ + @Override + public String getType() { + return TYPE; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJson#getCoordinates() + */ + @Override + public Iterable getCoordinates() { + return Collections.unmodifiableList(this.coordinates); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.coordinates); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof GeoJsonMultiLineString)) { + return false; + } + return ObjectUtils.nullSafeEquals(this.coordinates, ((GeoJsonMultiLineString) obj).coordinates); + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPoint.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPoint.java new file mode 100644 index 000000000..d7b974443 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPoint.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.geo; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.geo.Point; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * {@link GeoJsonMultiPoint} is defined as list of {@link Point}s. + * + * @author Christoph Strobl + * @since 1.7 + * @see http://geojson.org/geojson-spec.html#multipoint + */ +public class GeoJsonMultiPoint implements GeoJson> { + + private static final String TYPE = "MultiPoint"; + private final List points; + + /** + * @param points points must not be {@literal null} and have at least 2 entries. + */ + public GeoJsonMultiPoint(List points) { + + Assert.notNull(points, "Points must not be null."); + Assert.isTrue(points.size() >= 2, "Minimum of 2 Points required."); + + this.points = new ArrayList(points); + } + + public GeoJsonMultiPoint(Point p0, Point p1, Point... others) { + + this.points = new ArrayList(); + this.points.add(p0); + this.points.add(p1); + if (!ObjectUtils.isEmpty(others)) { + this.points.addAll(Arrays.asList(others)); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJson#getType() + */ + @Override + public String getType() { + return TYPE; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJson#getCoordinates() + */ + @Override + public List getCoordinates() { + return Collections.unmodifiableList(this.points); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.points); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof GeoJsonMultiPoint)) { + return false; + } + return ObjectUtils.nullSafeEquals(this.points, ((GeoJsonMultiPoint) obj).points); + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPolygon.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPolygon.java new file mode 100644 index 000000000..f0254394c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPolygon.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.geo; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * {@link GeoJsonMultiPolygon} is defined as a list of {@link GeoJsonPolygon}s. + * + * @author Christoph Strobl + * @since 1.7 + */ +public class GeoJsonMultiPolygon implements GeoJson> { + + private static final String TYPE = "MultiPolygon"; + private List coordinates = new ArrayList(); + + /** + * @param polygons must not be {@literal null}. + */ + public GeoJsonMultiPolygon(List polygons) { + + Assert.notNull(polygons, "Polygons for MultiPolygon must not be null!"); + this.coordinates.addAll(polygons); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJson#getType() + */ + @Override + public String getType() { + return TYPE; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJson#getCoordinates() + */ + @Override + public List getCoordinates() { + return Collections.unmodifiableList(this.coordinates); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.coordinates); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof GeoJsonMultiPolygon)) { + return false; + } + return ObjectUtils.nullSafeEquals(this.coordinates, ((GeoJsonMultiPolygon) obj).coordinates); + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPoint.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPoint.java new file mode 100644 index 000000000..ab83adb3a --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPoint.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.geo; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.data.geo.Point; + +/** + * {@link GeoJson} representation of {@link Point}. + * + * @author Christoph Strobl + * @since 1.7 + * @see http://geojson.org/geojson-spec.html#point + */ +public class GeoJsonPoint extends Point implements GeoJson> { + + private static final long serialVersionUID = -8026303425147474002L; + private static final String TYPE = "Point"; + + /** + * Creates {@link GeoJsonPoint} for given coordinates. + * + * @param x + * @param y + */ + public GeoJsonPoint(double x, double y) { + super(x, y); + } + + /** + * Creates {@link GeoJsonPoint} for given {@link Point}. + * + * @param point must not be {@literal null}. + */ + public GeoJsonPoint(Point point) { + super(point); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJson#getType() + */ + @Override + public String getType() { + return TYPE; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJson#getCoordinates() + */ + @Override + public List getCoordinates() { + return Arrays.asList(Double.valueOf(getX()), Double.valueOf(getY())); + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPolygon.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPolygon.java new file mode 100644 index 000000000..5738efc4f --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPolygon.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.geo; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.geo.Point; +import org.springframework.data.geo.Polygon; +import org.springframework.util.ObjectUtils; + +/** + * {@link GeoJson} representation of {@link Polygon}. Unlike {@link Polygon} the {@link GeoJsonPolygon} requires a + * closed border. Which means that the first and last {@link Point} have to have same coordinate pairs. + * + * @author Christoph Strobl + * @since 1.7 + * @see http://geojson.org/geojson-spec.html#polygon + */ +public class GeoJsonPolygon extends Polygon implements GeoJson> { + + private static final long serialVersionUID = 3936163018187247185L; + private static final String TYPE = "Polygon"; + private List coordinates = new ArrayList(); + + /** + * Creates new {@link GeoJsonPolygon}. + * + * @param p0 must not be {@literal null}. + * @param p1 must not be {@literal null}. + * @param p2 must not be {@literal null}. + * @param p3 must not be {@literal null}. + * @param others can be {@literal null}. + */ + public GeoJsonPolygon(Point p0, Point p1, Point p2, Point p3, final Point... others) { + + this(new ArrayList(Arrays.asList(p0, p1, p2, p3)) { + private static final long serialVersionUID = 3143657022446395361L; + + { + if (!ObjectUtils.isEmpty(others)) { + for (Point p : others) { + add(p); + } + } + } + }); + } + + /** + * Creates new {@link GeoJsonPolygon}. + * + * @param points must not be {@literal null}. + */ + public GeoJsonPolygon(List points) { + + super(points); + this.coordinates.add(new GeoJsonLineString(points)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJson#getType() + */ + @Override + public String getType() { + return TYPE; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJson#getCoordinates() + */ + @Override + public List getCoordinates() { + return Collections.unmodifiableList(this.coordinates); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java index f8190738f..a0066a34f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java @@ -29,6 +29,7 @@ import org.springframework.data.geo.Circle; import org.springframework.data.geo.Point; import org.springframework.data.geo.Shape; import org.springframework.data.mongodb.InvalidMongoDbApiUsageException; +import org.springframework.data.mongodb.core.geo.GeoJson; import org.springframework.data.mongodb.core.geo.Sphere; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -439,6 +440,11 @@ public class Criteria implements CriteriaDefinition { * @return */ public Criteria maxDistance(double maxDistance) { + + if (createNearCriteriaForCommand("$near", maxDistance) || createNearCriteriaForCommand("$nearSphere", maxDistance)) { + return this; + } + criteria.put("$maxDistance", maxDistance); return this; } @@ -540,7 +546,13 @@ public class Criteria implements CriteriaDefinition { boolean not = false; for (String k : this.criteria.keySet()) { + Object value = this.criteria.get(k); + + if (requiresGeoJsonFormat(value)) { + value = new BasicDBObject("$geometry", value); + } + if (not) { DBObject notDbo = new BasicDBObject(); notDbo.put(k, value); @@ -593,6 +605,31 @@ public class Criteria implements CriteriaDefinition { } } + private boolean requiresGeoJsonFormat(Object value) { + return value instanceof GeoJson + || (value instanceof GeoCommand && ((GeoCommand) value).getShape() instanceof GeoJson); + } + + private boolean createNearCriteriaForCommand(String command, double maxDistance) { + + if (!criteria.containsKey(command)) { + return false; + } + + Object existingNearOperationValue = criteria.get(command); + if (existingNearOperationValue instanceof DBObject) { + ((DBObject) existingNearOperationValue).put("$maxDistance", maxDistance); + return true; + } else if (existingNearOperationValue instanceof GeoJson) { + + BasicDBObject dbo = new BasicDBObject("$geometry", existingNearOperationValue); + dbo.put("$maxDistance", maxDistance); + criteria.put(command, dbo); + return true; + } + return false; + } + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoJsonConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoJsonConverterUnitTests.java new file mode 100644 index 000000000..33fb1bf2c --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoJsonConverterUnitTests.java @@ -0,0 +1,450 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.convert; + +import static org.hamcrest.core.IsEqual.*; +import static org.hamcrest.core.IsNull.*; +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; +import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.convert.GeoConverters.DbObjectToGeoJsonLineStringConverter; +import org.springframework.data.mongodb.core.convert.GeoConverters.DbObjectToGeoJsonMultiLineStringConverter; +import org.springframework.data.mongodb.core.convert.GeoConverters.DbObjectToGeoJsonMultiPointConverter; +import org.springframework.data.mongodb.core.convert.GeoConverters.DbObjectToGeoJsonMultiPolygonConverter; +import org.springframework.data.mongodb.core.convert.GeoConverters.DbObjectToGeoJsonPointConverter; +import org.springframework.data.mongodb.core.convert.GeoConverters.DbObjectToGeoJsonPolygonConverter; +import org.springframework.data.mongodb.core.convert.GeoConverters.GeoJsonToDbObjectConverter; +import org.springframework.data.mongodb.core.geo.GeoJson; +import org.springframework.data.mongodb.core.geo.GeoJsonGeometryCollection; +import org.springframework.data.mongodb.core.geo.GeoJsonLineString; +import org.springframework.data.mongodb.core.geo.GeoJsonMultiLineString; +import org.springframework.data.mongodb.core.geo.GeoJsonMultiPoint; +import org.springframework.data.mongodb.core.geo.GeoJsonMultiPolygon; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; +import org.springframework.data.mongodb.core.geo.GeoJsonPolygon; +import org.springframework.data.mongodb.test.util.BasicDbListBuilder; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.BasicDBObjectBuilder; +import com.mongodb.DBObject; + +/** + * @author Christoph Strobl + */ +@RunWith(Suite.class) +@SuiteClasses({ GeoJsonConverterUnitTests.GeoJsonToDbObjectConverterUnitTests.class, + GeoJsonConverterUnitTests.DbObjectToGeoJsonPointConverterUnitTests.class, + GeoJsonConverterUnitTests.DbObjectToGeoJsonPolygonConverterUnitTests.class, + GeoJsonConverterUnitTests.DbObjectToGeoJsonLineStringConverterUnitTests.class, + GeoJsonConverterUnitTests.DbObjectToGeoJsonMultiPolygonConverterUnitTests.class, + GeoJsonConverterUnitTests.DbObjectToGeoJsonMultiLineStringConverterUnitTests.class, + GeoJsonConverterUnitTests.DbObjectToGeoJsonMultiPointConverterUnitTests.class }) +public class GeoJsonConverterUnitTests { + + /* + * --- GeoJson + */ + static final GeoJsonPoint SINGLE_POINT = new GeoJsonPoint(100, 50); + + static final Point POINT_0 = new Point(0, 0); + static final Point POINT_1 = new Point(100, 0); + static final Point POINT_2 = new Point(100, 100); + static final Point POINT_3 = new Point(0, 100); + + static final GeoJsonMultiPoint MULTI_POINT = new GeoJsonMultiPoint(POINT_0, POINT_2, POINT_3); + static final GeoJsonLineString LINE_STRING = new GeoJsonLineString(POINT_0, POINT_1, POINT_2); + @SuppressWarnings("unchecked") static final GeoJsonMultiLineString MULTI_LINE_STRING = new GeoJsonMultiLineString( + Arrays.asList(POINT_0, POINT_1, POINT_2), Arrays.asList(POINT_3, POINT_0)); + static final GeoJsonPolygon POLYGON = new GeoJsonPolygon(POINT_0, POINT_1, POINT_2, POINT_3, POINT_0); + static final GeoJsonMultiPolygon MULTI_POLYGON = new GeoJsonMultiPolygon(Arrays.asList(POLYGON)); + static final GeoJsonGeometryCollection GEOMETRY_COLLECTION = new GeoJsonGeometryCollection( + Arrays.> asList(SINGLE_POINT, POLYGON)); + /* + * -- GeoJson DBObjects + */ + + // Point + static final BasicDBList SINGE_POINT_CORDS = new BasicDbListBuilder() // + .add(SINGLE_POINT.getX()) // + .add(SINGLE_POINT.getY()) // + .get(); // + static final DBObject SINGLE_POINT_DBO = new BasicDBObjectBuilder() // + .add("type", "Point") // + .add("coordinates", SINGE_POINT_CORDS)// + .get(); + + // MultiPoint + static final BasicDBList MULTI_POINT_CORDS = new BasicDbListBuilder() // + .add(new BasicDbListBuilder().add(POINT_0.getX()).add(POINT_0.getY()).get()) // + .add(new BasicDbListBuilder().add(POINT_2.getX()).add(POINT_2.getY()).get()) // + .add(new BasicDbListBuilder().add(POINT_3.getX()).add(POINT_3.getY()).get()) // + .get(); + static final DBObject MULTI_POINT_DBO = new BasicDBObjectBuilder() // + .add("type", "MultiPoint")// + .add("coordinates", MULTI_POINT_CORDS)// + .get(); + + // Polygon + static final BasicDBList POLYGON_OUTER_CORDS = new BasicDbListBuilder() // + .add(new BasicDbListBuilder().add(POINT_0.getX()).add(POINT_0.getY()).get()) // + .add(new BasicDbListBuilder().add(POINT_1.getX()).add(POINT_1.getY()).get()) // + .add(new BasicDbListBuilder().add(POINT_2.getX()).add(POINT_2.getY()).get()) // + .add(new BasicDbListBuilder().add(POINT_3.getX()).add(POINT_3.getY()).get()) // + .add(new BasicDbListBuilder().add(POINT_0.getX()).add(POINT_0.getY()).get()) // + .get(); + static final BasicDBList POLYGON_CORDS = new BasicDbListBuilder().add(POLYGON_OUTER_CORDS).get(); + static final DBObject POLYGON_DBO = new BasicDBObjectBuilder() // + .add("type", "Polygon") // + .add("coordinates", POLYGON_CORDS) // + .get(); + + // LineString + static final BasicDBList LINE_STRING_CORDS_0 = new BasicDbListBuilder() // + .add(new BasicDbListBuilder().add(POINT_0.getX()).add(POINT_0.getY()).get()) // + .add(new BasicDbListBuilder().add(POINT_1.getX()).add(POINT_1.getY()).get()) // + .add(new BasicDbListBuilder().add(POINT_2.getX()).add(POINT_2.getY()).get()) // + .get(); + static final BasicDBList LINE_STRING_CORDS_1 = new BasicDbListBuilder() // + .add(new BasicDbListBuilder().add(POINT_3.getX()).add(POINT_3.getY()).get()) // + .add(new BasicDbListBuilder().add(POINT_0.getX()).add(POINT_0.getY()).get()) // + .get(); + static final DBObject LINE_STRING_DBO = new BasicDBObjectBuilder().add("type", "LineString") + .add("coordinates", LINE_STRING_CORDS_0).get(); + + // MultiLineString + static final BasicDBList MUILT_LINE_STRING_CORDS = new BasicDbListBuilder() // + .add(LINE_STRING_CORDS_0) // + .add(LINE_STRING_CORDS_1) // + .get(); + static final DBObject MULTI_LINE_STRING_DBO = new BasicDBObjectBuilder().add("type", "MultiLineString") + .add("coordinates", MUILT_LINE_STRING_CORDS).get(); + + // MultiPolygoin + static final BasicDBList MULTI_POLYGON_CORDS = new BasicDbListBuilder().add(POLYGON_CORDS).get(); + static final DBObject MULTI_POLYGON_DBO = new BasicDBObjectBuilder().add("type", "MultiPolygon") + .add("coordinates", MULTI_POLYGON_CORDS).get(); + + // GeometryCollection + static final BasicDBList GEOMETRY_COLLECTION_GEOMETRIES = new BasicDbListBuilder() // + .add(SINGLE_POINT_DBO)// + .add(POLYGON_DBO)// + .get(); + static final DBObject GEOMETRY_COLLECTION_DBO = new BasicDBObjectBuilder().add("type", "GeometryCollection") + .add("geometries", GEOMETRY_COLLECTION_GEOMETRIES).get(); + + /** + * @author Christoph Strobl + */ + public static class DbObjectToGeoJsonPolygonConverterUnitTests { + + DbObjectToGeoJsonPolygonConverter converter = DbObjectToGeoJsonPolygonConverter.INSTANCE; + public @Rule ExpectedException expectedException = ExpectedException.none(); + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldConvertDboCorrectly() { + assertThat(converter.convert(POLYGON_DBO), equalTo(POLYGON)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldReturnNullWhenConvertIsGivenNull() { + assertThat(converter.convert(null), nullValue()); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldThrowExceptionWhenTypeDoesNotMatchPolygon() { + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("'YouDontKonwMe' to Polygon"); + + converter.convert(new BasicDBObject("type", "YouDontKonwMe")); + } + + } + + /** + * @author Christoph Strobl + */ + public static class DbObjectToGeoJsonPointConverterUnitTests { + + DbObjectToGeoJsonPointConverter converter = DbObjectToGeoJsonPointConverter.INSTANCE; + public @Rule ExpectedException expectedException = ExpectedException.none(); + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldConvertDboCorrectly() { + assertThat(converter.convert(SINGLE_POINT_DBO), equalTo(SINGLE_POINT)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldReturnNullWhenConvertIsGivenNull() { + assertThat(converter.convert(null), nullValue()); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("'YouDontKonwMe' to Point"); + + converter.convert(new BasicDBObject("type", "YouDontKonwMe")); + } + } + + /** + * @author Christoph Strobl + */ + public static class DbObjectToGeoJsonLineStringConverterUnitTests { + + DbObjectToGeoJsonLineStringConverter converter = DbObjectToGeoJsonLineStringConverter.INSTANCE; + public @Rule ExpectedException expectedException = ExpectedException.none(); + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldConvertDboCorrectly() { + assertThat(converter.convert(LINE_STRING_DBO), equalTo(LINE_STRING)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldReturnNullWhenConvertIsGivenNull() { + assertThat(converter.convert(null), nullValue()); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("'YouDontKonwMe' to LineString"); + + converter.convert(new BasicDBObject("type", "YouDontKonwMe")); + } + } + + /** + * @author Christoph Strobl + */ + public static class DbObjectToGeoJsonMultiLineStringConverterUnitTests { + + DbObjectToGeoJsonMultiLineStringConverter converter = DbObjectToGeoJsonMultiLineStringConverter.INSTANCE; + public @Rule ExpectedException expectedException = ExpectedException.none(); + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldConvertDboCorrectly() { + assertThat(converter.convert(MULTI_LINE_STRING_DBO), equalTo(MULTI_LINE_STRING)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldReturnNullWhenConvertIsGivenNull() { + assertThat(converter.convert(null), nullValue()); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("'YouDontKonwMe' to MultiLineString"); + + converter.convert(new BasicDBObject("type", "YouDontKonwMe")); + } + } + + /** + * @author Christoph Strobl + */ + public static class DbObjectToGeoJsonMultiPointConverterUnitTests { + + DbObjectToGeoJsonMultiPointConverter converter = DbObjectToGeoJsonMultiPointConverter.INSTANCE; + public @Rule ExpectedException expectedException = ExpectedException.none(); + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldConvertDboCorrectly() { + assertThat(converter.convert(MULTI_POINT_DBO), equalTo(MULTI_POINT)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldReturnNullWhenConvertIsGivenNull() { + assertThat(converter.convert(null), nullValue()); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("'YouDontKonwMe' to MultiPoint"); + + converter.convert(new BasicDBObject("type", "YouDontKonwMe")); + } + } + + /** + * @author Christoph Strobl + */ + public static class DbObjectToGeoJsonMultiPolygonConverterUnitTests { + + DbObjectToGeoJsonMultiPolygonConverter converter = DbObjectToGeoJsonMultiPolygonConverter.INSTANCE; + public @Rule ExpectedException expectedException = ExpectedException.none(); + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldConvertDboCorrectly() { + assertThat(converter.convert(MULTI_POLYGON_DBO), equalTo(MULTI_POLYGON)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldReturnNullWhenConvertIsGivenNull() { + assertThat(converter.convert(null), nullValue()); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("'YouDontKonwMe' to MultiPolygon"); + + converter.convert(new BasicDBObject("type", "YouDontKonwMe")); + } + } + + /** + * @author Christoph Strobl + */ + public static class GeoJsonToDbObjectConverterUnitTests { + + GeoJsonToDbObjectConverter converter = GeoJsonToDbObjectConverter.INSTANCE; + + /** + * @see DATAMONGO-1135 + */ + public void convertShouldReturnNullWhenGivenNull() { + assertThat(converter.convert(null), nullValue()); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void shouldConvertGeoJsonPointCorrectly() { + assertThat(converter.convert(SINGLE_POINT), equalTo(SINGLE_POINT_DBO)); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void shouldConvertGeoJsonPolygonCorrectly() { + assertThat(converter.convert(POLYGON), equalTo(POLYGON_DBO)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldConvertGeoJsonLineStringCorrectly() { + assertThat(converter.convert(LINE_STRING), equalTo(LINE_STRING_DBO)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldConvertGeoJsonMultiLineStringCorrectly() { + assertThat(converter.convert(MULTI_LINE_STRING), equalTo(MULTI_LINE_STRING_DBO)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldConvertGeoJsonMultiPointCorrectly() { + assertThat(converter.convert(MULTI_POINT), equalTo(MULTI_POINT_DBO)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldConvertGeoJsonMultiPolygonCorrectly() { + assertThat(converter.convert(MULTI_POLYGON), equalTo(MULTI_POLYGON_DBO)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouldConvertGeometryCollectionCorrectly() { + assertThat(converter.convert(GEOMETRY_COLLECTION), equalTo(GEOMETRY_COLLECTION_DBO)); + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java index eaf70bda0..c0ef246a9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java @@ -20,6 +20,7 @@ import static org.junit.Assert.*; import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.Query.*; +import static org.springframework.data.mongodb.test.util.IsBsonObject.*; import java.math.BigInteger; import java.util.ArrayList; @@ -36,9 +37,12 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.annotation.Id; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.geo.Point; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.DBObjectTestUtils; import org.springframework.data.mongodb.core.Person; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; +import org.springframework.data.mongodb.core.geo.GeoJsonPolygon; import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; @@ -702,6 +706,75 @@ public class QueryMapperUnitTests { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("nested.id", 1).get())); } + /** + * @see DATAMONGO-1135 + */ + @Test + public void nearShouldUseGeoJsonRepresentationOnUnmappedProperty() { + + Query query = query(where("foo").near(new GeoJsonPoint(100, 50))); + + DBObject dbo = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(ClassWithGeoTypes.class)); + + assertThat(dbo, isBsonObject().containing("foo.$near.$geometry.type", "Point")); + assertThat(dbo, isBsonObject().containing("foo.$near.$geometry.coordinates.[0]", 100D)); + assertThat(dbo, isBsonObject().containing("foo.$near.$geometry.coordinates.[1]", 50D)); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void nearShouldUseGeoJsonRepresentationWhenMappingToGoJsonType() { + + Query query = query(where("geoJsonPoint").near(new GeoJsonPoint(100, 50))); + + DBObject dbo = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(ClassWithGeoTypes.class)); + + assertThat(dbo, isBsonObject().containing("geoJsonPoint.$near.$geometry.type", "Point")); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void nearSphereShouldUseGeoJsonRepresentationWhenMappingToGoJsonType() { + + Query query = query(where("geoJsonPoint").nearSphere(new GeoJsonPoint(100, 50))); + + DBObject dbo = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(ClassWithGeoTypes.class)); + + assertThat(dbo, isBsonObject().containing("geoJsonPoint.$nearSphere.$geometry.type", "Point")); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void shouldMapNameCorrectlyForGeoJsonType() { + + Query query = query(where("namedGeoJsonPoint").nearSphere(new GeoJsonPoint(100, 50))); + + DBObject dbo = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(ClassWithGeoTypes.class)); + + assertThat(dbo, + isBsonObject().containing("geoJsonPointWithNameViaFieldAnnotation.$nearSphere.$geometry.type", "Point")); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void withinShouldUseGeoJsonPolygonWhenMappingPolygonOn2DSphereIndex() { + + Query query = query(where("geoJsonPoint").within( + new GeoJsonPolygon(new Point(0, 0), new Point(100, 100), new Point(100, 0), new Point(0, 0)))); + + DBObject dbo = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(ClassWithGeoTypes.class)); + + assertThat(dbo, isBsonObject().containing("geoJsonPoint.$geoWithin.$geometry.type", "Polygon")); + } + @Document public class Foo { @Id private ObjectId id; @@ -794,4 +867,12 @@ public class QueryMapperUnitTests { @Field("id") String id; } + + static class ClassWithGeoTypes { + + double[] justAnArray; + Point legacyPoint; + GeoJsonPoint geoJsonPoint; + @Field("geoJsonPointWithNameViaFieldAnnotation") GeoJsonPoint namedGeoJsonPoint; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java new file mode 100644 index 000000000..0d97e73d5 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java @@ -0,0 +1,360 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.geo; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.Query.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.geo.GeoResults; +import org.springframework.data.geo.Metric; +import org.springframework.data.geo.Metrics; +import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.config.AbstractMongoConfiguration; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; +import org.springframework.data.mongodb.core.index.GeoSpatialIndexed; +import org.springframework.data.mongodb.core.index.GeospatialIndex; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.query.NearQuery; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.mongodb.Mongo; +import com.mongodb.MongoClient; +import com.mongodb.WriteConcern; + +/** + * @author Christoph Strobl + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class GeoJsonTests { + + @Configuration + static class TestConfig extends AbstractMongoConfiguration { + + @Override + protected String getDatabaseName() { + return "database"; + } + + @Override + public Mongo mongo() throws Exception { + return new MongoClient(); + } + } + + @Autowired MongoTemplate template; + + @Before + public void setUp() { + + template.setWriteConcern(WriteConcern.FSYNC_SAFE); + + createIndex(); + addVenues(); + } + + @After + public void tearDown() { + + dropIndex(); + removeCollections(); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void geoNear() { + + NearQuery geoNear = NearQuery.near(new GeoJsonPoint(-73, 40), Metrics.KILOMETERS).num(10).maxDistance(150); + + GeoResults result = template.geoNear(geoNear, Venue2DSphere.class); + + assertThat(result.getContent().size(), is(not(0))); + assertThat(result.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void withinPolygon() { + + Point first = new Point(-73.99756, 40.73083); + Point second = new Point(-73.99756, 40.741404); + Point third = new Point(-73.988135, 40.741404); + Point fourth = new Point(-73.988135, 40.73083); + + GeoJsonPolygon polygon = new GeoJsonPolygon(first, second, third, fourth, first); + + List venues = template.find(query(where("location").within(polygon)), Venue2DSphere.class); + assertThat(venues.size(), is(4)); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void nearPoint() { + + GeoJsonPoint point = new GeoJsonPoint(-73.99171, 40.738868); + + Query query = query(where("location").near(point).maxDistance(0.01)); + List venues = template.find(query, Venue2DSphere.class); + assertThat(venues.size(), is(1)); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void nearSphere() { + + GeoJsonPoint point = new GeoJsonPoint(-73.99171, 40.738868); + + Query query = query(where("location").nearSphere(point).maxDistance(0.003712240453784)); + List venues = template.find(query, Venue2DSphere.class); + + assertThat(venues.size(), is(1)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouleSaveAndRetrieveDocumentWithGeoJsonPointTypeCorrectly() { + + DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); + obj.id = "geoJsonPoint"; + obj.geoJsonPoint = new GeoJsonPoint(100, 50); + + template.save(obj); + + DocumentWithPropertyUsingGeoJsonType result = template.findOne(query(where("id").is(obj.id)), + DocumentWithPropertyUsingGeoJsonType.class); + + assertThat(result.geoJsonPoint, equalTo(obj.geoJsonPoint)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouleSaveAndRetrieveDocumentWithGeoJsonPolygonTypeCorrectly() { + + DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); + obj.id = "geoJsonPolygon"; + obj.geoJsonPolygon = new GeoJsonPolygon(new Point(0, 0), new Point(0, 1), new Point(1, 1), new Point(1, 0), + new Point(0, 0)); + + template.save(obj); + + DocumentWithPropertyUsingGeoJsonType result = template.findOne(query(where("id").is(obj.id)), + DocumentWithPropertyUsingGeoJsonType.class); + + assertThat(result.geoJsonPolygon, equalTo(obj.geoJsonPolygon)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouleSaveAndRetrieveDocumentWithGeoJsonLineStringTypeCorrectly() { + + DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); + obj.id = "geoJsonLineString"; + obj.geoJsonLineString = new GeoJsonLineString(new Point(0, 0), new Point(0, 1), new Point(1, 1)); + + template.save(obj); + + DocumentWithPropertyUsingGeoJsonType result = template.findOne(query(where("id").is(obj.id)), + DocumentWithPropertyUsingGeoJsonType.class); + + assertThat(result.geoJsonLineString, equalTo(obj.geoJsonLineString)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiLineStringTypeCorrectly() { + + DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); + obj.id = "geoJsonMultiLineString"; + obj.geoJsonMultiLineString = new GeoJsonMultiLineString(Arrays.asList(new GeoJsonLineString(new Point(0, 0), + new Point(0, 1), new Point(1, 1)), new GeoJsonLineString(new Point(199, 0), new Point(2, 3)))); + + template.save(obj); + + DocumentWithPropertyUsingGeoJsonType result = template.findOne(query(where("id").is(obj.id)), + DocumentWithPropertyUsingGeoJsonType.class); + + assertThat(result.geoJsonMultiLineString, equalTo(obj.geoJsonMultiLineString)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiPointTypeCorrectly() { + + DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); + obj.id = "geoJsonMultiPoint"; + obj.geoJsonMultiPoint = new GeoJsonMultiPoint(Arrays.asList(new Point(0, 0), new Point(0, 1), new Point(1, 1))); + + template.save(obj); + + DocumentWithPropertyUsingGeoJsonType result = template.findOne(query(where("id").is(obj.id)), + DocumentWithPropertyUsingGeoJsonType.class); + + assertThat(result.geoJsonMultiPoint, equalTo(obj.geoJsonMultiPoint)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiPolygonTypeCorrectly() { + + DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); + obj.id = "geoJsonMultiPolygon"; + obj.geoJsonMultiPolygon = new GeoJsonMultiPolygon(Arrays.asList(new GeoJsonPolygon(new Point(0, 0), + new Point(0, 1), new Point(1, 1), new Point(0, 0)))); + + template.save(obj); + + DocumentWithPropertyUsingGeoJsonType result = template.findOne(query(where("id").is(obj.id)), + DocumentWithPropertyUsingGeoJsonType.class); + + assertThat(result.geoJsonMultiPolygon, equalTo(obj.geoJsonMultiPolygon)); + } + + /** + * @see DATAMONGO-1137 + */ + @Test + public void shouleSaveAndRetrieveDocumentWithGeoJsonGeometryCollectionTypeCorrectly() { + + DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); + obj.id = "geoJsonGeometryCollection"; + obj.geoJsonGeometryCollection = new GeoJsonGeometryCollection(Arrays.> asList( + new GeoJsonPoint(100, 200), new GeoJsonPolygon(new Point(0, 0), new Point(0, 1), new Point(1, 1), new Point(1, + 0), new Point(0, 0)))); + + template.save(obj); + + DocumentWithPropertyUsingGeoJsonType result = template.findOne(query(where("id").is(obj.id)), + DocumentWithPropertyUsingGeoJsonType.class); + + assertThat(result.geoJsonGeometryCollection, equalTo(obj.geoJsonGeometryCollection)); + } + + private void addVenues() { + + template.insert(new Venue2DSphere("Penn Station", -73.99408, 40.75057)); + template.insert(new Venue2DSphere("10gen Office", -73.99171, 40.738868)); + template.insert(new Venue2DSphere("Flatiron Building", -73.988135, 40.741404)); + template.insert(new Venue2DSphere("Players Club", -73.997812, 40.739128)); + template.insert(new Venue2DSphere("City Bakery ", -73.992491, 40.738673)); + template.insert(new Venue2DSphere("Splash Bar", -73.992491, 40.738673)); + template.insert(new Venue2DSphere("Momofuku Milk Bar", -73.985839, 40.731698)); + template.insert(new Venue2DSphere("Shake Shack", -73.98820, 40.74164)); + template.insert(new Venue2DSphere("Penn Station", -73.99408, 40.75057)); + template.insert(new Venue2DSphere("Empire State Building", -73.98602, 40.74894)); + template.insert(new Venue2DSphere("Ulaanbaatar, Mongolia", 106.9154, 47.9245)); + template.insert(new Venue2DSphere("Maplewood, NJ", -74.2713, 40.73137)); + } + + protected void createIndex() { + dropIndex(); + template.indexOps(Venue2DSphere.class).ensureIndex( + new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2DSPHERE)); + } + + protected void dropIndex() { + try { + template.indexOps(Venue2DSphere.class).dropIndex("location"); + } catch (Exception e) { + + } + } + + protected void removeCollections() { + template.dropCollection(Venue2DSphere.class); + template.dropCollection(DocumentWithPropertyUsingGeoJsonType.class); + } + + @Document(collection = "venue2dsphere") + static class Venue2DSphere { + + @Id private String id; + private String name; + private @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) double[] location; + + @PersistenceConstructor + public Venue2DSphere(String name, double[] location) { + this.name = name; + this.location = location; + } + + public Venue2DSphere(String name, double x, double y) { + this.name = name; + this.location = new double[] { x, y }; + } + + public String getName() { + return name; + } + + public double[] getLocation() { + return location; + } + + @Override + public String toString() { + return "Venue2DSphere [id=" + id + ", name=" + name + ", location=" + Arrays.toString(location) + "]"; + } + } + + static class DocumentWithPropertyUsingGeoJsonType { + + String id; + GeoJsonPoint geoJsonPoint; + GeoJsonPolygon geoJsonPolygon; + GeoJsonLineString geoJsonLineString; + GeoJsonMultiLineString geoJsonMultiLineString; + GeoJsonMultiPoint geoJsonMultiPoint; + GeoJsonMultiPolygon geoJsonMultiPolygon; + GeoJsonGeometryCollection geoJsonGeometryCollection; + } + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java index a4fea2457..e42cd5365 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java @@ -19,7 +19,10 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import org.junit.Test; +import org.springframework.data.geo.Point; import org.springframework.data.mongodb.InvalidMongoDbApiUsageException; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; +import org.springframework.data.mongodb.test.util.IsBsonObject; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; @@ -164,4 +167,48 @@ public class CriteriaTests { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("$not", new BasicDBObject("$lt", "foo")).get())); } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void geoJsonTypesShouldBeWrappedInGeometry() { + + DBObject dbo = new Criteria("foo").near(new GeoJsonPoint(100, 200)).getCriteriaObject(); + + assertThat(dbo, IsBsonObject.isBsonObject().containing("foo.$near.$geometry", new GeoJsonPoint(100, 200))); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void legacyCoordinateTypesShouldNotBeWrappedInGeometry() { + + DBObject dbo = new Criteria("foo").near(new Point(100, 200)).getCriteriaObject(); + + assertThat(dbo, IsBsonObject.isBsonObject().notContaining("foo.$near.$geometry")); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void maxDistanceShouldBeMappedInsideNearWhenUsedAlongWithGeoJsonType() { + + DBObject dbo = new Criteria("foo").near(new GeoJsonPoint(100, 200)).maxDistance(50D).getCriteriaObject(); + + assertThat(dbo, IsBsonObject.isBsonObject().containing("foo.$near.$maxDistance", 50D)); + } + + /** + * @see DATAMONGO-1135 + */ + @Test + public void maxDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonType() { + + DBObject dbo = new Criteria("foo").nearSphere(new GeoJsonPoint(100, 200)).maxDistance(50D).getCriteriaObject(); + + assertThat(dbo, IsBsonObject.isBsonObject().containing("foo.$nearSphere.$maxDistance", 50D)); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/BasicDbListBuilder.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/BasicDbListBuilder.java new file mode 100644 index 000000000..f34e6650d --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/BasicDbListBuilder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.test.util; + +import com.mongodb.BasicDBList; + +/** + * @author Christoph Strobl + */ +public class BasicDbListBuilder { + + private final BasicDBList dbl; + + public BasicDbListBuilder() { + this.dbl = new BasicDBList(); + } + + public BasicDbListBuilder add(Object value) { + + this.dbl.add(value); + return this; + } + + public BasicDBList get() { + return this.dbl; + } + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/IsBsonObject.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/IsBsonObject.java new file mode 100644 index 000000000..04f4b4ce9 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/IsBsonObject.java @@ -0,0 +1,189 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.test.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.bson.BSONObject; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.hamcrest.core.IsEqual; +import org.springframework.data.mongodb.core.query.SerializationUtils; +import org.springframework.util.ClassUtils; + +import com.mongodb.DBObject; + +/** + * @author Christoph Strobl + * @param + */ +public class IsBsonObject extends TypeSafeMatcher { + + private List expectations = new ArrayList();; + + public static IsBsonObject isBsonObject() { + return new IsBsonObject(); + } + + @Override + protected void describeMismatchSafely(T item, Description mismatchDescription) { + mismatchDescription.appendText("was ").appendValue(SerializationUtils.serializeToJsonSafely(item)); + } + + @Override + public void describeTo(Description description) { + + for (ExpectedBsonContent expectation : expectations) { + + if (expectation.not) { + description.appendText(String.format("Path %s should not be present.", expectation.path)); + } else if (expectation.value == null) { + description.appendText(String.format("Expected to find path %s.", expectation.path)); + } else { + description.appendText(String.format("Expected to find %s for path %s.", expectation.value, expectation.path)); + } + } + + } + + @Override + protected boolean matchesSafely(T item) { + + if (expectations.isEmpty()) { + return true; + } + + for (ExpectedBsonContent expectation : expectations) { + + Object o = getValue(item, expectation.path); + + if (o == null && expectation.not) { + return true; + } + + if (o == null) { + return false; + } + + if (expectation.type != null && !ClassUtils.isAssignable(expectation.type, o.getClass())) { + return false; + } + + if (expectation.value != null && !new IsEqual(expectation.value).matches(o)) { + return false; + } + + } + return true; + } + + public IsBsonObject containing(String key) { + + ExpectedBsonContent expected = new ExpectedBsonContent(); + expected.path = key; + + this.expectations.add(expected); + return this; + } + + public IsBsonObject containing(String key, Class type) { + + ExpectedBsonContent expected = new ExpectedBsonContent(); + expected.path = key; + expected.type = type; + + this.expectations.add(expected); + return this; + } + + public IsBsonObject containing(String key, Object value) { + + if (value == null) { + return notContaining(key); + } + + ExpectedBsonContent expected = new ExpectedBsonContent(); + expected.path = key; + expected.type = ClassUtils.getUserClass(value); + expected.value = value; + + this.expectations.add(expected); + return this; + } + + public IsBsonObject notContaining(String key) { + + ExpectedBsonContent expected = new ExpectedBsonContent(); + expected.path = key; + expected.not = true; + + this.expectations.add(expected); + return this; + } + + static class ExpectedBsonContent { + String path; + Class type; + Object value; + boolean not = false; + } + + Object getValue(BSONObject source, String path) { + + String[] fragments = path.split("\\."); + if (fragments.length == 1) { + return source.get(path); + } + + Iterator it = Arrays.asList(fragments).iterator(); + + Object current = source; + while (it.hasNext()) { + + String key = it.next(); + + if (!(current instanceof BSONObject) && !key.startsWith("[")) { + return null; + } + + if (key.startsWith("[")) { + String indexNumber = key.substring(1, key.indexOf("]")); + if (current instanceof List) { + current = ((List) current).get(Integer.valueOf(indexNumber)); + } + if (!it.hasNext()) { + return current; + } + } else { + + if (current instanceof DBObject) { + current = ((DBObject) current).get(key); + } + + if (!it.hasNext()) { + return current; + } + + } + } + + throw new NoSuchElementException(String.format("Unable to find '%s' in %s.", path, source)); + } +}