Skip to content

Commit e951a3b

Browse files
GeoJsonPolygon et al
1 parent 7689e5b commit e951a3b

File tree

7 files changed

+105
-32
lines changed

7 files changed

+105
-32
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/GeoCommand.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.data.geo.Circle;
2323
import org.springframework.data.geo.Polygon;
2424
import org.springframework.data.geo.Shape;
25+
import org.springframework.data.mongodb.core.geo.GeoJson;
2526
import org.springframework.data.mongodb.core.geo.Sphere;
2627
import org.springframework.util.Assert;
2728

@@ -75,6 +76,9 @@ private String getCommand(Shape shape) {
7576

7677
Assert.notNull(shape, "Shape must not be null");
7778

79+
if(shape instanceof GeoJson<?>) {
80+
return "$geometry";
81+
}
7882
if (shape instanceof Box) {
7983
return "$box";
8084
} else if (shape instanceof Circle) {

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.data.geo.Shape;
3838
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
3939
import org.springframework.data.mongodb.core.convert.MongoWriter;
40+
import org.springframework.data.mongodb.core.geo.GeoJson;
4041
import org.springframework.data.mongodb.core.geo.Sphere;
4142
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
4243
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
@@ -53,9 +54,9 @@
5354
import org.springframework.data.repository.query.QueryMethod;
5455
import org.springframework.data.repository.query.parser.PartTree;
5556
import org.springframework.data.util.TypeInformation;
57+
import org.springframework.util.ClassUtils;
5658

5759
import com.mongodb.DBRef;
58-
import org.springframework.util.ClassUtils;
5960

6061
/**
6162
* @author Christoph Strobl
@@ -133,19 +134,21 @@ public PlaceholderParameterAccessor(QueryMethod queryMethod) {
133134
} else {
134135
placeholders = new ArrayList<>();
135136
Parameters<?, ?> parameters = queryMethod.getParameters();
136-
for(Parameter parameter : parameters.toList()) {
137-
if(ClassUtils.isAssignable(Point.class, parameter.getType())) {
137+
for (Parameter parameter : parameters.toList()) {
138+
if (ClassUtils.isAssignable(GeoJson.class, parameter.getType())) {
139+
placeholders.add(parameter.getIndex(), new GeoJsonPlaceholder(parameter.getIndex(), ""));
140+
}
141+
else if (ClassUtils.isAssignable(Point.class, parameter.getType())) {
138142
placeholders.add(parameter.getIndex(), new PointPlaceholder(parameter.getIndex()));
139-
} else if(ClassUtils.isAssignable(Circle.class, parameter.getType())) {
143+
} else if (ClassUtils.isAssignable(Circle.class, parameter.getType())) {
140144
placeholders.add(parameter.getIndex(), new CirclePlaceholder(parameter.getIndex()));
141-
} else if(ClassUtils.isAssignable(Box.class, parameter.getType())) {
145+
} else if (ClassUtils.isAssignable(Box.class, parameter.getType())) {
142146
placeholders.add(parameter.getIndex(), new BoxPlaceholder(parameter.getIndex()));
143-
} else if(ClassUtils.isAssignable(Sphere.class, parameter.getType())) {
147+
} else if (ClassUtils.isAssignable(Sphere.class, parameter.getType())) {
144148
placeholders.add(parameter.getIndex(), new SpherePlaceholder(parameter.getIndex()));
145-
} else if(ClassUtils.isAssignable(Polygon.class, parameter.getType())) {
149+
} else if (ClassUtils.isAssignable(Polygon.class, parameter.getType())) {
146150
placeholders.add(parameter.getIndex(), new PolygonPlaceholder(parameter.getIndex()));
147151
}
148-
149152
else {
150153
placeholders.add(parameter.getIndex(), Placeholder.indexed(parameter.getIndex()));
151154
}
@@ -238,6 +241,7 @@ public Iterator<Object> iterator() {
238241
static class CirclePlaceholder extends Circle implements Placeholder {
239242

240243
int index;
244+
241245
public CirclePlaceholder(int index) {
242246
super(new PointPlaceholder(index), Distance.of(1, Metrics.NEUTRAL)); //
243247
this.index = index;
@@ -257,6 +261,7 @@ public String toString() {
257261
static class SpherePlaceholder extends Sphere implements Placeholder {
258262

259263
int index;
264+
260265
public SpherePlaceholder(int index) {
261266
super(new PointPlaceholder(index), Distance.of(1, Metrics.NEUTRAL)); //
262267
this.index = index;
@@ -273,6 +278,37 @@ public String toString() {
273278
}
274279
}
275280

281+
static class GeoJsonPlaceholder implements Placeholder, GeoJson<List<Placeholder>>, Shape {
282+
283+
int index;
284+
String type;
285+
286+
public GeoJsonPlaceholder(int index, String type) {
287+
this.index = index;
288+
this.type = type;
289+
}
290+
291+
@Override
292+
public Object getValue() {
293+
return "?%s".formatted(index);
294+
}
295+
296+
@Override
297+
public String toString() {
298+
return getValue().toString();
299+
}
300+
301+
@Override
302+
public String getType() {
303+
return type;
304+
}
305+
306+
@Override
307+
public List<Placeholder> getCoordinates() {
308+
return List.of();
309+
}
310+
}
311+
276312
static class BoxPlaceholder extends Box implements Placeholder {
277313
int index;
278314

@@ -296,7 +332,8 @@ static class PolygonPlaceholder extends Polygon implements Placeholder {
296332
int index;
297333

298334
public PolygonPlaceholder(int index) {
299-
super(new PointPlaceholder(index), new PointPlaceholder(index), new PointPlaceholder(index), new PointPlaceholder(index));
335+
super(new PointPlaceholder(index), new PointPlaceholder(index), new PointPlaceholder(index),
336+
new PointPlaceholder(index));
300337
this.index = index;
301338
}
302339

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoCodeBlocks.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline;
4545
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
4646
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
47+
import org.springframework.data.mongodb.core.geo.GeoJson;
4748
import org.springframework.data.mongodb.core.geo.Sphere;
4849
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
4950
import org.springframework.data.mongodb.core.query.BasicQuery;
@@ -786,7 +787,9 @@ static class QueryCodeBlockBuilder {
786787
this.arguments = new ArrayList<>();
787788
for (MongoParameter parameter : queryMethod.getParameters().getBindableParameters()) {
788789
String parameterName = context.getParameterName(parameter.getIndex());
789-
if (ClassUtils.isAssignable(Circle.class, parameter.getType())) {
790+
if (ClassUtils.isAssignable(GeoJson.class, parameter.getType())) {
791+
arguments.add(CodeBlock.of(parameterName));
792+
} else if (ClassUtils.isAssignable(Circle.class, parameter.getType())) {
790793
arguments.add(CodeBlock.builder().add(
791794
"$1T.of($1T.of($2L.getCenter().getX(), $2L.getCenter().getY()), $2L.getRadius().getNormalizedValue())",
792795
List.class, parameterName).build());
@@ -802,14 +805,13 @@ static class QueryCodeBlockBuilder {
802805
"$1T.of($1T.of($2L.getCenter().getX(), $2L.getCenter().getY()), $2L.getRadius().getNormalizedValue())",
803806
List.class, parameterName).build());
804807
} else if (ClassUtils.isAssignable(Polygon.class, parameter.getType())) {
808+
805809
// $polygon: [ [ <x1> , <y1> ], [ <x2> , <y2> ], [ <x3> , <y3> ], ... ]
806810
String localVar = context.localVariable("_p");
807811
arguments.add(
808812
CodeBlock.builder().add("$1L.getPoints().stream().map($2L -> $3T.of($2L.getX(), $2L.getY())).toList()",
809813
parameterName, localVar, List.class).build());
810-
}
811-
812-
else {
814+
} else {
813815
arguments.add(CodeBlock.of(parameterName));
814816
}
815817
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoRepositoryContributor.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@
1515
*/
1616
package org.springframework.data.mongodb.repository.aot;
1717

18-
import static org.springframework.data.mongodb.repository.aot.MongoCodeBlocks.*;
18+
import static org.springframework.data.mongodb.repository.aot.MongoCodeBlocks.QueryCodeBlockBuilder;
19+
import static org.springframework.data.mongodb.repository.aot.MongoCodeBlocks.aggregationBlockBuilder;
20+
import static org.springframework.data.mongodb.repository.aot.MongoCodeBlocks.aggregationExecutionBlockBuilder;
21+
import static org.springframework.data.mongodb.repository.aot.MongoCodeBlocks.deleteExecutionBlockBuilder;
22+
import static org.springframework.data.mongodb.repository.aot.MongoCodeBlocks.geoNearBlockBuilder;
23+
import static org.springframework.data.mongodb.repository.aot.MongoCodeBlocks.queryBlockBuilder;
24+
import static org.springframework.data.mongodb.repository.aot.MongoCodeBlocks.queryExecutionBlockBuilder;
25+
import static org.springframework.data.mongodb.repository.aot.MongoCodeBlocks.updateBlockBuilder;
26+
import static org.springframework.data.mongodb.repository.aot.MongoCodeBlocks.updateExecutionBlockBuilder;
1927

2028
import java.lang.reflect.Method;
2129
import java.util.Locale;
@@ -25,6 +33,7 @@
2533
import org.apache.commons.logging.LogFactory;
2634
import org.jspecify.annotations.Nullable;
2735
import org.springframework.core.annotation.AnnotatedElementUtils;
36+
import org.springframework.data.geo.GeoPage;
2837
import org.springframework.data.mongodb.core.MongoOperations;
2938
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
3039
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@@ -42,6 +51,7 @@
4251
import org.springframework.data.repository.query.parser.PartTree;
4352
import org.springframework.javapoet.CodeBlock;
4453
import org.springframework.javapoet.TypeName;
54+
import org.springframework.util.ClassUtils;
4555
import org.springframework.util.ObjectUtils;
4656
import org.springframework.util.StringUtils;
4757

@@ -95,7 +105,8 @@ protected void customizeConstructor(AotRepositoryConstructorBuilder constructorB
95105
return aggregationMethodContributor(queryMethod, aggregation);
96106
}
97107

98-
if(queryMethod.isGeoNearQuery() || (queryMethod.getParameters().getMaxDistanceIndex() != -1 && queryMethod.getReturnType().isCollectionLike())) {
108+
if (queryMethod.isGeoNearQuery() || (queryMethod.getParameters().getMaxDistanceIndex() != -1
109+
&& queryMethod.getReturnType().isCollectionLike())) {
99110
NearQueryInteraction near = new NearQueryInteraction();
100111
return nearQueryMethodContributor(queryMethod, near);
101112
}
@@ -159,8 +170,8 @@ private QueryInteraction createStringQuery(RepositoryInformation repositoryInfor
159170
} else {
160171

161172
PartTree partTree = new PartTree(queryMethod.getName(), repositoryInformation.getDomainType());
162-
query = new QueryInteraction(queryCreator.createQuery(partTree, queryMethod),
163-
partTree.isCountProjection(), partTree.isDelete(), partTree.isExistsProjection());
173+
query = new QueryInteraction(queryCreator.createQuery(partTree, queryMethod), partTree.isCountProjection(),
174+
partTree.isDelete(), partTree.isExistsProjection());
164175
}
165176

166177
if (queryAnnotation != null && StringUtils.hasText(queryAnnotation.sort())) {
@@ -177,7 +188,7 @@ private static boolean backoff(MongoQueryMethod method) {
177188

178189
// TODO: namedQuery, Regex queries, queries accepting Shapes (e.g. within) or returning arrays.
179190
boolean skip = method.isSearchQuery() || method.getName().toLowerCase(Locale.ROOT).contains("regex")
180-
|| method.getReturnType().getType().isArray();
191+
|| method.getReturnType().getType().isArray() || ClassUtils.isAssignable(GeoPage.class, method.getReturnType().getType());
181192

182193
if (skip && logger.isDebugEnabled()) {
183194
logger.debug("Skipping AOT generation for [%s]. Method is either returning an array or a geo-near, regex query"
@@ -187,14 +198,14 @@ private static boolean backoff(MongoQueryMethod method) {
187198
}
188199

189200
private static MethodContributor<MongoQueryMethod> nearQueryMethodContributor(MongoQueryMethod queryMethod,
190-
NearQueryInteraction interaction) {
201+
NearQueryInteraction interaction) {
191202

192203
return MethodContributor.forQueryMethod(queryMethod).withMetadata(interaction).contribute(context -> {
193204

194205
CodeBlock.Builder builder = CodeBlock.builder();
195206

196207
builder.add(geoNearBlockBuilder(context, queryMethod).usingQueryVariableName("nearQuery").build());
197-
// builder.add(aggregationExecutionBlockBuilder(context, queryMethod).referencing("aggregation").build());
208+
// builder.add(aggregationExecutionBlockBuilder(context, queryMethod).referencing("aggregation").build());
198209

199210
return builder.build();
200211
});

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
import org.jspecify.annotations.NullUnmarked;
7272
import org.jspecify.annotations.Nullable;
7373
import org.springframework.core.convert.converter.Converter;
74-
import org.springframework.data.geo.Circle;
7574
import org.springframework.data.mongodb.CodecRegistryProvider;
7675
import org.springframework.data.mongodb.core.mapping.FieldName;
7776
import org.springframework.data.mongodb.core.mapping.FieldName.Type;
@@ -1094,17 +1093,18 @@ public GeoCommand decode(BsonReader reader, DecoderContext decoderContext) {
10941093
public void encode(BsonWriter writer, GeoCommand value, EncoderContext encoderContext) {
10951094

10961095
if (writer instanceof SpringJsonWriter sjw) {
1097-
writer.writeStartDocument();
1098-
writer.writeName(value.getCommand());
1099-
if (value.getShape() instanceof Placeholder p) { // maybe we should wrap input to use geo command object
1100-
sjw.writePlaceholder(p.toString());
1101-
// Circle c = null;
1102-
// List.of(c.getCenter(), c.getRadius())
1103-
// ;
1104-
1105-
// createQuery("{'location.coordinates':{'$geoWithin':{'$center':?0}}}", new Object[]{ List.of(circle.getCenter(), circle.getRadius()))
1096+
if (!value.getCommand().equals("$geometry")) {
1097+
writer.writeStartDocument();
1098+
writer.writeName(value.getCommand());
1099+
if (value.getShape() instanceof Placeholder p) { // maybe we should wrap input to use geo command object
1100+
sjw.writePlaceholder(p.toString());
1101+
}
1102+
writer.writeEndDocument();
1103+
} else {
1104+
if (value.getShape() instanceof Placeholder p) { // maybe we should wrap input to use geo command object
1105+
sjw.writePlaceholder(p.toString());
1106+
}
11061107
}
1107-
writer.writeEndDocument();
11081108
} else {
11091109
writer.writeString(value.getCommand(), value.getShape().toString());
11101110
}

spring-data-mongodb/src/test/java/example/aot/UserRepository.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.data.geo.Point;
4343
import org.springframework.data.geo.Polygon;
4444
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
45+
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
4546
import org.springframework.data.mongodb.repository.Aggregation;
4647
import org.springframework.data.mongodb.repository.Hint;
4748
import org.springframework.data.mongodb.repository.Person;
@@ -121,14 +122,16 @@ public interface UserRepository extends CrudRepository<User, String> {
121122

122123
List<User> findByLocationCoordinatesWithin(Polygon polygon);
123124

125+
List<User> findByLocationCoordinatesWithin(GeoJsonPolygon polygon);
126+
124127
GeoResults<User> findByLocationCoordinatesNear(Point point, Distance maxDistance);
125128

126129
List<GeoResult<User>> findUserAsListByLocationCoordinatesNear(Point point, Distance maxDistance);
127130

128131
GeoResults<User> findByLocationCoordinatesNear(Point point, Range<Distance> distance);
129132

130133
GeoPage<User> findByLocationCoordinatesNear(Point point, Distance maxDistance, Pageable pageable);
131-
134+
132135
// TODO: TextSearch
133136

134137
/* Annotated Queries */

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/aot/MongoRepositoryContributorTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.bson.Document;
3232
import org.junit.jupiter.api.BeforeAll;
3333
import org.junit.jupiter.api.BeforeEach;
34+
import org.junit.jupiter.api.Disabled;
3435
import org.junit.jupiter.api.Test;
3536
import org.junit.jupiter.api.extension.ExtendWith;
3637
import org.springframework.beans.factory.annotation.Autowired;
@@ -59,6 +60,7 @@
5960
import org.springframework.data.mongodb.core.MongoTemplate;
6061
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
6162
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
63+
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
6264
import org.springframework.data.mongodb.test.util.Client;
6365
import org.springframework.data.mongodb.test.util.MongoClientExtension;
6466
import org.springframework.data.mongodb.test.util.MongoTestUtils;
@@ -654,6 +656,19 @@ void findsPeopleByLocationWithinPolygon() {
654656
assertThat(result).extracting(User::getUsername).containsExactly("leia", "vader");
655657
}
656658

659+
@Test
660+
void findsPeopleByLocationWithinGeoJsonPolygon() {
661+
662+
Point first = new Point(-78.99171, 35.738868);
663+
Point second = new Point(-78.99171, 45.738868);
664+
Point third = new Point(-68.99171, 45.738868);
665+
Point fourth = new Point(-68.99171, 35.738868);
666+
667+
List<User> result = fragment
668+
.findByLocationCoordinatesWithin(new GeoJsonPolygon(first, second, third, fourth, first));
669+
assertThat(result).extracting(User::getUsername).containsExactly("leia", "vader");
670+
}
671+
657672
@Test
658673
void testNearWithGeoResult() {
659674

@@ -680,6 +695,7 @@ void testNearWithRange() {
680695
}
681696

682697
@Test
698+
@Disabled("too complicated")
683699
void testNearReturningGeoPage() {
684700

685701
// TODO: still need to create the count and extract the total elements

0 commit comments

Comments
 (0)