Skip to content

Commit 5faf54b

Browse files
committed
DATAES-89 - implements missing "within" in ElasticsearchQueryCreator
1 parent cb79831 commit 5faf54b

File tree

7 files changed

+160
-10
lines changed

7 files changed

+160
-10
lines changed

src/main/java/org/springframework/data/elasticsearch/core/CriteriaFilterProcessor.java

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
import org.springframework.data.elasticsearch.core.geo.GeoBox;
3030
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
3131
import org.springframework.data.elasticsearch.core.query.Criteria;
32+
import org.springframework.data.geo.Distance;
33+
import org.springframework.data.geo.Metrics;
34+
import org.springframework.data.geo.Point;
3235
import org.springframework.util.Assert;
3336

3437
/**
@@ -106,20 +109,31 @@ private FilterBuilder processCriteriaEntry(OperationKey key, Object value, Strin
106109
Object[] valArray = (Object[]) value;
107110
Assert.noNullElements(valArray, "Geo distance filter takes 2 not null elements array as parameter.");
108111
Assert.isTrue(valArray.length == 2, "Geo distance filter takes a 2-elements array as parameter.");
109-
Assert.isTrue(valArray[0] instanceof GeoPoint || valArray[0] instanceof String, "First element of a geo distance filter must be a GeoLocation or String");
110-
Assert.isTrue(valArray[1] instanceof String, "Second element of a geo distance filter must be a String");
111-
112-
String dist = (String) valArray[1];
113-
if (valArray[0] instanceof GeoPoint) {
114-
GeoPoint loc = (GeoPoint) valArray[0];
115-
((GeoDistanceFilterBuilder) filter).lat(loc.getLat()).lon(loc.getLon()).distance(dist);
112+
Assert.isTrue(valArray[0] instanceof GeoPoint || valArray[0] instanceof String || valArray[0] instanceof Point, "First element of a geo distance filter must be a GeoPoint, a Point or a String");
113+
Assert.isTrue(valArray[1] instanceof String || valArray[1] instanceof Distance, "Second element of a geo distance filter must be a String or a Distance");
114+
115+
116+
StringBuilder dist = new StringBuilder();
117+
118+
if(valArray[1] instanceof Distance) {
119+
extractDistanceString((Distance)valArray[1], dist);
120+
} else {
121+
dist.append((String) valArray[1]);
122+
}
123+
124+
if (valArray[0] instanceof GeoPoint) {
125+
GeoPoint loc = (GeoPoint) valArray[0];
126+
((GeoDistanceFilterBuilder) filter).lat(loc.getLat()).lon(loc.getLon()).distance(dist.toString());
127+
} else if (valArray[0] instanceof Point) {
128+
GeoPoint loc = GeoPoint.fromPoint((Point)valArray[0]);
129+
((GeoDistanceFilterBuilder) filter).lat(loc.getLat()).lon(loc.getLon()).distance(dist.toString());
116130
} else {
117131
String loc = (String) valArray[0];
118132
if (loc.contains(",")) {
119133
String c[] = loc.split(",");
120-
((GeoDistanceFilterBuilder) filter).lat(Double.parseDouble(c[0])).lon(Double.parseDouble(c[1])).distance(dist);
134+
((GeoDistanceFilterBuilder) filter).lat(Double.parseDouble(c[0])).lon(Double.parseDouble(c[1])).distance(dist.toString());
121135
} else {
122-
((GeoDistanceFilterBuilder) filter).geohash(loc).distance(dist);
136+
((GeoDistanceFilterBuilder) filter).geohash(loc).distance(dist.toString());
123137
}
124138
}
125139

@@ -151,7 +165,30 @@ private FilterBuilder processCriteriaEntry(OperationKey key, Object value, Strin
151165
return filter;
152166
}
153167

154-
private void oneParameterBBox(GeoBoundingBoxFilterBuilder filter, Object value) {
168+
169+
/**
170+
* extract the distance string from a {@link org.springframework.data.geo.Distance} object.
171+
*
172+
* @param distance distance object to extract string from
173+
* @param sb StringBuilder to build the distance string
174+
*/
175+
private void extractDistanceString(Distance distance, StringBuilder sb) {
176+
// handle Distance object
177+
sb.append((int) distance.getValue());
178+
179+
Metrics metric = (Metrics) distance.getMetric();
180+
181+
switch (metric) {
182+
case KILOMETERS :
183+
sb.append("km");
184+
break;
185+
case MILES:
186+
sb.append("mi");
187+
break;
188+
}
189+
}
190+
191+
private void oneParameterBBox(GeoBoundingBoxFilterBuilder filter, Object value) {
155192
Assert.isTrue(value instanceof GeoBox, "single-element of boundedBy filter must be type of GeoBox");
156193
GeoBox geoBBox = (GeoBox) value;
157194
filter.topLeft(geoBBox.getTopLeft().getLat(), geoBBox.getTopLeft().getLon());

src/main/java/org/springframework/data/elasticsearch/core/geo/GeoPoint.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.elasticsearch.core.geo;
1717

18+
import org.springframework.data.geo.Point;
19+
1820
/**
1921
* geo-location used for #{@link org.springframework.data.elasticsearch.core.query.Criteria}.
2022
*
@@ -41,4 +43,16 @@ public double getLat() {
4143
public double getLon() {
4244
return lon;
4345
}
46+
47+
/**
48+
* build a GeoPoint from a {@link org.springframework.data.geo.Point}
49+
*
50+
* @param point {@link org.springframework.data.geo.Point}
51+
* @return a {@link org.springframework.data.elasticsearch.core.geo.GeoPoint}
52+
*/
53+
public static GeoPoint fromPoint(Point point) {
54+
return new GeoPoint(point.getY(), point.getX());
55+
}
4456
}
57+
58+

