From 5faf54b67c9b38f2d45a1c88c69c5339d0a3a49b Mon Sep 17 00:00:00 2001 From: Franck MARCHAND Date: Tue, 3 Jun 2014 01:13:40 +0200 Subject: [PATCH] DATAES-89 - implements missing "within" in ElasticsearchQueryCreator --- .../core/CriteriaFilterProcessor.java | 55 ++++++++++++++++--- .../data/elasticsearch/core/geo/GeoPoint.java | 14 +++++ .../elasticsearch/core/query/Criteria.java | 17 ++++++ .../parser/ElasticsearchQueryCreator.java | 18 ++++++ .../elasticsearch/entities/SampleEntity.java | 12 ++++ .../CustomMethodRepositoryTests.java | 45 +++++++++++++++ .../custom/SampleCustomMethodRepository.java | 7 +++ 7 files changed, 159 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/CriteriaFilterProcessor.java b/src/main/java/org/springframework/data/elasticsearch/core/CriteriaFilterProcessor.java index fde394713..d02763f1f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/CriteriaFilterProcessor.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/CriteriaFilterProcessor.java @@ -29,6 +29,9 @@ import org.elasticsearch.index.query.GeoDistanceFilterBuilder; import org.springframework.data.elasticsearch.core.geo.GeoBox; import org.springframework.data.elasticsearch.core.geo.GeoPoint; import org.springframework.data.elasticsearch.core.query.Criteria; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Metrics; +import org.springframework.data.geo.Point; import org.springframework.util.Assert; /** @@ -106,20 +109,31 @@ class CriteriaFilterProcessor { Object[] valArray = (Object[]) value; Assert.noNullElements(valArray, "Geo distance filter takes 2 not null elements array as parameter."); Assert.isTrue(valArray.length == 2, "Geo distance filter takes a 2-elements array as parameter."); - Assert.isTrue(valArray[0] instanceof GeoPoint || valArray[0] instanceof String, "First element of a geo distance filter must be a GeoLocation or String"); - Assert.isTrue(valArray[1] instanceof String, "Second element of a geo distance filter must be a String"); + 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"); + Assert.isTrue(valArray[1] instanceof String || valArray[1] instanceof Distance, "Second element of a geo distance filter must be a String or a Distance"); - String dist = (String) valArray[1]; - if (valArray[0] instanceof GeoPoint) { - GeoPoint loc = (GeoPoint) valArray[0]; - ((GeoDistanceFilterBuilder) filter).lat(loc.getLat()).lon(loc.getLon()).distance(dist); + + StringBuilder dist = new StringBuilder(); + + if(valArray[1] instanceof Distance) { + extractDistanceString((Distance)valArray[1], dist); + } else { + dist.append((String) valArray[1]); + } + + if (valArray[0] instanceof GeoPoint) { + GeoPoint loc = (GeoPoint) valArray[0]; + ((GeoDistanceFilterBuilder) filter).lat(loc.getLat()).lon(loc.getLon()).distance(dist.toString()); + } else if (valArray[0] instanceof Point) { + GeoPoint loc = GeoPoint.fromPoint((Point)valArray[0]); + ((GeoDistanceFilterBuilder) filter).lat(loc.getLat()).lon(loc.getLon()).distance(dist.toString()); } else { String loc = (String) valArray[0]; if (loc.contains(",")) { String c[] = loc.split(","); - ((GeoDistanceFilterBuilder) filter).lat(Double.parseDouble(c[0])).lon(Double.parseDouble(c[1])).distance(dist); + ((GeoDistanceFilterBuilder) filter).lat(Double.parseDouble(c[0])).lon(Double.parseDouble(c[1])).distance(dist.toString()); } else { - ((GeoDistanceFilterBuilder) filter).geohash(loc).distance(dist); + ((GeoDistanceFilterBuilder) filter).geohash(loc).distance(dist.toString()); } } @@ -151,7 +165,30 @@ class CriteriaFilterProcessor { return filter; } - private void oneParameterBBox(GeoBoundingBoxFilterBuilder filter, Object value) { + + /** + * extract the distance string from a {@link org.springframework.data.geo.Distance} object. + * + * @param distance distance object to extract string from + * @param sb StringBuilder to build the distance string + */ + private void extractDistanceString(Distance distance, StringBuilder sb) { + // handle Distance object + sb.append((int) distance.getValue()); + + Metrics metric = (Metrics) distance.getMetric(); + + switch (metric) { + case KILOMETERS : + sb.append("km"); + break; + case MILES: + sb.append("mi"); + break; + } + } + + private void oneParameterBBox(GeoBoundingBoxFilterBuilder filter, Object value) { Assert.isTrue(value instanceof GeoBox, "single-element of boundedBy filter must be type of GeoBox"); GeoBox geoBBox = (GeoBox) value; filter.topLeft(geoBBox.getTopLeft().getLat(), geoBBox.getTopLeft().getLon()); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/geo/GeoPoint.java b/src/main/java/org/springframework/data/elasticsearch/core/geo/GeoPoint.java index 116eb623a..afdba0cc9 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/geo/GeoPoint.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/geo/GeoPoint.java @@ -15,6 +15,8 @@ */ package org.springframework.data.elasticsearch.core.geo; +import org.springframework.data.geo.Point; + /** * geo-location used for #{@link org.springframework.data.elasticsearch.core.query.Criteria}. * @@ -41,4 +43,16 @@ public class GeoPoint { public double getLon() { return lon; } + + /** + * build a GeoPoint from a {@link org.springframework.data.geo.Point} + * + * @param point {@link org.springframework.data.geo.Point} + * @return a {@link org.springframework.data.elasticsearch.core.geo.GeoPoint} + */ + public static GeoPoint fromPoint(Point point) { + return new GeoPoint(point.getY(), point.getX()); + } } + + diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java b/src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java index 3beefe4b8..d4f1bcec3 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java @@ -21,6 +21,8 @@ import org.apache.commons.lang.StringUtils; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.elasticsearch.core.geo.GeoBox; import org.springframework.data.elasticsearch.core.geo.GeoPoint; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Point; import org.springframework.util.Assert; /** @@ -360,6 +362,21 @@ public class Criteria { return this; } + /** + * Creates new CriteriaEntry for {@code location WITHIN distance} + * + * @param location {@link org.springframework.data.geo.Point} center coordinates + * @param distance {@link org.springframework.data.geo.Distance} radius + * . + * @return Criteria the chaind criteria with the new 'within' criteria included. + */ + public Criteria within(Point location, Distance distance) { + Assert.notNull(location, "Location value for near criteria must not be null"); + Assert.notNull(location, "Distance value for near criteria must not be null"); + filterCriteria.add(new CriteriaEntry(OperationKey.WITHIN, new Object[]{location, distance})); + return this; + } + /** * Creates new CriteriaEntry for {@code geoLocation WITHIN distance} * diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/parser/ElasticsearchQueryCreator.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/parser/ElasticsearchQueryCreator.java index 38c47c4dd..97c1955ba 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/parser/ElasticsearchQueryCreator.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/parser/ElasticsearchQueryCreator.java @@ -20,9 +20,12 @@ import java.util.Iterator; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Sort; +import org.springframework.data.elasticsearch.core.geo.GeoPoint; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.elasticsearch.core.query.Criteria; import org.springframework.data.elasticsearch.core.query.CriteriaQuery; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Point; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.PersistentPropertyPath; import org.springframework.data.repository.query.ParameterAccessor; @@ -120,6 +123,21 @@ public class ElasticsearchQueryCreator extends AbstractQueryCreator page = repository.findByLocationWithin(new GeoPoint(45.7806d, 3.0875d), "2km", new PageRequest(0, 10)); + // then + assertThat(page, is(notNullValue())); + assertThat(page.getTotalElements(), is(equalTo(1L))); + } + + @Test + public void shouldExecuteCustomMethodWithWithinPoint() { + // given + String documentId = randomNumeric(5); + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setId(documentId); + sampleEntity.setType("test"); + sampleEntity.setRate(10); + sampleEntity.setMessage("foo"); + sampleEntity.setLocation(new GeoPoint(45.7806d, 3.0875d)); + + repository.save(sampleEntity); + + // when + Page page = repository.findByLocationWithin(new Point(3.0875d, 45.7806d), new Distance(2, Metrics.KILOMETERS), new PageRequest(0, 10)); + // then + assertThat(page, is(notNullValue())); + assertThat(page.getTotalElements(), is(equalTo(1L))); + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/custom/SampleCustomMethodRepository.java b/src/test/java/org/springframework/data/elasticsearch/repositories/custom/SampleCustomMethodRepository.java index b6e1f9bd5..a7ca3856a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/custom/SampleCustomMethodRepository.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/custom/SampleCustomMethodRepository.java @@ -20,8 +20,11 @@ import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.annotations.Query; +import org.springframework.data.elasticsearch.core.geo.GeoPoint; import org.springframework.data.elasticsearch.entities.SampleEntity; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Point; /** * @author Rizwan Idrees @@ -64,4 +67,8 @@ public interface SampleCustomMethodRepository extends ElasticsearchRepository findByAvailableFalse(Pageable pageable); Page findByMessageOrderByTypeAsc(String message, Pageable pageable); + + Page findByLocationWithin(GeoPoint point, String distance, Pageable pageable); + + Page findByLocationWithin(Point point, Distance distance, Pageable pageable); }