Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@
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.mapping.FieldType;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.GeoCommand;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.lang.Contract;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
Expand All @@ -60,6 +63,7 @@
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thiago Diniz da Silveira
* @author dragonfsky
* @since 1.5
*/
@SuppressWarnings("ConstantConditions")
Expand Down Expand Up @@ -149,6 +153,31 @@ public Point convert(Document source) {
}
}

/**
* Converts a {@link List} of coordinates into a {@link Point}.
*
* @author dragonfsky
* @since 5.1
*/
@ReadingConverter
enum ListToPointConverter implements Converter<List<Number>, @Nullable Point> {

INSTANCE;

@Override
@SuppressWarnings("NullAway")
public Point convert(List<Number> source) {

if (ObjectUtils.isEmpty(source)) {
return null;
}

Assert.isTrue(source.size() == 2, "Source must contain 2 elements");

return new Point(toPrimitiveDoubleValue(source.get(0)), toPrimitiveDoubleValue(source.get(1)));
}
}

/**
* Converts a {@link Point} into a {@link List} of {@link Double}s.
*
Expand Down Expand Up @@ -728,6 +757,28 @@ static List<Double> toList(Point point) {
return Arrays.asList(point.getX(), point.getY());
}

static boolean isArrayBackedPoint(MongoPersistentProperty property, @Nullable Object value) {
return value instanceof Point && isArrayBackedPointProperty(property);
}

static boolean isArrayBackedPointProperty(MongoPersistentProperty property) {
return property.hasExplicitWriteTarget() && property.getMongoField().getFieldType() == FieldType.ARRAY
&& Point.class.isAssignableFrom(property.getType());
}

static List<Double> writeArrayBackedPoint(Point point) {
return toList(point);
}

@SuppressWarnings("unchecked")
static @Nullable Point readArrayBackedPoint(Object source) {

Collection<?> coordinates = BsonUtils.asCollection(source);
List<?> coordinateList = coordinates instanceof List<?> list ? list : new ArrayList<>(coordinates);

return ListToPointConverter.INSTANCE.convert((List<Number>) coordinateList);
}

/**
* Converts a coordinate pairs nested in {@link List} into {@link GeoJsonPoint}s.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import org.springframework.data.convert.TypeMapper;
import org.springframework.data.convert.ValueConversionContext;
import org.springframework.data.core.TypeInformation;
import org.springframework.data.geo.Point;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.MappingException;
Expand Down Expand Up @@ -1339,6 +1340,11 @@ private void writeSimpleInternal(@Nullable Object value, Bson bson, MongoPersist
return;
}

if (GeoConverters.isArrayBackedPoint(property, value)) {
accessor.put(property, GeoConverters.writeArrayBackedPoint((Point) value));
return;
}

accessor.put(property, getPotentiallyConvertedSimpleWrite(value,
property.hasExplicitWriteTarget() ? property.getFieldType() : Object.class));
}
Expand Down Expand Up @@ -2008,6 +2014,10 @@ static class MongoDbPropertyValueProvider implements PropertyValueProvider<Mongo
return null;
}

if (GeoConverters.isArrayBackedPointProperty(property)) {
return (T) GeoConverters.readArrayBackedPoint(value);
}

ConversionContext contextToUse = context.forProperty(property);

return (T) contextToUse.convert(value, property.getTypeInformation());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.springframework.data.core.PropertyReferenceException;
import org.springframework.data.core.TypeInformation;
import org.springframework.data.domain.Example;
import org.springframework.data.geo.Point;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
Expand Down Expand Up @@ -925,6 +926,31 @@ private Object applyFieldTargetTypeHintToValue(Field documentField, @Nullable Ob
return value;
}

if (GeoConverters.isArrayBackedPoint(documentField.getProperty(), value)) {
return GeoConverters.writeArrayBackedPoint((Point) value);
}

if (GeoConverters.isArrayBackedPointProperty(documentField.getProperty())
&& value instanceof Collection<?> source) {

List<Object> converted = new ArrayList<>(source.size());
boolean hasPoint = false;

for (Object candidate : source) {

if (candidate instanceof Point point) {
converted.add(GeoConverters.writeArrayBackedPoint(point));
hasPoint = true;
} else {
converted.add(candidate);
}
}

if (hasPoint) {
return converted;
}
}

if (!conversionService.canConvert(value.getClass(), documentField.getProperty().getFieldType())) {
return value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.data.core.TypeInformation;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.geo.Point;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.convert.MongoConversionContext.WriteOperatorContext;
Expand Down Expand Up @@ -236,6 +237,12 @@ private boolean isQuery(@Nullable Object value) {
}

TypeInformation<?> typeHint = field == null ? TypeInformation.OBJECT : field.getTypeHint();

if (field != null && field.getProperty() != null
&& GeoConverters.isArrayBackedPoint(field.getProperty(), value)) {
return GeoConverters.writeArrayBackedPoint((Point) value);
}

return converter.convertToMongoType(value, typeHint);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
* @author dragonfsky
* @since 1.5
*/
public class GeoConvertersUnitTests {
Expand Down Expand Up @@ -152,6 +153,13 @@ public void convertsPointCorrectlyWhenUsingNonDoubleForCoordinates() {
.isEqualTo(new Point(1, 2));
}

@Test // GH-4997
public void convertsCoordinateListToPointCorrectly() {

assertThat(ListToPointConverter.INSTANCE.convert(Arrays.asList(-73.99171, 40.738868)))
.isEqualTo(new Point(-73.99171, 40.738868));
}

@Test // DATAMONGO-1607
public void convertsCircleCorrectlyWhenUsingNonDoubleForCoordinates() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
* @author Roman Puchkovskiy
* @author Heesu Jung
* @author Julia Lee
* @author dragonfsky
*/
@ExtendWith(MockitoExtension.class)
class MappingMongoConverterUnitTests {
Expand Down Expand Up @@ -1552,6 +1553,40 @@ void shouldReadEntityWithGeoBoxCorrectly() {
assertThat(result.box).isEqualTo(object.box);
}

@Test // GH-4997
void shouldReadEntityWithGeoPointFromArrayCoordinates() {

org.bson.Document document = new org.bson.Document("point", Arrays.asList(-73.99171, 40.738868));

ClassWithArrayBackedGeoPoint result = converter.read(ClassWithArrayBackedGeoPoint.class, document);

assertThat(result.point).isEqualTo(new Point(-73.99171, 40.738868));
}

@Test // GH-4997
void shouldWriteArrayBackedGeoPointAsArrayCoordinates() {

ClassWithArrayBackedGeoPoint object = new ClassWithArrayBackedGeoPoint();
object.point = new Point(-73.99171, 40.738868);

org.bson.Document document = new org.bson.Document();
converter.write(object, document);

assertThat(document.get("point")).isEqualTo(Arrays.asList(-73.99171, 40.738868));
}

@Test // GH-4997
void shouldKeepDefaultGeoPointRepresentation() {

ClassWithGeoPoint object = new ClassWithGeoPoint();
object.point = new Point(-73.99171, 40.738868);

org.bson.Document document = new org.bson.Document();
converter.write(object, document);

assertThat(document.get("point")).isEqualTo(toDocument(object.point));
}

@Test // DATAMONGO-858
void shouldWriteEntityWithGeoPolygonCorrectly() {

Expand Down Expand Up @@ -4059,6 +4094,16 @@ class ClassWithGeoBox {
Box box;
}

class ClassWithGeoPoint {

Point point;
}

class ClassWithArrayBackedGeoPoint {

@Field(targetType = FieldType.ARRAY) Point point;
}

class ClassWithGeoCircle {

Circle circle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,30 @@ void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() {
assertThat(document).containsEntry("legacyPoint.y", 20D);
}

@Test // GH-4997
void shouldMapArrayBackedPointQueryToCoordinateArray() {

Query query = query(where("arrayBackedPoint").is(new Point(-73.99171, 40.738868)));

org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
context.getPersistentEntity(ClassWithGeoTypes.class));

assertThat(document).containsEntry("arrayBackedPoint", Arrays.asList(-73.99171, 40.738868));
}

@Test // GH-4997
void shouldMapArrayBackedPointInQueryToCoordinateArrays() {

Query query = query(where("arrayBackedPoint").in(new Point(-73.99171, 40.738868), new Point(10D, 20D)));

org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
context.getPersistentEntity(ClassWithGeoTypes.class));

List<List<Double>> expected = Arrays.asList(Arrays.asList(-73.99171, 40.738868),
Arrays.asList(10D, 20D));
assertThat(getAsDocument(document, "arrayBackedPoint").get("$in")).isEqualTo(expected);
}

@Test // GH-3544
void exampleWithCombinedCriteriaShouldBeMappedCorrectly() {

Expand Down Expand Up @@ -1902,6 +1926,7 @@ static class ClassWithGeoTypes {

double[] justAnArray;
Point legacyPoint;
@Field(targetType = FieldType.ARRAY) Point arrayBackedPoint;
GeoJsonPoint geoJsonPoint;
@Field("geoJsonPointWithNameViaFieldAnnotation") GeoJsonPoint namedGeoJsonPoint;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.geo.Point;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mongodb.core.DocumentTestUtils;
import org.springframework.data.mongodb.core.mapping.DocumentReference;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.mongodb.core.query.Criteria;
Expand Down Expand Up @@ -149,6 +151,18 @@ void updateMapperShouldNotPersistTypeInformationForNullValues() {
assertThat(set.get("_class")).isNull();
}

@Test // GH-4997
void updateMapperShouldMapArrayBackedPointToCoordinateArray() {

Update update = Update.update("point", new Point(-73.99171, 40.738868));

Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ClassWithArrayBackedGeoPoint.class));

Document set = getAsDocument(mappedObject, "$set");
assertThat(set.get("point")).isEqualTo(Arrays.asList(-73.99171, 40.738868));
}

@Test // DATAMONGO-407
void updateMapperShouldRetainTypeInformationForNestedCollectionElements() {

Expand Down Expand Up @@ -1830,4 +1844,9 @@ static class WithPropertyValueConverter {
@ValueConverter(ReversingValueConverter.class)
String text;
}

static class ClassWithArrayBackedGeoPoint {

@Field(targetType = FieldType.ARRAY) Point point;
}
}