From 97dd38eb792624c489b6c294e362657430b449b7 Mon Sep 17 00:00:00 2001 From: Harry9656 Date: Sat, 9 Sep 2023 21:37:16 +0200 Subject: [PATCH] BAEL-6538: Geospatial Support in ElasticSearch (#14554) * Migrated to spring-boot and created the first working test. * JAVA-6538: Refactor Java GeoQueries to Java Client for Ealastic. --- .../spring-data-elasticsearch/pom.xml | 71 ++-- .../main/java/com/baeldung/Application.java | 12 + .../com/baeldung/elasticsearch/Location.java | 16 + .../com/baeldung/elasticsearch/Person.java | 2 - .../ElasticSearchManualTest.java | 154 ++++----- .../elasticsearch/GeoQueriesManualTest.java | 323 +++++++++--------- 6 files changed, 292 insertions(+), 286 deletions(-) create mode 100644 persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/Application.java create mode 100644 persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/elasticsearch/Location.java diff --git a/persistence-modules/spring-data-elasticsearch/pom.xml b/persistence-modules/spring-data-elasticsearch/pom.xml index 8e5e8ab7c7..a353f60ad2 100644 --- a/persistence-modules/spring-data-elasticsearch/pom.xml +++ b/persistence-modules/spring-data-elasticsearch/pom.xml @@ -8,77 +8,56 @@ com.baeldung - parent-spring-5 + parent-boot-3 0.0.1-SNAPSHOT - ../../parent-spring-5 + ../../parent-boot-3 - - org.springframework - spring-web - ${spring.version} - org.springframework.data spring-data-elasticsearch ${spring-data-elasticsearch.version} - - org.elasticsearch - elasticsearch - ${elasticsearch.version} - - - com.alibaba - fastjson - ${fastjson.version} - + + + + + + + + + + + + org.elasticsearch.client elasticsearch-rest-high-level-client 7.17.11 - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - org.projectlombok lombok 1.18.28 - org.locationtech.spatial4j - spatial4j - ${spatial4j.version} - - - org.locationtech.jts - jts-core - ${jts.version} - - - xerces - xercesImpl - - - - - org.springframework - spring-test - ${spring.version} - test + org.springframework.boot + spring-boot-autoconfigure - + + + + org.springframework.boot + spring-boot-maven-plugin + + + 5.1.2 8.9.0 - 2.0.37 - 0.8 - 1.18.2 2.15.2 diff --git a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/Application.java b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/Application.java new file mode 100644 index 0000000000..c95b7cb5cb --- /dev/null +++ b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/Application.java @@ -0,0 +1,12 @@ +package com.baeldung; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/elasticsearch/Location.java b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/elasticsearch/Location.java new file mode 100644 index 0000000000..cdc8f8b255 --- /dev/null +++ b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/elasticsearch/Location.java @@ -0,0 +1,16 @@ +package com.baeldung.elasticsearch; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Location { + + private String name; + private List location; +} diff --git a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/elasticsearch/Person.java b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/elasticsearch/Person.java index d51d1a0576..9a01af84e7 100644 --- a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/elasticsearch/Person.java +++ b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/elasticsearch/Person.java @@ -12,8 +12,6 @@ import java.util.Date; public class Person { private int age; - private String fullName; - private Date dateOfBirth; } diff --git a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/ElasticSearchManualTest.java b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/ElasticSearchManualTest.java index 0d4117c75e..e0656db04e 100644 --- a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/ElasticSearchManualTest.java +++ b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/ElasticSearchManualTest.java @@ -15,18 +15,18 @@ import co.elastic.clients.json.jackson.JacksonJsonpMapper; import co.elastic.clients.transport.ElasticsearchTransport; import co.elastic.clients.transport.rest_client.RestClientTransport; import lombok.extern.slf4j.Slf4j; + import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; -import org.junit.Before; -import org.junit.Test; -import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.StringReader; import java.util.Date; import java.util.List; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * This Manual test requires: Elasticsearch instance running on localhost:9200. @@ -35,41 +35,35 @@ import static org.junit.Assert.assertEquals; * docker run -d --name elastic-test -p 9200:9200 -e "discovery.type=single-node" -e "xpack.security.enabled=false" docker.elastic.co/elasticsearch/elasticsearch:8.9.0 */ @Slf4j -@Disabled("Manual test") public class ElasticSearchManualTest { private ElasticsearchClient client = null; - @Before + @BeforeEach public void setUp() throws IOException { - RestClient restClient = RestClient - .builder(HttpHost.create("http://localhost:9200")) - .build(); + RestClient restClient = RestClient.builder(HttpHost.create("http://localhost:9200")) + .build(); ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); client = new ElasticsearchClient(transport); Person person1 = new Person(10, "John Doe", new Date()); Person person2 = new Person(25, "Janette Doe", new Date()); Person person3 = new Person(8, "Mark Doe", new Date()); - client.index(builder -> builder - .index("person") - .id(person1.getFullName()) - .document(person1)); - client.index(builder -> builder - .index("person") - .id(person2.getFullName()) - .document(person2)); - client.index(builder -> builder - .index("person") - .id(person3.getFullName()) - .document(person3)); + client.index(builder -> builder.index("person") + .id(person1.getFullName()) + .document(person1)); + client.index(builder -> builder.index("person") + .id(person2.getFullName()) + .document(person2)); + client.index(builder -> builder.index("person") + .id(person3.getFullName()) + .document(person3)); } @Test public void givenJsonDocument_whenJavaObject_thenIndexDocument() throws Exception { Person person = new Person(20, "Mark Doe", new Date(1471466076564L)); - IndexResponse response = client.index(i -> i - .index("person") - .id(person.getFullName()) - .document(person)); + IndexResponse response = client.index(i -> i.index("person") + .id(person.getFullName()) + .document(person)); log.info("Indexed with version: {}", response.version()); assertEquals(Result.Created, response.result()); @@ -81,10 +75,9 @@ public class ElasticSearchManualTest { public void givenJsonString_whenJavaObject_thenIndexDocument() throws Exception { String jsonString = "{\"age\":10,\"dateOfBirth\":1471466076564,\"fullName\":\"John Doe\"}"; StringReader stringReader = new StringReader(jsonString); - IndexResponse response = client.index(i -> i - .index("person") - .id("John Doe") - .withJson(stringReader)); + IndexResponse response = client.index(i -> i.index("person") + .id("John Doe") + .withJson(stringReader)); log.info("Indexed with version: {}", response.version()); assertEquals("person", response.index()); assertEquals("John Doe", response.id()); @@ -93,9 +86,8 @@ public class ElasticSearchManualTest { @Test public void givenDocumentId_whenJavaObject_thenDeleteDocument() throws Exception { String documentId = "Mark Doe"; - DeleteResponse response = client.delete(i -> i - .index("person") - .id(documentId)); + DeleteResponse response = client.delete(i -> i.index("person") + .id(documentId)); assertEquals(Result.Deleted, response.result()); assertEquals("Mark Doe", response.id()); } @@ -103,24 +95,23 @@ public class ElasticSearchManualTest { @Test public void givenSearchRequest_whenMatch_thenReturnAllResults() throws Exception { String searchText = "John"; - SearchResponse searchResponse = client.search(s -> s - .index("person") - .query(q -> q - .match(t -> t - .field("fullName") - .query(searchText))), Person.class); + SearchResponse searchResponse = client.search(s -> s.index("person") + .query(q -> q.match(t -> t.field("fullName") + .query(searchText))), Person.class); - List> hits = searchResponse.hits().hits(); + List> hits = searchResponse.hits() + .hits(); assertEquals(1, hits.size()); - assertEquals("John Doe", hits.get(0).source().getFullName()); + assertEquals("John Doe", hits.get(0) + .source() + .getFullName()); } @Test public void givenGetRequest_whenMatch_thenReturnAllResults() throws IOException { String documentId = "John Doe"; - GetResponse getResponse = client.get(s -> s - .index("person") - .id(documentId), Person.class); + GetResponse getResponse = client.get(s -> s.index("person") + .id(documentId), Person.class); Person source = getResponse.source(); assertEquals("John Doe", source.getFullName()); } @@ -128,47 +119,58 @@ public class ElasticSearchManualTest { @Test public void givenSearchRequest_whenMatchAndRange_thenReturnAllResults() throws Exception { String searchText = "John"; - SearchResponse searchResponse = client.search(s -> s - .index("person") - .query(q -> q - .match(t -> t - .field("fullName").query(searchText))) - .query(q -> q - .range(range -> range - .field("age").from("1").to("10"))), - Person.class); + SearchResponse searchResponse = client.search(s -> s.index("person") + .query(q -> q.match(t -> t.field("fullName") + .query(searchText))) + .query(q -> q.range(range -> range.field("age") + .from("1") + .to("10"))), Person.class); - List> hits = searchResponse.hits().hits(); + List> hits = searchResponse.hits() + .hits(); assertEquals(1, hits.size()); - assertEquals("John Doe", hits.get(0).source().getFullName()); + assertEquals("John Doe", hits.get(0) + .source() + .getFullName()); } - @Test public void givenMultipleQueries_thenReturnResults() throws Exception { - Query ageQuery = RangeQuery.of(r -> r.field("age").from("5").to("15"))._toQuery(); - SearchResponse response1 = client.search(s -> s.query(q -> q.bool(b -> b - .must(ageQuery))), Person.class); - response1.hits().hits().forEach(hit -> log.info("Response 1: {}", hit.source())); + Query ageQuery = RangeQuery.of(r -> r.field("age") + .from("5") + .to("15")) + ._toQuery(); + SearchResponse response1 = client.search(s -> s.query(q -> q.bool(b -> b.must(ageQuery))), Person.class); + response1.hits() + .hits() + .forEach(hit -> log.info("Response 1: {}", hit.source())); - Query fullNameQuery = MatchQuery.of(m -> m.field("fullName").query("John"))._toQuery(); - SearchResponse response2 = client.search(s -> s.query(q -> q.bool(b -> b - .must(fullNameQuery))), Person.class); - response2.hits().hits().forEach(hit -> log.info("Response 2: {}", hit.source())); - Query doeContainsQuery = SimpleQueryStringQuery.of(q -> q.query("*Doe"))._toQuery(); - SearchResponse response3 = client.search(s -> s.query(q -> q.bool(b -> b - .must(doeContainsQuery))), Person.class); - response3.hits().hits().forEach(hit -> log.info("Response 3: {}", hit.source())); + Query fullNameQuery = MatchQuery.of(m -> m.field("fullName") + .query("John")) + ._toQuery(); + SearchResponse response2 = client.search(s -> s.query(q -> q.bool(b -> b.must(fullNameQuery))), Person.class); + response2.hits() + .hits() + .forEach(hit -> log.info("Response 2: {}", hit.source())); + Query doeContainsQuery = SimpleQueryStringQuery.of(q -> q.query("*Doe")) + ._toQuery(); + SearchResponse response3 = client.search(s -> s.query(q -> q.bool(b -> b.must(doeContainsQuery))), Person.class); + response3.hits() + .hits() + .forEach(hit -> log.info("Response 3: {}", hit.source())); - Query simpleStringQuery = SimpleQueryStringQuery.of(q -> q.query("+John -Doe OR Janette"))._toQuery(); - SearchResponse response4 = client.search(s -> s.query(q -> q.bool(b -> b - .must(simpleStringQuery))), Person.class); - response4.hits().hits().forEach(hit -> log.info("Response 4: {}", hit.source())); + Query simpleStringQuery = SimpleQueryStringQuery.of(q -> q.query("+John -Doe OR Janette")) + ._toQuery(); + SearchResponse response4 = client.search(s -> s.query(q -> q.bool(b -> b.must(simpleStringQuery))), Person.class); + response4.hits() + .hits() + .forEach(hit -> log.info("Response 4: {}", hit.source())); - SearchResponse response5 = client.search(s -> s.query(q -> q.bool(b -> b - .must(ageQuery) - .must(fullNameQuery) - .must(simpleStringQuery))), Person.class); - response5.hits().hits().forEach(hit -> log.info("Response 5: {}", hit.source())); + SearchResponse response5 = client.search(s -> s.query(q -> q.bool(b -> b.must(ageQuery) + .must(fullNameQuery) + .must(simpleStringQuery))), Person.class); + response5.hits() + .hits() + .forEach(hit -> log.info("Response 5: {}", hit.source())); } } diff --git a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/GeoQueriesManualTest.java b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/GeoQueriesManualTest.java index abe10f4607..51e6ebca70 100644 --- a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/GeoQueriesManualTest.java +++ b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/GeoQueriesManualTest.java @@ -1,199 +1,198 @@ package com.baeldung.elasticsearch; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.GeoShapeRelation; +import co.elastic.clients.elasticsearch.core.IndexResponse; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; +import co.elastic.clients.json.JsonData; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; +import java.io.StringReader; import java.util.List; -import java.util.stream.Collectors; -import com.baeldung.spring.data.es.config.Config; +import lombok.extern.slf4j.Slf4j; -import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; -import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; -import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.action.index.IndexResponse; -import org.elasticsearch.action.search.SearchRequest; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.client.RequestOptions; -import org.elasticsearch.client.RestHighLevelClient; -import org.elasticsearch.client.indices.CreateIndexRequest; -import org.elasticsearch.common.geo.GeoPoint; -import org.elasticsearch.common.geo.ShapeRelation; -import org.elasticsearch.common.unit.DistanceUnit; -import org.elasticsearch.index.query.GeoShapeQueryBuilder; -import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.builder.SearchSourceBuilder; -import org.elasticsearch.xcontent.XContentType; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.locationtech.jts.geom.Coordinate; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; /** * This Manual test requires: Elasticsearch instance running on localhost:9200. - * - * The following docker command can be used: docker run -d --name es762 -p - * 9200:9200 -e "discovery.type=single-node" elasticsearch:7.6.2 + *

+ * The following docker command can be used: docker run -d --name elastic-test -p 9200:9200 -e + * "discovery.type=single-node" -e "xpack.security.enabled=false" + * docker.elastic.co/elasticsearch/elasticsearch:8.9.0 */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = Config.class) -public class GeoQueriesManualTest { + +@Slf4j +class GeoQueriesManualTest { private static final String WONDERS_OF_WORLD = "wonders-of-world"; - @Autowired - private RestHighLevelClient client; + private ElasticsearchClient client; - @Before + @BeforeEach public void setUp() throws Exception { - String jsonObject = "{\"properties\":{\"name\":{\"type\":\"text\",\"index\":false},\"region\":{\"type\":\"geo_shape\"},\"location\":{\"type\":\"geo_point\"}}}"; - - CreateIndexRequest req = new CreateIndexRequest(WONDERS_OF_WORLD); - req.mapping(jsonObject, XContentType.JSON); - + RestClient restClient = RestClient.builder(HttpHost.create("http://localhost:9200")) + .build(); + ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); + client = new ElasticsearchClient(transport); + log.info("Creating index: {}", WONDERS_OF_WORLD); client.indices() - .create(req, RequestOptions.DEFAULT); - } - -// @Test -// public void givenGeoShapeData_whenExecutedGeoShapeQuery_thenResultNonEmpty() throws IOException { -// String jsonObject = "{\"name\":\"Agra\",\"region\":{\"type\":\"envelope\",\"coordinates\":[[75,30.2],[80.1, 25]]}}"; -// IndexRequest indexRequest = new IndexRequest(WONDERS_OF_WORLD); -// indexRequest.source(jsonObject, XContentType.JSON); -// IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT); -// -// String tajMahalId = response.getId(); -// -// RefreshRequest refreshRequest = new RefreshRequest(WONDERS_OF_WORLD); -// client.indices() -// .refresh(refreshRequest, RequestOptions.DEFAULT); -// -// Coordinate topLeft = new Coordinate(74, 31.2); -// Coordinate bottomRight = new Coordinate(81.1, 24); -// -// GeoShapeQueryBuilder qb = QueryBuilders.geoShapeQuery("region", new EnvelopeBuilder(topLeft, bottomRight).buildGeometry()); -// qb.relation(ShapeRelation.INTERSECTS); -// -// SearchSourceBuilder source = new SearchSourceBuilder().query(qb); -// SearchRequest searchRequest = new SearchRequest(WONDERS_OF_WORLD); -// searchRequest.source(source); -// -// SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); -// -// List ids = Arrays.stream(searchResponse.getHits() -// .getHits()) -// .map(SearchHit::getId) -// .collect(Collectors.toList()); -// -// assertTrue(ids.contains(tajMahalId)); -// } - - @Test - public void givenGeoPointData_whenExecutedGeoBoundingBoxQuery_thenResultNonEmpty() throws Exception { - String jsonObject = "{\"name\":\"Pyramids of Giza\",\"location\":[31.131302,29.976480]}"; - - IndexRequest indexRequest = new IndexRequest(WONDERS_OF_WORLD); - indexRequest.source(jsonObject, XContentType.JSON); - IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT); - - String pyramidsOfGizaId = response.getId(); - - RefreshRequest refreshRequest = new RefreshRequest(WONDERS_OF_WORLD); - client.indices() - .refresh(refreshRequest, RequestOptions.DEFAULT); - - QueryBuilder qb = QueryBuilders.geoBoundingBoxQuery("location") - .setCorners(31, 30, 28, 32); - - SearchSourceBuilder source = new SearchSourceBuilder().query(qb); - SearchRequest searchRequest = new SearchRequest(WONDERS_OF_WORLD); - searchRequest.source(source); - - SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); - - List ids = Arrays.stream(searchResponse.getHits() - .getHits()) - .map(SearchHit::getId) - .collect(Collectors.toList()); - assertTrue(ids.contains(pyramidsOfGizaId)); + .create(builder -> builder.index(WONDERS_OF_WORLD) + .mappings(typeMapping -> typeMapping.properties("region", region -> region.geoShape(gs -> gs)) + .properties("location", location -> location.geoPoint(gp -> gp)))); } @Test - public void givenGeoPointData_whenExecutedGeoDistanceQuery_thenResultNonEmpty() throws Exception { - String jsonObject = "{\"name\":\"Lighthouse of alexandria\",\"location\":[31.131302,29.976480]}"; + void givenGeoShapeData_whenExecutedGeoShapeQuery_thenResultNonEmpty() throws IOException { + String jsonObject = """ + { + "name":"Agra", + "region":{ + "type":"envelope", + "coordinates":[[75,30.2],[80.1,25]] + } + } + """; + IndexResponse response = client.index(idx -> idx.index(WONDERS_OF_WORLD) + .withJson(new StringReader(jsonObject))); - IndexRequest indexRequest = new IndexRequest(WONDERS_OF_WORLD); - indexRequest.source(jsonObject, XContentType.JSON); - IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT); - - String lighthouseOfAlexandriaId = response.getId(); - - RefreshRequest refreshRequest = new RefreshRequest(WONDERS_OF_WORLD); + String tajMahalId = response.id(); client.indices() - .refresh(refreshRequest, RequestOptions.DEFAULT); + .refresh(); - QueryBuilder qb = QueryBuilders.geoDistanceQuery("location") - .point(29.976, 31.131) - .distance(10, DistanceUnit.MILES); + StringReader jsonData = new StringReader(""" + { + "type":"envelope", + "coordinates": [[74.0, 31.2], [81.1, 24.0 ] ] + } + """); - SearchSourceBuilder source = new SearchSourceBuilder().query(qb); - SearchRequest searchRequest = new SearchRequest(WONDERS_OF_WORLD); - searchRequest.source(source); - - SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); - - List ids = Arrays.stream(searchResponse.getHits() - .getHits()) - .map(SearchHit::getId) - .collect(Collectors.toList()); - assertTrue(ids.contains(lighthouseOfAlexandriaId)); + SearchRequest searchRequest = new SearchRequest.Builder().query(query -> query.bool(boolQuery -> boolQuery.filter(query1 -> query1.geoShape(geoShapeQuery -> geoShapeQuery.field("region") + .shape(geoShapeFieldQuery -> geoShapeFieldQuery.relation(GeoShapeRelation.Within) + .shape(JsonData.from(jsonData))))))) + .build(); + log.info("Search request: {}", searchRequest); + SearchResponse search = client.search(searchRequest, Object.class); + log.info("Search response: {}", search); + List searchResults = search.hits() + .hits() + .stream() + .map(Hit::id) + .toList(); + assertTrue(searchResults.contains(tajMahalId)); } @Test - public void givenGeoPointData_whenExecutedGeoPolygonQuery_thenResultNonEmpty() throws Exception { - String jsonObject = "{\"name\":\"The Great Rann of Kutch\",\"location\":[69.859741,23.733732]}"; + void givenGeoPointData_whenExecutedGeoBoundingBoxQuery_thenResultNonEmpty() throws Exception { + Location pyramidsOfGiza = new Location("Pyramids of Giza", List.of(31.1328, 29.9761)); + IndexResponse response = client.index(builder -> builder.index(WONDERS_OF_WORLD) + .document(pyramidsOfGiza)); - IndexRequest indexRequest = new IndexRequest(WONDERS_OF_WORLD); - indexRequest.source(jsonObject, XContentType.JSON); - IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT); + String pyramidsOfGizaId = response.id(); - String greatRannOfKutchid = response.getId(); - - RefreshRequest refreshRequest = new RefreshRequest(WONDERS_OF_WORLD); + log.info("Indexed pyramid of Giza: {}", pyramidsOfGizaId); client.indices() - .refresh(refreshRequest, RequestOptions.DEFAULT); + .refresh(); - List allPoints = new ArrayList(); - allPoints.add(new GeoPoint(22.733, 68.859)); - allPoints.add(new GeoPoint(24.733, 68.859)); - allPoints.add(new GeoPoint(23, 70.859)); - QueryBuilder qb = QueryBuilders.geoPolygonQuery("location", allPoints); - - SearchSourceBuilder source = new SearchSourceBuilder().query(qb); - SearchRequest searchRequest = new SearchRequest(WONDERS_OF_WORLD); - searchRequest.source(source); - - SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); - - List ids = Arrays.stream(searchResponse.getHits() - .getHits()) - .map(SearchHit::getId) - .collect(Collectors.toList()); - assertTrue(ids.contains(greatRannOfKutchid)); + SearchRequest.Builder builder = new SearchRequest.Builder().index(WONDERS_OF_WORLD); + builder.query(query -> query.geoBoundingBox(geoBoundingBoxQuery -> geoBoundingBoxQuery.field("location") + .boundingBox(geoBounds -> geoBounds.tlbr(bl4 -> bl4.topLeft(geoLocation -> geoLocation.coords(List.of(30.0, 31.0))) + .bottomRight(geoLocation -> geoLocation.coords(List.of(32.0, 28.0))))))); + SearchRequest build = builder.build(); + log.info("Search request: {}", build); + SearchResponse searchResponse = client.search(build, Location.class); + log.info("Search response: {}", searchResponse); + List returnedLocations = searchResponse.hits() + .hits() + .stream() + .map(Hit::source) + .toList(); + assertEquals(pyramidsOfGiza, returnedLocations.get(0)); } - @After + @Test + void givenGeoPointData_whenExecutedGeoDistanceQuery_thenResultNonEmpty() throws Exception { + String jsonObject = """ + { + "name":"Lighthouse of alexandria", + "location":{ "lat": 31.2139, "lon": 29.8856 } + } + """; + IndexResponse response = client.index(idx -> idx.index(WONDERS_OF_WORLD) + .withJson(new StringReader(jsonObject))); + + String lightHouseOfAlexandriaId = response.id(); + client.indices() + .refresh(); + SearchRequest searchRequest = new SearchRequest.Builder().index(WONDERS_OF_WORLD) + .query(query -> query.geoDistance(geoDistanceQuery -> geoDistanceQuery.field("location") + .distance("10 miles") + .location(geoLocation -> geoLocation.latlon(latLonGeoLocation -> latLonGeoLocation.lon(29.88) + .lat(31.21))))) + .build(); + log.info("Search request: {}", searchRequest); + SearchResponse search = client.search(searchRequest, Object.class); + log.info("Search response: {}", search); + List ids = search.hits() + .hits() + .stream() + .map(Hit::id) + .toList(); + assertTrue(ids.contains(lightHouseOfAlexandriaId)); + } + + @Test + void givenGeoPointData_whenExecutedGeoPolygonQuery_thenResultNonEmpty() throws Exception { + String jsonObject = """ + { + "name":"The Great Rann polygonPoints Kutch", + "location":{"lon": 69.859741, "lat": 23.733732} + } + """; + IndexResponse response = client.index(idx -> idx.index(WONDERS_OF_WORLD) + .withJson(new StringReader(jsonObject))); + String greatRannOfKutchid = response.id(); + client.indices() + .refresh(); + log.info("Indexed greatRannOfKutchid: {}", greatRannOfKutchid); + + JsonData jsonData = JsonData.fromJson(""" + { + "type":"polygon", + "coordinates":[[[68.859,22.733],[68.859,24.733],[70.859,23]]] + } + """); + + SearchRequest build = new SearchRequest.Builder().query(query -> query.bool(boolQuery -> boolQuery.filter(query1 -> query1.geoShape(geoShapeQuery -> geoShapeQuery.field("location") + .shape(geoShapeFieldQuery -> geoShapeFieldQuery.relation(GeoShapeRelation.Within) + .shape(jsonData)))))) + .build(); + log.info("Search request: {}", build); + SearchResponse search = client.search(build, Object.class); + log.info("Search response: {}", search); + List searchResults = search.hits() + .hits() + .stream() + .map(Hit::id) + .toList(); + assertTrue(searchResults.contains(greatRannOfKutchid)); + } + + @AfterEach public void destroy() throws Exception { - DeleteIndexRequest deleteIndex = new DeleteIndexRequest(WONDERS_OF_WORLD); client.indices() - .delete(deleteIndex, RequestOptions.DEFAULT); + .delete(builder -> builder.index(WONDERS_OF_WORLD)); } }