src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.springframework.dao.InvalidDataAccessApiUsageException;
2222
import org.springframework.data.elasticsearch.core.geo.GeoBox;
2323
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
24+
import org.springframework.data.geo.Distance;
25+
import org.springframework.data.geo.Point;
2426
import org.springframework.util.Assert;
2527

2628
/**
@@ -360,6 +362,21 @@ public Criteria within(GeoPoint location, String distance) {
360362
return this;
361363
}
362364

365+
/**
366+
* Creates new CriteriaEntry for {@code location WITHIN distance}
367+
*
368+
* @param location {@link org.springframework.data.geo.Point} center coordinates
369+
* @param distance {@link org.springframework.data.geo.Distance} radius
370+
* .
371+
* @return Criteria the chaind criteria with the new 'within' criteria included.
372+
*/
373+
public Criteria within(Point location, Distance distance) {
374+
Assert.notNull(location, "Location value for near criteria must not be null");
375+
Assert.notNull(location, "Distance value for near criteria must not be null");
376+
filterCriteria.add(new CriteriaEntry(OperationKey.WITHIN, new Object[]{location, distance}));
377+
return this;
378+
}
379+
363380
/**
364381
* Creates new CriteriaEntry for {@code geoLocation WITHIN distance}
365382
*

src/main/java/org/springframework/data/elasticsearch/repository/query/parser/ElasticsearchQueryCreator.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020

2121
import org.springframework.dao.InvalidDataAccessApiUsageException;
2222
import org.springframework.data.domain.Sort;
23+
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
2324
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
2425
import org.springframework.data.elasticsearch.core.query.Criteria;
2526
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
27+
import org.springframework.data.geo.Distance;
28+
import org.springframework.data.geo.Point;
2629
import org.springframework.data.mapping.context.MappingContext;
2730
import org.springframework.data.mapping.context.PersistentPropertyPath;
2831
import org.springframework.data.repository.query.ParameterAccessor;
@@ -120,6 +123,21 @@ private Criteria from(Part.Type type, Criteria instance, Iterator<?> parameters)
120123
return criteria.in(asArray(parameters.next()));
121124
case NOT_IN:
122125
return criteria.in(asArray(parameters.next())).not();
126+
case WITHIN: {
127+
Object firstParameter = parameters.next();
128+
Object secondParameter = parameters.next();
129+
130+
if(firstParameter instanceof GeoPoint && secondParameter instanceof String)
131+
return criteria.within((GeoPoint)firstParameter, (String)secondParameter);
132+
133+
if(firstParameter instanceof Point && secondParameter instanceof Distance)
134+
return criteria.within((Point)firstParameter, (Distance)secondParameter);
135+
136+
137+
if(firstParameter instanceof String && secondParameter instanceof String)
138+
return criteria.within((String)firstParameter, (String)secondParameter);
139+
}
140+
123141
default:
124142
throw new InvalidDataAccessApiUsageException("Illegal criteria found '" + type + "'.");
125143
}

src/test/java/org/springframework/data/elasticsearch/entities/SampleEntity.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.springframework.data.annotation.Id;
2121
import org.springframework.data.annotation.Version;
2222
import org.springframework.data.elasticsearch.annotations.Document;
23+
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
2324

2425
/**
2526
* @author Rizwan Idrees
@@ -35,6 +36,9 @@ public class SampleEntity {
3536
private int rate;
3637
private boolean available;
3738
private String highlightedMessage;
39+
40+
private GeoPoint location;
41+
3842
@Version
3943
private Long version;
4044

@@ -86,6 +90,14 @@ public void setHighlightedMessage(String highlightedMessage) {
8690
this.highlightedMessage = highlightedMessage;
8791
}
8892

93+
public GeoPoint getLocation() {
94+
return location;
95+
}
96+
97+
public void setLocation(GeoPoint location) {
98+
this.location = location;
99+
}
100+
89101
public Long getVersion() {
90102
return version;
91103
}

src/test/java/org/springframework/data/elasticsearch/repositories/CustomMethodRepositoryTests.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@
3030
import org.springframework.data.domain.PageRequest;
3131
import org.springframework.data.domain.Sort;
3232
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
33+
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
3334
import org.springframework.data.elasticsearch.entities.SampleEntity;
3435
import org.springframework.data.elasticsearch.repositories.custom.SampleCustomMethodRepository;
36+
import org.springframework.data.geo.Distance;
37+
import org.springframework.data.geo.Metrics;
38+
import org.springframework.data.geo.Point;
3539
import org.springframework.test.context.ContextConfiguration;
3640
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
3741

@@ -53,6 +57,7 @@ public class CustomMethodRepositoryTests {
5357
public void before() {
5458
elasticsearchTemplate.deleteIndex(SampleEntity.class);
5559
elasticsearchTemplate.createIndex(SampleEntity.class);
60+
elasticsearchTemplate.putMapping(SampleEntity.class);
5661
elasticsearchTemplate.refresh(SampleEntity.class, true);
5762
}
5863

@@ -486,4 +491,44 @@ public void shouldReturnListForMessage() {
486491
assertThat(sampleEntities.isEmpty(), is(false));
487492
assertThat(sampleEntities.size(), is(1));
488493
}
494+
495+
@Test
496+
public void shouldExecuteCustomMethodWithWithinGeoPoint() {
497+
// given
498+
String documentId = randomNumeric(5);
499+
SampleEntity sampleEntity = new SampleEntity();
500+
sampleEntity.setId(documentId);
501+
sampleEntity.setType("test");
502+
sampleEntity.setRate(10);
503+
sampleEntity.setMessage("foo");
504+
sampleEntity.setLocation(new GeoPoint(45.7806d, 3.0875d));
505+
506+
repository.save(sampleEntity);
507+
508+
// when
509+
Page<SampleEntity> page = repository.findByLocationWithin(new GeoPoint(45.7806d, 3.0875d), "2km", new PageRequest(0, 10));
510+
// then
511+
assertThat(page, is(notNullValue()));
512+
assertThat(page.getTotalElements(), is(equalTo(1L)));
513+
}
514+
515+
@Test
516+
public void shouldExecuteCustomMethodWithWithinPoint() {
517+
// given
518+
String documentId = randomNumeric(5);
519+
SampleEntity sampleEntity = new SampleEntity();
520+
sampleEntity.setId(documentId);
521+
sampleEntity.setType("test");
522+
sampleEntity.setRate(10);
523+
sampleEntity.setMessage("foo");
524+
sampleEntity.setLocation(new GeoPoint(45.7806d, 3.0875d));
525+
526+
repository.save(sampleEntity);
527+
528+
// when
529+
Page<SampleEntity> page = repository.findByLocationWithin(new Point(3.0875d, 45.7806d), new Distance(2, Metrics.KILOMETERS), new PageRequest(0, 10));
530+
// then
531+
assertThat(page, is(notNullValue()));
532+
assertThat(page.getTotalElements(), is(equalTo(1L)));
533+
}
489534
}

src/test/java/org/springframework/data/elasticsearch/repositories/custom/SampleCustomMethodRepository.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
import org.springframework.data.domain.Page;
2121
import org.springframework.data.domain.Pageable;
2222
import org.springframework.data.elasticsearch.annotations.Query;
23+
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
2324
import org.springframework.data.elasticsearch.entities.SampleEntity;
2425
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
26+
import org.springframework.data.geo.Distance;
27+
import org.springframework.data.geo.Point;
2528

2629
/**
2730
* @author Rizwan Idrees
@@ -64,4 +67,8 @@ public interface SampleCustomMethodRepository extends ElasticsearchRepository<Sa
6467
Page<SampleEntity> findByAvailableFalse(Pageable pageable);
6568

6669
Page<SampleEntity> findByMessageOrderByTypeAsc(String message, Pageable pageable);
70+
71+
Page<SampleEntity> findByLocationWithin(GeoPoint point, String distance, Pageable pageable);
72+
73+
Page<SampleEntity> findByLocationWithin(Point point, Distance distance, Pageable pageable);
6774
}

0 commit comments

Comments
 (0)