1
0
mirror of synced 2026-05-24 05:03:17 +00:00

Compare commits

..

92 Commits

Author SHA1 Message Date
Mark Paluch f98047dd11 Release version 5.1.12 (2023.0.12).
See #2894
2024-05-17 10:41:59 +02:00
Mark Paluch 434e31c37c Prepare 5.1.12 (2023.0.12).
See #2894
2024-05-17 10:41:50 +02:00
Mark Paluch bfa811d866 After release cleanups.
See #2872
2024-04-12 09:34:53 +02:00
Mark Paluch 1880534274 Prepare next development iteration.
See #2872
2024-04-12 09:34:52 +02:00
Mark Paluch 83ba449448 Release version 5.1.11 (2023.0.11).
See #2872
2024-04-12 09:32:44 +02:00
Mark Paluch 814b468915 Prepare 5.1.11 (2023.0.11).
See #2872
2024-04-12 09:32:35 +02:00
Mark Paluch 0a88988247 After release cleanups.
See #2849
2024-03-15 09:28:54 +01:00
Mark Paluch 57c70da88c Prepare next development iteration.
See #2849
2024-03-15 09:28:53 +01:00
Mark Paluch d0c0b16aea Release version 5.1.10 (2023.0.10).
See #2849
2024-03-15 09:26:26 +01:00
Mark Paluch 7b74f2404d Prepare 5.1.10 (2023.0.10).
See #2849
2024-03-15 09:25:49 +01:00
Mark Paluch 35609a2c79 Upgrade to Netty 4.1.107.Final.
Closes #2871
2024-03-14 16:08:07 +01:00
Peter-Josef Meisch e105648631 Fix setting setting id in bulkrequest.
Original Pull Request #2862
Closes #2861

(cherry picked from commit debf04b499)
(cherry picked from commit b52e8d1431)
2024-02-28 21:46:11 +01:00
Mark Paluch a952e1044a After release cleanups.
See #2824
2024-02-16 08:58:37 +01:00
Mark Paluch f77b60f86b Prepare next development iteration.
See #2824
2024-02-16 08:58:35 +01:00
Mark Paluch 4de6b28cde Release version 5.1.9 (2023.0.9).
See #2824
2024-02-16 08:55:43 +01:00
Mark Paluch 99ee76d1fa Prepare 5.1.9 (2023.0.9).
See #2824
2024-02-16 08:55:31 +01:00
Peter-Josef Meisch c7ea83f58d Fix criteria filter in native query.
Original Pull Request #2846
Closes #2840

(cherry picked from commit e9ecebd9ef)
(cherry picked from commit 310f0ed567)
2024-02-06 21:24:58 +01:00
Peter-Josef Meisch 221317f914 Fix store null values implementation.
Original Pull Request #2843
2024-01-31 19:45:26 +01:00
Mark Paluch 889b273996 Refine Artifactory build name.
See #2772
2024-01-31 15:14:04 +01:00
Mark Paluch 7c2b14a526 After release cleanups.
See #2803
2024-01-12 10:03:26 +01:00
Mark Paluch 32a7013723 Prepare next development iteration.
See #2803
2024-01-12 10:03:24 +01:00
Mark Paluch 67f6db932d Release version 5.1.8 (2023.0.8).
See #2803
2024-01-12 10:00:16 +01:00
Mark Paluch 9fddc28bdb Prepare 5.1.8 (2023.0.8).
See #2803
2024-01-12 10:00:04 +01:00
Mark Paluch 16d7f10f97 Extend license header copyright years to 2024.
See #2817
2024-01-02 10:59:01 +01:00
Peter-Josef Meisch e6c125acda Make org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate.ClientCallback public.
Original Pull Request #2815
Closes #2814

(cherry picked from commit 260dadd4d6)
(cherry picked from commit e2e6b5005b)
2023-12-29 14:00:04 +01:00
Mark Paluch cc6899703b After release cleanups.
See #2770
2023-12-15 13:18:18 +01:00
Mark Paluch 971a72e7b8 Prepare next development iteration.
See #2770
2023-12-15 13:18:16 +01:00
Mark Paluch 033b3fbf55 Release version 5.1.7 (2023.0.7).
See #2770
2023-12-15 13:15:05 +01:00
Mark Paluch b9c8c2ef7e Prepare 5.1.7 (2023.0.7).
See #2770
2023-12-15 13:14:52 +01:00
Mark Paluch 37c78e4289 Update CI properties.
See #2770
2023-12-14 08:50:13 +01:00
Mark Paluch 0122f61ec0 Upgrade to Maven Wrapper 3.9.6.
See #2800
2023-12-14 08:37:40 +01:00
Peter-Josef Meisch b775357524 Fix type of returned sort values.
Original Pull Request #2786
Closes #2777

(cherry picked from commit 3833975a1a)
(cherry picked from commit 0fb98eda39)
2023-11-30 21:09:49 +01:00
Mark Paluch c9fe8a29b9 Introduce property for Jenkins user and Artifactory server details.
Closes #2781
2023-11-27 11:18:52 +01:00
Mark Paluch c444bbd65f After release cleanups.
See #2736
2023-11-17 13:41:01 +01:00
Mark Paluch 9c1b001da1 Prepare next development iteration.
See #2736
2023-11-17 13:40:59 +01:00
Mark Paluch ea234c7b68 Release version 5.1.6 (2023.0.6).
See #2736
2023-11-17 13:38:04 +01:00
Mark Paluch 42af6e375c Prepare 5.1.6 (2023.0.6).
See #2736
2023-11-17 13:37:52 +01:00
Peter-Josef Meisch 5dfd05992f Upgrade dependency of deprecated Elasticsearch client to 7.17.15.
Origina Pull Request #2769
Closes #2765
2023-11-15 21:38:17 +01:00
Peter-Josef Meisch 64cf9566d9 Use routing info on delete operations.
Original Pull Request #2755
Closes #2754

(cherry picked from commit 9abcacb2e9)
2023-11-06 22:17:04 +01:00
Peter-Josef Meisch 2c857178f4 Fix reactive native sort.
Original Pull Request #2746
Closes #2745

(cherry picked from commit 73fe0869e6)
2023-10-23 21:50:50 +02:00
Peter-Josef Meisch f087d5aac3 Fix field parameters in search request creation.
Original Pull Request #2739
Closes #2727
2023-10-14 11:17:09 +02:00
John Blum fbcb76f8ad After release cleanups.
See #2699
2023-10-13 08:09:35 -07:00
John Blum ba06741c93 Prepare next development iteration.
See #2699
2023-10-13 08:09:33 -07:00
John Blum a228629c7d Release version 5.1.5 (2023.0.5).
See #2699
2023-10-13 08:01:15 -07:00
John Blum 6fd688f3a2 Prepare 5.1.5 (2023.0.5).
See #2699
2023-10-13 08:00:48 -07:00
Peter-Josef Meisch 7d85f0bdd8 Upgrade to Elasticsearch 7.17.14.
Original Pull Request #2732
2023-10-13 08:37:45 +02:00
John Blum c7534fa8b9 Upgrade to Maven Wrapper 3.9.5.
See #2715
2023-10-04 18:22:43 -07:00
John Blum fe9e0b5d0c Update CI properties.
See #2699
2023-10-04 17:46:55 -07:00
Peter-Josef Meisch d26d01bab1 Fix converting of Range<?> in Lists.
Original Pull Request #2707
Closes #2706

(cherry picked from commit 3330d65edf)
2023-09-23 12:53:19 +02:00
Mark Paluch 026be264fe After release cleanups.
See #2674
2023-09-15 10:55:36 +02:00
Mark Paluch fd1ba5869d Prepare next development iteration.
See #2674
2023-09-15 10:55:34 +02:00
Mark Paluch 7f3035dab5 Release version 5.1.4 (2023.0.4).
See #2674
2023-09-15 10:52:26 +02:00
Mark Paluch a5c4867684 Prepare 5.1.4 (2023.0.4).
See #2674
2023-09-15 10:52:12 +02:00
Peter-Josef Meisch 572cc7ffea Fix refresh policy in UpdateQuery.
Original Pull Request #2696
Closes #2692

(cherry picked from commit 2d0aee08ce)
2023-09-13 21:49:19 +02:00
Peter-Josef Meisch 4e7bcac5f5 Upgrade deprecated client to Elasticsearch-7.17.13.
Original Pull Request #2694
Closes #2688
2023-09-13 19:38:50 +02:00
Peter-Josef Meisch 4628908e84 Polishing.
(cherry picked from commit a82952b124)
2023-09-03 15:44:09 +02:00
Sébastien Comeau 922f4b1760 Fix: missing PhraseSuggestion.Entry.Option's score and collateMatch values.
Original Pull Request #2680
Closes #2681

(cherry picked from commit 7c466395c4)
2023-09-03 15:44:08 +02:00
Peter-Josef Meisch 4614c62bb5 Fix search_after field values (#2679)
Closes #2678

(cherry picked from commit 9adc4d2b36)
2023-08-28 20:27:21 +02:00
Mark Paluch 063020f8b3 After release cleanups.
See #2631
2023-08-18 14:07:36 +02:00
Mark Paluch 0c98c419c9 Prepare next development iteration.
See #2631
2023-08-18 14:07:34 +02:00
Mark Paluch 3f085b2675 Release version 5.1.3 (2023.0.3).
See #2631
2023-08-18 14:04:04 +02:00
Mark Paluch 42aeb48b43 Prepare 5.1.3 (2023.0.3).
See #2631
2023-08-18 14:03:49 +02:00
Julia Lee 449910b4f7 Update CI properties.
See #2631
2023-08-14 11:29:43 -04:00
Julia Lee 2e99f5b2e0 Upgrade to Maven Wrapper 3.9.4.
See #2669
2023-08-14 07:55:32 -04:00
Peter-Josef Meisch 9b9136d852 Fix similarity field mapping.
Original Pull Request #2666
Closes #2659

(cherry picked from commit 8c5ff92cd2)
2023-08-13 21:40:31 +02:00
Peter-Josef Meisch a266d7c46a Upgrade deprecated dependency to Elasticsearch 7.17.12.
Original Pull Request #2655
#Closes #2651
2023-07-30 19:51:01 +02:00
Peter-Josef Meisch c045a8ae29 Fix MappingElasticsearchConverter.
Original Pull Request #2637
Closes #2627

(cherry picked from commit d9bb9911f9)
2023-07-18 22:51:45 +02:00
Mark Paluch 11c87a1251 After release cleanups.
See #2595
2023-07-14 13:57:59 +02:00
Mark Paluch c9e9bf757e Prepare next development iteration.
See #2595
2023-07-14 13:57:58 +02:00
Mark Paluch db8d89aeff Release version 5.1.2 (2023.0.2).
See #2595
2023-07-14 13:54:08 +02:00
Mark Paluch 0ed14e7caa Prepare 5.1.2 (2023.0.2).
See #2595
2023-07-14 13:52:55 +02:00
Peter-Josef Meisch d0658affc3 Upgrade to Elasticsearch 7.17.11.
Original Pull Request #2625
Closes #2622
2023-07-12 20:05:40 +02:00
Mark Paluch 7c10128cec Update CI properties.
See #2595
2023-07-03 09:49:00 +02:00
Mark Paluch 6e3535d68d Upgrade to Maven Wrapper 3.9.3.
See #2614
2023-07-03 09:48:29 +02:00
Peter-Josef Meisch 121b47e1be Fix IndicesBoost error.
Original Pull Request #2606
Closes #2598

(cherry picked from commit d9fd722bb6)
2023-06-27 22:28:16 +02:00
Greg L. Turnquist 3abe6d9c1b Stop posting build status on internal Slack.
See #2603
2023-06-26 13:38:20 -05:00
seunghyun.cheong d43b44ba9c Adding GeoDistanceOrder's direction in request.
Original Pull Request #2602
Closes #2601

(cherry picked from commit 8a164b1039)

Polishing

(cherry picked from commit b7570ffa95)

add implementation for old client
2023-06-24 11:16:14 +02:00
John Blum a715d2836d After release cleanups.
See #2548
2023-06-16 08:13:31 -07:00
John Blum a69b95867a Prepare next development iteration.
See #2548
2023-06-16 08:13:28 -07:00
John Blum d424195c8b Release version 5.1.1 (2023.0.1).
See #2548
2023-06-16 08:05:37 -07:00
John Blum 44d1614a20 Prepare 5.1.1 (2023.0.1).
See #2548
2023-06-16 08:05:07 -07:00
Mark Paluch 7b6823cc31 Upgrade to Maven Wrapper 3.9.2.
See #2588
2023-06-13 08:54:05 +02:00
Mark Paluch 2e43c7800b Reformat pom.xml.
See #2585
2023-06-06 10:57:04 +02:00
Mark Paluch 59968e8118 Use snapshot and milestone repositories instead of libs-snapshot and libs-milestone.
Closes #2585
2023-06-06 10:56:48 +02:00
Peter-Josef Meisch a5934442bf Fix reactive save of Flux.
Original Pull Request #2581
Closes #2576

(cherry picked from commit d6b5540614)
2023-06-02 17:39:16 +02:00
Peter-Josef Meisch 89afa819f3 Polishing
(cherry picked from commit 6edd3cf1fe)
2023-05-17 20:23:23 +02:00
Pierre Mazieres @Semarchy 5561a5b1ae "BaseQuery" class : the "build" method does not apply to the "runtimeFields" field.
Original Pull Request #2568
Closes #2567

(cherry picked from commit 7b527c4451)
2023-05-17 20:23:23 +02:00
Peter-Josef Meisch 23a5071ee5 Upgrade to Elasticsearch 8.7.1.
Closes #2541

(cherry picked from commit af924c2cbc)
2023-05-12 22:35:53 +02:00
Peter-Josef Meisch d1324a26db Documentation misses link to migration page 5.0 -> 5.1.
Original Pull Request #2554
Closes #2553
2023-05-12 18:59:21 +02:00
Mark Paluch 34e3e8f5da Update Jenkins triggers after GA release.
See #2532
2023-05-12 14:47:15 +02:00
Christoph Strobl 1571b9b4e2 After release cleanups.
See #2532
2023-05-12 14:19:11 +02:00
Christoph Strobl 78e7dd2764 Prepare next development iteration.
See #2532
2023-05-12 14:19:10 +02:00
440 changed files with 28173 additions and 11358 deletions
-8
View File
@@ -26,11 +26,3 @@ target
/zap.env
/localdocker.env
.localdocker-env
build/
node_modules
node
package.json
package-lock.json
.mvn/.gradle-enterprise
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<extension>
<groupId>com.gradle</groupId>
<artifactId>gradle-enterprise-maven-extension</artifactId>
<version>1.19.2</version>
</extension>
<extension>
<groupId>com.gradle</groupId>
<artifactId>common-custom-user-data-maven-extension</artifactId>
<version>1.12.4</version>
</extension>
</extensions>
-31
View File
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<gradleEnterprise
xmlns="https://www.gradle.com/gradle-enterprise-maven" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.gradle.com/gradle-enterprise-maven https://www.gradle.com/schema/gradle-enterprise-maven.xsd">
<server>
<url>https://ge.spring.io</url>
</server>
<buildScan>
<backgroundBuildScanUpload>false</backgroundBuildScanUpload>
<captureGoalInputFiles>true</captureGoalInputFiles>
<publishIfAuthenticated>true</publishIfAuthenticated>
<obfuscation>
<ipAddresses>#{{'0.0.0.0'}}</ipAddresses>
</obfuscation>
</buildScan>
<buildCache>
<local>
<enabled>true</enabled>
</local>
<remote>
<server>
<credentials>
<username>${env.GRADLE_ENTERPRISE_CACHE_USERNAME}</username>
<password>${env.GRADLE_ENTERPRISE_CACHE_PASSWORD}</password>
</credentials>
</server>
<enabled>true</enabled>
<storeEnabled>#{env['GRADLE_ENTERPRISE_CACHE_USERNAME'] != null and env['GRADLE_ENTERPRISE_CACHE_PASSWORD'] != null}</storeEnabled>
</remote>
</buildCache>
</gradleEnterprise>
+1 -1
View File
@@ -1,3 +1,3 @@
#Thu Dec 14 08:40:44 CET 2023
#Thu Dec 14 08:37:40 CET 2023
wrapperUrl=https\://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
+4
View File
@@ -8,3 +8,7 @@ Do not submit a Pull Request before having created an issue and having discussed
== Running the test locally
In order to run the tests locally with `./mvnw test` you need to have docker running because Spring Data Elasticsearch uses https://www.testcontainers.org/[Testcontainers] to start a local running Elasticsearch instance.
== Class names of the test classes
Test classes that do depend on the client have either `ERHLC` (when using the deprecated Elasticsearch `RestHighLevelClient`) or `ELC` (the new `ElasticsearchClient`) in their name.
Vendored
+1 -10
View File
@@ -9,7 +9,7 @@ pipeline {
triggers {
pollSCM 'H/10 * * * *'
upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS)
upstream(upstreamProjects: "spring-data-commons/3.1.x", threshold: hudson.model.Result.SUCCESS)
}
options {
@@ -33,8 +33,6 @@ pipeline {
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
DEVELOCITY_CACHE = credentials("${p['develocity.cache.credentials']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
steps {
@@ -63,8 +61,6 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
DEVELOCITY_CACHE = credentials("${p['develocity.cache.credentials']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
steps {
script {
@@ -92,16 +88,11 @@ pipeline {
options { timeout(time: 20, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
DEVELOCITY_CACHE = credentials("${p['develocity.cache.credentials']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
steps {
script {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
sh 'MAVEN_OPTS="-Duser.name=' + "${p['jenkins.user.name']}" + ' -Duser.home=/tmp/jenkins-home" ' +
"DEVELOCITY_CACHE_USERNAME=${DEVELOCITY_CACHE_USR} " +
"DEVELOCITY_CACHE_PASSWORD=${DEVELOCITY_CACHE_PSW} " +
"GRADLE_ENTERPRISE_ACCESS_KEY=${DEVELOCITY_ACCESS_KEY} " +
"./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root " +
"-Dartifactory.server=${p['artifactory.url']} " +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
+54 -8
View File
@@ -1,15 +1,17 @@
= Spring Data for Elasticsearch image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]] image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?search.rootProjectNames=Spring Data Elasticsearch"]
image:https://spring.io/badges/spring-data-elasticsearch/ga.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start] image:https://spring.io/badges/spring-data-elasticsearch/snapshot.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start]
= Spring Data for Elasticsearch image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]]
The primary goal of the https://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
The Spring Data Elasticsearch project provides integration with the https://www.elastic.co/[Elasticsearch] search engine.
Key functional areas of Spring Data Elasticsearch are a POJO centric model for interacting with Elasticsearch Documents and easily writing a Repository style data access layer.
Key functional areas of Spring Data Elasticsearch are a POJO centric model for interacting with a Elasticsearch Documents and easily writing a Repository style data access layer.
This project is lead and maintained by the community.
== Features
* Spring configuration support using Java based `@Configuration` classes or an XML namespace for an ES client instances.
* Spring configuration support using Java based `@Configuration` classes or an XML namespace for a ES clients instances.
* `ElasticsearchOperations` class and implementations that increases productivity performing common ES operations.
Includes integrated object mapping between documents and POJOs.
* Feature Rich Object Mapping integrated with Springs Conversion Service
@@ -17,6 +19,28 @@ Includes integrated object mapping between documents and POJOs.
* Automatic implementation of `Repository` interfaces including support for custom search methods.
* CDI support for repositories
== About Elasticsearch versions and clients
=== Elasticsearch 7.17 client libraries
At the end of 2021 Elasticsearch with version 7.17 released the new version of their Java client and deprecated the `RestHighLevelCLient` which was the default way to access Elasticsearch up to then.
Spring Data Elasticsearch will in version 4.4 offer the possibility to optionally use the new client as an alternative to the existing setup using the `RestHighLevelCLient`.
The default client that is used still is the `RestHighLevelCLient`, first because the integration of the new client is not yet complete, the new client still has features missing and bugs which will hopefully be resolved soon.
Second, and more important, the new Elasticsearch client forces users to switch from using `javax.json.spi.JsonProvider` to `jakarta.json.spi.JsonProvider`.
Spring Data Elasticsearch cannot enforce this switch; Spring Boot will switch to `jakarta` with version 3 and then it's safe for Spring Data Elasticsearch to switch to the new client.
So for version 4.4 Spring Data Elasticsearch will keep using the `RestHighLevelCLient` in version 7.17.x (as long as this will be available).
=== Elasticsearch 8 client libraries
In Elasticsearch 8, the `RestHighLevelCLient` has been removed.
This means that a switch to this client version can only be done with the next major upgrade which will be Spring Data Elasticsearch 5, based on Spring Data 3, used by Spring Boot 3, based on Spring 6 and Java 17.
=== Elasticsearch 8 cluster
It should be possible to use the Elasticsearch 7 client to access a cluster running version 8 by setting the appropriate compatibility headers (see the documentation at https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.clients.configuration). but I encountered and heard of cases where the response from the server is not parseable by the client although the headers are set, so use with care.
== Code of Conduct
This project is governed by the https://github.com/spring-projects/.github/blob/e3cc2ff230d8f1dca06535aa6b5a4a23815861d4/CODE_OF_CONDUCT.md[Spring Code of Conduct].
@@ -62,7 +86,25 @@ public class MyService {
=== Using the RestClient
Please check the [official documentation](https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.clients.configuration).
Provide a configuration like this:
[source,java]
----
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
return RestClients.create(clientConfiguration).rest();
}
}
----
=== Maven configuration
@@ -77,6 +119,9 @@ Add the Maven dependency:
</dependency>
----
// NOTE: since Github does not support include directives, the content of
// the src/main/asciidoc/reference/preface.adoc file is duplicated here
// Always change both files!
**Compatibility Matrix**
The compatibility between Spring Data Elasticsearch, Elasticsearch client drivers and Spring Boot versions can be found in the https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#preface.versions[reference documentation].
@@ -124,7 +169,8 @@ Wed love to help!
https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/[reference documentation], and https://docs.spring.io/spring-data/elasticsearch/docs/current/api/[Javadocs].
* Learn the Spring basics Spring Data builds on Spring Framework, check the https://spring.io[spring.io] web-site for a wealth of reference documentation.
If you are just starting out with Spring, try one of the https://spring.io/guides[guides].
* Ask a question or chat with the community on https://app.gitter.im/#/room/#spring-projects_spring-data:gitter.im[Gitter].
* Ask a question - we monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-data[`spring-data-elasticsearch`].
You can also chat with the community on https://gitter.im/spring-projects/spring-data[Gitter].
* Report bugs with Spring Data for Elasticsearch at https://github.com/spring-projects/spring-data-elasticsearch/issues[https://github.com/spring-projects/spring-data-elasticsearch/issues].
== Reporting Issues
@@ -134,7 +180,7 @@ If you want to raise an issue, please follow the recommendations below:
* Before you log a bug, please search the
https://github.com/spring-projects/spring-data-elasticsearch/issues[issue tracker] to see if someone has already reported the problem.
* If the issue doesn't already exist, https://github.com/spring-projects/spring-data-elasticsearch/issues/new[create a new issue].
* If the issue doesnt already exist, https://github.com/spring-projects/spring-data-elasticsearch/issues/new[create a new issue].
* Please provide as much information as possible with the issue report, we like to know the version of Spring Data Elasticsearch that you are using and JVM version.
* If you need to paste code, or include a stack trace use Markdown +++```+++ escapes before and after your text.
* If possible try to create a test-case or project that replicates the issue.
@@ -165,10 +211,10 @@ Building the documentation builds also the project without running tests.
[source,bash]
----
$ ./mvnw clean install -Pantora
$ ./mvnw clean install -Pdistribute
----
The generated documentation is available from `target/antora/site/index.html`.
The generated documentation is available from `target/site/reference/html/index.html`.
== Examples
+21
View File
@@ -18,3 +18,24 @@ is run. There must be _docker_ running, as the integration tests use docker to s
Integration tests are tests that have the Junit5 Tag `@Tag("integration-test")` on the test class. Normally this should not be set explicitly, but the annotation `@SpringIntegrationTest` should be used. This not only marks the test as integration test, but integrates an automatic setup of an Elasticsearch Testcontainer and integrate this with Spring, so
that the required Beans can be automatically injected. Check _src/test/java/org/springframework/data/elasticsearch/JUnit5SampleRestClientBasedTests.java_ as a reference setup
== Mutation testing
The pom includes a plugin dependency to run mutation tests using [pitest](https://pitest.org/). These tests must be explicitly configured and run, they are not included in the normal build steps. Before pitest can run, a normal `./mvnw test` must be executed. The configuration excludes integration tests, only unit tests are considered.
pitest can be run directly from the commandline
----
./mvnw org.pitest:pitest-maven:mutationCoverage
----
This will output an html report to _target/pit-reports/YYYYMMDDHHMI_.
To speed-up repeated analysis of the same codebase set the withHistory parameter to true.
----
./mvnw -DwithHistory org.pitest:pitest-maven:mutationCoverage
----
The classes to test are defined either in the pom.xml or can be set on the commandline:
----
./mvnw -DwithHistory org.pitest:pitest-maven:mutationCoverage -DtargetClasses="org.springframework.data.elasticsearch.support.*"
----
+1 -6
View File
@@ -2,12 +2,7 @@
set -euo pipefail
export DEVELOCITY_CACHE_USERNAME=${DEVELOCITY_CACHE_USR}
export DEVELOCITY_CACHE_PASSWORD=${DEVELOCITY_CACHE_PSW}
export JENKINS_USER=${JENKINS_USER_NAME}
# The environment variable to configure access key is still GRADLE_ENTERPRISE_ACCESS_KEY
export GRADLE_ENTERPRISE_ACCESS_KEY=${DEVELOCITY_ACCESS_KEY}
MAVEN_OPTS="-Duser.name=${JENKINS_USER} -Duser.home=/tmp/jenkins-home" \
./mvnw -s settings.xml clean -Dscan=false -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch
./mvnw -s settings.xml clean -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch
+1 -4
View File
@@ -1,6 +1,6 @@
# Java versions
java.main.tag=17.0.9_9-jdk-focal
java.next.tag=21.0.1_12-jdk-jammy
java.next.tag=20-jdk-jammy
# Docker container images - standard
docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.main.tag}
@@ -10,7 +10,6 @@ docker.java.next.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/ecli
docker.mongodb.4.4.version=4.4.25
docker.mongodb.5.0.version=5.0.21
docker.mongodb.6.0.version=6.0.10
docker.mongodb.7.0.version=7.0.2
# Supported versions of Redis
docker.redis.6.version=6.2.13
@@ -28,6 +27,4 @@ docker.credentials=hub.docker.com-springbuildmaster
artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c
artifactory.url=https://repo.spring.io
artifactory.repository.snapshot=libs-snapshot-local
develocity.cache.credentials=gradle_enterprise_cache_user
develocity.access-key=gradle_enterprise_secret_access_key
jenkins.user.name=spring-builds+jenkins
-5
View File
@@ -5,13 +5,8 @@ set -euo pipefail
mkdir -p /tmp/jenkins-home/.m2/spring-data-elasticsearch
chown -R 1001:1001 .
export DEVELOCITY_CACHE_USERNAME=${DEVELOCITY_CACHE_USR}
export DEVELOCITY_CACHE_PASSWORD=${DEVELOCITY_CACHE_PSW}
export JENKINS_USER=${JENKINS_USER_NAME}
# The environment variable to configure access key is still GRADLE_ENTERPRISE_ACCESS_KEY
export GRADLE_ENTERPRISE_ACCESS_KEY=${DEVELOCITY_ACCESS_KEY}
MAVEN_OPTS="-Duser.name=${JENKINS_USER} -Duser.home=/tmp/jenkins-home" \
./mvnw -s settings.xml \
-P${PROFILE} clean dependency:list verify -Dsort -U -B -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch
+59 -83
View File
@@ -5,12 +5,12 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>5.3.0</version>
<version>5.1.12</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>3.3.0</version>
<version>3.1.12</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -18,17 +18,22 @@
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
<properties>
<springdata.commons>3.3.0</springdata.commons>
<springdata.commons>3.1.12</springdata.commons>
<!-- version of the ElasticsearchClient -->
<elasticsearch-java>8.13.2</elasticsearch-java>
<!-- version of the RestHighLevelClient -->
<elasticsearch-rhlc>7.17.15</elasticsearch-rhlc>
<!-- version of the new ElasticsearchClient -->
<elasticsearch-java>8.7.1</elasticsearch-java>
<log4j>2.18.0</log4j>
<!-- netty dependency can be removed once the WebClient code is gone -->
<netty>4.1.107.Final</netty>
<blockhound-junit>1.0.8.RELEASE</blockhound-junit>
<hoverfly>0.14.4</hoverfly>
<log4j>2.18.0</log4j>
<jsonassert>1.5.1</jsonassert>
<testcontainers>1.18.0</testcontainers>
<wiremock>2.35.1</wiremock>
<wiremock>2.35.0</wiremock>
<java-module-name>spring.data.elasticsearch</java-module-name>
@@ -88,6 +93,18 @@
<url>https://github.com/spring-projects/spring-data-elasticsearch/issues</url>
</issueManagement>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>${netty}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring -->
@@ -115,12 +132,33 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty-http</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<!-- optional Elasticsearch RestHighLevelClient, deprecated in SDE 5.0 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>${elasticsearch-rhlc}</version>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- new Elasticsearch client, needs the low-level rest client and json api -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
@@ -166,6 +204,7 @@
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
@@ -180,33 +219,25 @@
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-se</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<!-- Kotlin extension -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<optional>true</optional>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-spi</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactor</artifactId>
<optional>true</optional>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-impl</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<!-- Test -->
@@ -222,13 +253,6 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-test</artifactId>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
@@ -316,21 +340,6 @@
<scope>test</scope>
</dependency>
<!--we need Murmur3Hash in a test, before 5.2 we had it from the old Elasticsearch dependency -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>${archunit}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -399,17 +408,8 @@
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j}</version>
</path>
</annotationProcessorPaths>
</configuration>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
@@ -461,30 +461,6 @@
</plugins>
</build>
</profile>
<profile>
<id>antora-process-resources</id>
<build>
<resources>
<resource>
<directory>src/main/antora/resources/antora-resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</profile>
<profile>
<id>antora</id>
<build>
<plugins>
<plugin>
<groupId>io.spring.maven.antora</groupId>
<artifactId>antora-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
-42
View File
@@ -1,42 +0,0 @@
# PACKAGES antora@3.2.0-alpha.2 @antora/atlas-extension:1.0.0-alpha.1 @antora/collector-extension@1.0.0-alpha.3 @springio/antora-extensions@1.1.0-alpha.2 @asciidoctor/tabs@1.0.0-alpha.12 @opendevise/antora-release-line-extension@1.0.0-alpha.2
#
# The purpose of this Antora playbook is to build the docs in the current branch.
antora:
extensions:
- '@antora/collector-extension'
- require: '@springio/antora-extensions/root-component-extension'
root_component_name: 'data-elasticsearch'
site:
title: Spring Data Elasticsearch
url: https://docs.spring.io/spring-data-elasticsearch/reference/
content:
sources:
- url: ./../../..
branches: HEAD
start_path: src/main/antora
worktrees: true
- url: https://github.com/spring-projects/spring-data-commons
# Refname matching:
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
branches: [ main, 3.2.x ]
start_path: src/main/antora
asciidoc:
attributes:
page-pagination: ''
hide-uri-scheme: '@'
tabs-sync-option: '@'
chomp: 'all'
extensions:
- '@asciidoctor/tabs'
- '@springio/asciidoctor-extensions'
sourcemap: true
urls:
latest_version_segment: ''
runtime:
log:
failure_level: warn
format: pretty
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.3.5/ui-bundle.zip
snapshot: true
-12
View File
@@ -1,12 +0,0 @@
name: data-elasticsearch
version: true
title: Spring Data Elasticsearch
nav:
- modules/ROOT/nav.adoc
ext:
collector:
- run:
command: ./mvnw validate process-resources -am -Pantora-process-resources
local: true
scan:
dir: target/classes/
-42
View File
@@ -1,42 +0,0 @@
* xref:index.adoc[Overview]
** xref:commons/upgrade.adoc[]
** xref:migration-guides.adoc[]
*** xref:migration-guides/migration-guide-3.2-4.0.adoc[]
*** xref:migration-guides/migration-guide-4.0-4.1.adoc[]
*** xref:migration-guides/migration-guide-4.1-4.2.adoc[]
*** xref:migration-guides/migration-guide-4.2-4.3.adoc[]
*** xref:migration-guides/migration-guide-4.3-4.4.adoc[]
*** xref:migration-guides/migration-guide-4.4-5.0.adoc[]
*** xref:migration-guides/migration-guide-5.0-5.1.adoc[]
*** xref:migration-guides/migration-guide-5.1-5.2.adoc[]
* xref:elasticsearch.adoc[]
** xref:elasticsearch/clients.adoc[]
** xref:elasticsearch/object-mapping.adoc[]
** xref:elasticsearch/template.adoc[]
** xref:elasticsearch/reactive-template.adoc[]
** xref:elasticsearch/entity-callbacks.adoc[]
** xref:elasticsearch/auditing.adoc[]
** xref:elasticsearch/join-types.adoc[]
** xref:elasticsearch/routing.adoc[]
** xref:elasticsearch/misc.adoc[]
** xref:elasticsearch/scripted-and-runtime-fields.adoc[]
* xref:repositories.adoc[]
** xref:repositories/core-concepts.adoc[]
** xref:repositories/definition.adoc[]
** xref:elasticsearch/repositories/elasticsearch-repositories.adoc[]
** xref:elasticsearch/repositories/reactive-elasticsearch-repositories.adoc[]
** xref:repositories/create-instances.adoc[]
** xref:repositories/query-methods-details.adoc[]
** xref:elasticsearch/repositories/elasticsearch-repository-queries.adoc[]
** xref:repositories/projections.adoc[]
** xref:repositories/custom-implementations.adoc[]
** xref:repositories/core-domain-events.adoc[]
** xref:repositories/null-handling.adoc[]
** xref:elasticsearch/repositories/cdi-integration.adoc[]
** xref:repositories/query-keywords-reference.adoc[]
** xref:repositories/query-return-types-reference.adoc[]
* https://github.com/spring-projects/spring-data-commons/wiki[Wiki]
@@ -1 +0,0 @@
include::{commons}@data-commons::page$upgrade.adoc[]
@@ -1,16 +0,0 @@
[[elasticsearch.core]]
= Elasticsearch Support
:page-section-summary-toc: 1
Spring Data support for Elasticsearch contains a wide range of features:
* Spring configuration support for various xref:elasticsearch/clients.adoc[Elasticsearch clients].
* The xref:elasticsearch/template.adoc[`ElasticsearchTemplate` and `ReactiveElasticsearchTemplate`] helper classes that provide object mapping between ES index operations and POJOs.
* xref:elasticsearch/template.adoc#exception-translation[Exception translation] into Spring's portable {springDocsUrl}data-access.html#dao-exceptions[Data Access Exception Hierarchy].
* Feature rich xref:elasticsearch/object-mapping.adoc[object mapping] integrated with _Spring's_ {springDocsUrl}core.html#core-convert[Conversion Service].
* xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model.annotations[Annotation-based mapping] metadata that is extensible to support other metadata formats.
* Java-based xref:elasticsearch/template.adoc#cassandra.template.query[query, criteria, and update DSLs].
* Automatic implementation of xref:repositories.adoc[imperative and reactive `Repository` interfaces] including support for xref:repositories/custom-implementations.adoc[custom query methods].
For most data-oriented tasks, you can use the `[Reactive]ElasticsearchTemplate` or the `Repository` support, both of which use the rich object-mapping functionality.
Spring Data Elasticsearch uses consistent naming conventions on objects in various APIs to those found in the DataStax Java Driver so that they are familiar and so that you can map your existing knowledge onto the Spring APIs.
@@ -1,35 +0,0 @@
[[elasticsearch.cdi]]
= CDI Integration
The Spring Data Elasticsearch repositories can also be set up using CDI functionality.
.Spring Data Elasticsearch repositories using CDI
====
[source,java]
----
class ElasticsearchTemplateProducer {
@Produces
@ApplicationScoped
public ElasticsearchOperations createElasticsearchTemplate() {
// ... <1>
}
}
class ProductService {
private ProductRepository repository; <2>
public Page<Product> findAvailableBookByName(String name, Pageable pageable) {
return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
}
@Inject
public void setRepository(ProductRepository repository) {
this.repository = repository;
}
}
----
<1> Create a component by using the same calls as are used in the xref:elasticsearch/template.adoc[Elasticsearch Operations] chapter.
<2> Let the CDI framework inject the Repository into your class.
====
@@ -1,228 +0,0 @@
[[elasticsearch.misc.scripted-and-runtime-fields]]
= Scripted and runtime fields
Spring Data Elasticsearch supports scripted fields and runtime fields.
Please refer to the Elasticsearch documentation about scripting (https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html) and runtime fields (https://www.elastic.co/guide/en/elasticsearch/reference/8.9/runtime.html) for detailed information about this.
In the context of Spring Data Elasticsearch you can use
* scripted fields that are used to return fields that are calculated on the result documents and added to the returned document.
* runtime fields that are calculated on the stored documents and can be used in a query and/or be returned in the search result.
The following code snippets will show what you can do (these show imperative code, but the reactive implementation works similar).
[[the-person-entity]]
== The person entity
The enity that is used in these examples is a `Person` entity.
This entity has a `birthDate` and an `age` property.
Whereas the birthdate is fix, the age depends on the time when a query is issued and needs to be calculated dynamically.
====
[source,java]
----
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.ScriptedField;
import org.springframework.lang.Nullable;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import java.lang.Integer;
@Document(indexName = "persons")
public record Person(
@Id
@Nullable
String id,
@Field(type = Text)
String lastName,
@Field(type = Text)
String firstName,
@Field(type = Keyword)
String gender,
@Field(type = Date, format = DateFormat.basic_date)
LocalDate birthDate,
@Nullable
@ScriptedField Integer age <.>
) {
public Person(String id,String lastName, String firstName, String gender, String birthDate) {
this(id, <.>
lastName,
firstName,
LocalDate.parse(birthDate, DateTimeFormatter.ISO_LOCAL_DATE),
gender,
null);
}
}
----
<.> the `age` property will be calculated and filled in search results.
<.> a convenience constructor to set up the test data.
====
Note that the `age` property is annotated with `@ScriptedField`.
This inhibits the writing of a corresponding entry in the index mapping and marks the property as a target to put a calculated field from a search response.
[[the-repository-interface]]
== The repository interface
The repository used in this example:
====
[source,java]
----
public interface PersonRepository extends ElasticsearchRepository<Person, String> {
SearchHits<Person> findAllBy(ScriptedField scriptedField);
SearchHits<Person> findByGenderAndAgeLessThanEqual(String gender, Integer age, RuntimeField runtimeField);
}
----
====
[[the-service-class]]
== The service class
The service class has a repository injected and an `ElasticsearchOperations` instance to show several ways of populating and using the `age` property.
We show the code split up in different pieces to put the explanations in
====
[source,java]
----
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.RuntimeField;
import org.springframework.data.elasticsearch.core.query.ScriptData;
import org.springframework.data.elasticsearch.core.query.ScriptType;
import org.springframework.data.elasticsearch.core.query.ScriptedField;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PersonService {
private final ElasticsearchOperations operations;
private final PersonRepository repository;
public PersonService(ElasticsearchOperations operations, SaRPersonRepository repository) {
this.operations = operations;
this.repository = repository;
}
public void save() { <.>
List<Person> persons = List.of(
new Person("1", "Smith", "Mary", "f", "1987-05-03"),
new Person("2", "Smith", "Joshua", "m", "1982-11-17"),
new Person("3", "Smith", "Joanna", "f", "2018-03-27"),
new Person("4", "Smith", "Alex", "m", "2020-08-01"),
new Person("5", "McNeill", "Fiona", "f", "1989-04-07"),
new Person("6", "McNeill", "Michael", "m", "1984-10-20"),
new Person("7", "McNeill", "Geraldine", "f", "2020-03-02"),
new Person("8", "McNeill", "Patrick", "m", "2022-07-04"));
repository.saveAll(persons);
}
----
<.> a utility method to store some data in Elasticsearch.
====
[[scripted-fields]]
=== Scripted fields
The next piece shows how to use a scripted field to calculate and return the age of the persons.
Scripted fields can only add something to the returned data, the age cannot be used in the query (see runtime fields for that).
====
[source,java]
----
public SearchHits<Person> findAllWithAge() {
var scriptedField = ScriptedField.of("age", <.>
ScriptData.of(b -> b
.withType(ScriptType.INLINE)
.withScript("""
Instant currentDate = Instant.ofEpochMilli(new Date().getTime());
Instant startDate = doc['birth-date'].value.toInstant();
return (ChronoUnit.DAYS.between(startDate, currentDate) / 365);
""")));
// version 1: use a direct query
var query = new StringQuery("""
{ "match_all": {} }
""");
query.addScriptedField(scriptedField); <.>
query.addSourceFilter(FetchSourceFilter.of(b -> b.withIncludes("*"))); <.>
var result1 = operations.search(query, Person.class); <.>
// version 2: use the repository
var result2 = repository.findAllBy(scriptedField); <.>
return result1;
}
----
<.> define the `ScriptedField` that calculates the age of a person.
<.> when using a `Query`, add the scripted field to the query.
<.> when adding a scripted field to a `Query`, an additional source filter is needed to also retrieve the _normal_ fields from the document source.
<.> get the data where the `Person` entities now have the values set in their `age` property.
<.> when using the repository, all that needs to be done is adding the scripted field as method parameter.
====
[[runtime-fields]]
=== Runtime fields
When using runtime fields, the calculated value can be used in the query itself.
In the following code this is used to run a query for a given gender and maximum age of persons:
====
[source,java]
----
public SearchHits<Person> findWithGenderAndMaxAge(String gender, Integer maxAge) {
var runtimeField = new RuntimeField("age", "long", """ <.>
Instant currentDate = Instant.ofEpochMilli(new Date().getTime());
Instant startDate = doc['birthDate'].value.toInstant();
emit (ChronoUnit.DAYS.between(startDate, currentDate) / 365);
""");
// variant 1 : use a direct query
var query = CriteriaQuery.builder(Criteria
.where("gender").is(gender)
.and("age").lessThanEqual(maxAge))
.withRuntimeFields(List.of(runtimeField)) <.>
.withFields("age") <.>
.withSourceFilter(FetchSourceFilter.of(b -> b.withIncludes("*"))) <.>
.build();
var result1 = operations.search(query, Person.class); <.>
// variant 2: use the repository <.>
var result2 = repository.findByGenderAndAgeLessThanEqual(gender, maxAge, runtimeField);
return result1;
}
}
----
<.> define the runtime field that calculates the age of a person. // see https://asciidoctor.org/docs/user-manual/#builtin-attributes for builtin attributes.
<.> when using `Query`, add the runtime field.
<.> when adding a scripted field to a `Query`, an additional field parameter is needed to have the calculated value returned.
<.> when adding a scripted field to a `Query`, an additional source filter is needed to also retrieve the _normal_ fields from the document source.
<.> get the data filtered with the query and where the returned entites have the age property set.
<.> when using the repository, all that needs to be done is adding the runtime field as method parameter.
====
In addition to define a runtime fields on a query, they can also be defined in the index by setting the `runtimeFieldsPath` property of the `@Mapping` annotation to point to a JSON file that contains the runtime field definitions.
@@ -1,24 +0,0 @@
[[preface.versions]]
= Versions
The following table shows the Elasticsearch and Spring versions that are used by Spring Data release trains and the version of Spring Data Elasticsearch included in that.
[cols="^,^,^,^",options="header"]
|===
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework
| 2024.0 (?) | 5.3.x | 8.13.2 | ?
| 2023.1 (Vaughan) | 5.2.x | 8.11.1 | 6.1.x
| 2023.0 (Ullmann) | 5.1.x | 8.7.1 | 6.0.x
| 2022.0 (Turing) | 5.0.xfootnote:oom[Out of maintenance] | 8.5.3 | 6.0.x
| 2021.2 (Raj) | 4.4.xfootnote:oom[] | 7.17.3 | 5.3.x
| 2021.1 (Q) | 4.3.xfootnote:oom[] | 7.15.2 | 5.3.x
| 2021.0 (Pascal) | 4.2.xfootnote:oom[] | 7.12.0 | 5.3.x
| 2020.0 (Ockham) | 4.1.xfootnote:oom[] | 7.9.3 | 5.3.2
| Neumann | 4.0.xfootnote:oom[] | 7.6.2 | 5.2.12
| Moore | 3.2.xfootnote:oom[] |6.8.12 | 5.2.12
| Lovelace | 3.1.xfootnote:oom[] | 6.2.2 | 5.1.19
| Kay | 3.0.xfootnote:oom[] | 5.5.0 | 5.0.13
| Ingalls | 2.1.xfootnote:oom[] | 2.4.0 | 4.3.25
|===
Support for upcoming versions of Elasticsearch is being tracked and general compatibility should be given assuming the usage of the xref:elasticsearch/template.adoc[ElasticsearchOperations interface].
@@ -1,22 +0,0 @@
[[spring-data-elasticsearch-reference-documentation]]
= Spring Data Elasticsearch
:revnumber: {version}
:revdate: {localdate}
:feature-scroll: true
_Spring Data Elasticsearch provides repository support for the Elasticsearch database.
It eases development of applications with a consistent programming model that need to access Elasticsearch data sources._
[horizontal]
xref:elasticsearch/versions.adoc[Versions] :: Version Compatibility Matrix
xref:elasticsearch/clients.adoc[Clients] :: Elasticsearch Client Configuration
xref:elasticsearch.adoc[Elasticsearch] :: Elasticsearch support
xref:repositories.adoc[Repositories] :: Elasticsearch Repositories
xref:migration-guides.adoc[Migration] :: Migration Guides
https://github.com/spring-projects/spring-data-commons/wiki[Wiki] :: What's New, Upgrade Notes, Supported Versions, additional cross-version information.
BioMed Central Development Team; Oliver Drotbohm; Greg Turnquist; Christoph Strobl; Peter-Josef Meisch
(C) 2008-{copyright-year} VMware, Inc.
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
@@ -1,8 +0,0 @@
[[elasticsearch.migration]]
= Migration Guides
:page-section-summary-toc: 1
This section contains version-specific migration guides explaining how to upgrade across versions.
@@ -1,40 +0,0 @@
[[elasticsearch-migration-guide-5.1-5.2]]
= Upgrading from 5.1.x to 5.2.x
This section describes breaking changes from version 5.1.x to 5.2.x and how removed features can be replaced by new introduced features.
[[elasticsearch-migration-guide-5.1-5.2.breaking-changes]]
== Breaking Changes
[[bulk-failures]]
=== Bulk failures
In the `org.springframework.data.elasticsearch.BulkFailureException` class, the return type of the `getFailedDocuments` is changed from `Map<String, String>`
to `Map<String, FailureDetails>`, which allows to get additional details about failure reasons.
The definition of the `FailureDetails` class (inner to `BulkFailureException`):
[source,java]
public record FailureDetails(Integer status, String errorMessage) {
}
[[scripted-and-runtime-fields]]
=== scripted and runtime fields
The classes `org.springframework.data.elasticsearch.core.RuntimeField` and `org.springframework.data.elasticsearch.core.query.ScriptType` have been moved to the subpackage `org.springframework.data.elasticsearch.core.query`.
The `type` parameter of the `ScriptData` constructor is not nullable any longer.
[[elasticsearch-migration-guide-5.1-5.2.deprecations]]
== Deprecations
[[removal-of-deprecated-code]]
=== Removal of deprecated code
* All the code using the old deprecated `RestHighLevelClient` has been removed.
The default Elasticsearch client used since version 5.0 is the (not so) new Elasticsearch Java client.
* The `org.springframework.data.elasticsearch.client.ClientLogger` class has been removed.
This logger was configured with the `org.springframework.data.elasticsearch.client.WIRE` setting, but was not working with all clients.
From version 5 on, use the trace logger available in the Elasticsearch Java client, see xref:elasticsearch/clients.adoc#elasticsearch.clients.logging[Client Logging].
* The method `org.springframework.data.elasticsearch.core.ElasticsearchOperations.stringIdRepresentation(Object)` has been removed, use the `convertId(Object)` method defined in the same interface instead.
* The class `org.springframework.data.elasticsearch.core.Range` has been removed, use `org.springframework.data.domain.Range` instead.
* The methods `org.springframework.data.elasticsearch.core.query.IndexQuery.getParentId() and `setParentId(String)` have been removed, they weren't used anymore and were no-ops.
It has been removed from the `org.springframework.data.elasticsearch.core.query.IndexQuery` class as well.
@@ -1,15 +0,0 @@
[[elasticsearch-migration-guide-5.2-5.3]]
= Upgrading from 5.2.x to 5.3.x
This section describes breaking changes from version 5.2.x to 5.3.x and how removed features can be replaced by new introduced features.
[[elasticsearch-migration-guide-5.2-5.3.breaking-changes]]
== Breaking Changes
[[elasticsearch-migration-guide-5.2-5.3.deprecations]]
== Deprecations
=== Removals
The deprecated classes `org.springframework.data.elasticsearch.ELCQueries`
and `org.springframework.data.elasticsearch.client.elc.QueryBuilders` have been removed, use `org.springframework.data.elasticsearch.client.elc.Queries` instead.
@@ -1,8 +0,0 @@
[[elasticsearch.repositories]]
= Repositories
:page-section-summary-toc: 1
This chapter explains the basic foundations of Spring Data repositories and Elasticsearch specifics.
Before continuing to the Elasticsearch specifics, make sure you have a sound understanding of the basic concepts.
The goal of the Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores.
@@ -1,4 +0,0 @@
include::{commons}@data-commons::page$repositories/core-concepts.adoc[]
[[elasticsearch.entity-persistence.state-detection-strategies]]
include::{commons}@data-commons::page$is-new-state-detection.adoc[leveloffset=+1]
@@ -1 +0,0 @@
include::{commons}@data-commons::page$repositories/core-domain-events.adoc[]
@@ -1 +0,0 @@
include::{commons}@data-commons::page$repositories/core-extensions.adoc[]
@@ -1 +0,0 @@
include::{commons}@data-commons::page$repositories/create-instances.adoc[]
@@ -1 +0,0 @@
include::{commons}@data-commons::page$repositories/custom-implementations.adoc[]
@@ -1 +0,0 @@
include::{commons}@data-commons::page$repositories/definition.adoc[]
@@ -1 +0,0 @@
include::{commons}@data-commons::page$repositories/null-handling.adoc[]
@@ -1,4 +0,0 @@
[[elasticsearch.projections]]
= Projections
include::{commons}@data-commons::page$repositories/projections.adoc[leveloffset=+1]
@@ -1 +0,0 @@
include::{commons}@data-commons::page$repositories/query-keywords-reference.adoc[]
@@ -1 +0,0 @@
include::{commons}@data-commons::page$repositories/query-methods-details.adoc[]
@@ -1 +0,0 @@
include::{commons}@data-commons::page$repositories/query-return-types-reference.adoc[]
@@ -1,21 +0,0 @@
version: ${antora-component.version}
prerelease: ${antora-component.prerelease}
asciidoc:
attributes:
copyright-year: ${current.year}
version: ${project.version}
springversionshort: ${spring.short}
springversion: ${spring}
attribute-missing: 'warn'
commons: ${springdata.commons.docs}
include-xml-namespaces: false
spring-data-commons-docs-url: https://docs.spring.io/spring-data/commons/reference
spring-data-commons-javadoc-base: https://docs.spring.io/spring-data/commons/docs/${springdata.commons}/api/
springdocsurl: https://docs.spring.io/spring-framework/reference/{springversionshort}
springjavadocurl: https://docs.spring.io/spring-framework/docs/${spring}/javadoc-api
spring-framework-docs: '{springdocsurl}'
spring-framework-javadoc: '{springjavadocurl}'
springhateoasversion: ${spring-hateoas}
releasetrainversion: ${releasetrain}
store: Elasticsearch
+52
View File
@@ -0,0 +1,52 @@
= Spring Data Elasticsearch - Reference Documentation
BioMed Central Development Team; Oliver Drotbohm; Greg Turnquist; Christoph Strobl; Peter-Josef Meisch
:revnumber: {version}
:revdate: {localdate}
ifdef::backend-epub3[:front-cover-image: image:epub-cover.png[Front Cover,1050,1600]]
:spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc
(C) 2013-2024 The original author(s).
NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
toc::[]
include::preface.adoc[]
include::{spring-data-commons-docs}/upgrade.adoc[leveloffset=+1]
:leveloffset: +1
include::{spring-data-commons-docs}/repositories.adoc[]
:leveloffset: -1
[[reference]]
= Reference Documentation
:leveloffset: +1
include::reference/elasticsearch-clients.adoc[]
include::reference/elasticsearch-object-mapping.adoc[]
include::reference/elasticsearch-operations.adoc[]
include::reference/elasticsearch-repositories.adoc[]
include::{spring-data-commons-docs}/auditing.adoc[]
include::reference/elasticsearch-auditing.adoc[]
include::{spring-data-commons-docs}/entity-callbacks.adoc[]
include::reference/elasticsearch-entity-callbacks.adoc[leveloffset=+1]
include::reference/elasticsearch-join-types.adoc[]
include::reference/elasticsearch-routing.adoc[]
include::reference/elasticsearch-misc.adoc[]
:leveloffset: -1
[[appendix]]
= Appendix
:numbered!:
:leveloffset: +1
include::{spring-data-commons-docs}/repository-namespace-reference.adoc[]
include::{spring-data-commons-docs}/repository-populator-namespace-reference.adoc[]
include::{spring-data-commons-docs}/repository-query-keywords-reference.adoc[]
include::{spring-data-commons-docs}/repository-query-return-types-reference.adoc[]
include::reference/migration-guides.adoc[]
:leveloffset: -1
+54
View File
@@ -0,0 +1,54 @@
[[preface]]
= Preface
The Spring Data Elasticsearch project applies core Spring concepts to the development of solutions using the Elasticsearch Search Engine.
It provides:
* _Templates_ as a high-level abstraction for storing, searching, sorting documents and building aggregations.
* _Repositories_ which for example enable the user to express queries by defining interfaces having customized method names (for basic information about repositories see <<repositories>>).
You will notice similarities to the Spring data solr and mongodb support in the Spring Framework.
include::reference/elasticsearch-new.adoc[leveloffset=+1]
[[preface.metadata]]
== Project Metadata
* Version Control - https://github.com/spring-projects/spring-data-elasticsearch
* API Documentation - https://docs.spring.io/spring-data/elasticsearch/docs/current/api/
* Bugtracker - https://github.com/spring-projects/spring-data-elasticsearch/issues
* Release repository - https://repo1.maven.org/maven2/
* Milestone repository - https://repo.spring.io/milestone/
* Snapshot repository - https://repo.spring.io/snapshot/
[[preface.requirements]]
== Requirements
Requires an installation of https://www.elastic.co/products/elasticsearch[Elasticsearch].
[[preface.versions]]
=== Versions
The following table shows the Elasticsearch versions that are used by Spring Data release trains and version of
Spring Data Elasticsearch included in that, as well as the Spring Boot versions referring to that particular Spring
Data release train. The Elasticsearch version given shows with which client libraries Spring Data Elasticsearch was
built and tested.
[cols="^,^,^,^,^",options="header"]
|===
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot
| 2023.0 (Ullmann) | 5.1.x | 8.7.1 | 6.0.x | 3.1.x
| 2022.0 (Turing) | 5.0.x | 8.5.3 | 6.0.x | 3.0.x
| 2021.2 (Raj) | 4.4.xfootnote:oom[Out of maintenance] | 7.17.3 | 5.3.x | 2.7.x
| 2021.1 (Q) | 4.3.xfootnote:oom[] | 7.15.2 | 5.3.x | 2.6.x
| 2021.0 (Pascal) | 4.2.xfootnote:oom[] | 7.12.0 | 5.3.x | 2.5.x
| 2020.0 (Ockham) | 4.1.xfootnote:oom[] | 7.9.3 | 5.3.2 | 2.4.x
| Neumann | 4.0.xfootnote:oom[] | 7.6.2 | 5.2.12 |2.3.x
| Moore | 3.2.xfootnote:oom[] |6.8.12 | 5.2.12| 2.2.x
| Lovelace | 3.1.xfootnote:oom[] | 6.2.2 | 5.1.19 |2.1.x
| Kay | 3.0.xfootnote:oom[] | 5.5.0 | 5.0.13 | 2.0.x
| Ingalls | 2.1.xfootnote:oom[] | 2.4.0 | 4.3.25 | 1.5.x
|===
Support for upcoming versions of Elasticsearch is being tracked and general compatibility should be given assuming
the usage of the <<elasticsearch.operations,ElasticsearchOperations interface>>.
@@ -1,8 +1,8 @@
[[elasticsearch.auditing]]
= Elasticsearch Auditing
== Elasticsearch Auditing
[[elasticsearch.auditing.preparing]]
== Preparing entities
=== Preparing entities
In order for the auditing code to be able to decide whether an entity instance is new, the entity must implement the `Persistable<ID>` interface which is defined as follows:
@@ -56,7 +56,7 @@ public class Person implements Persistable<Long> {
<.> an object is new if it either has no `id` or none of fields containing creation attributes are set.
[[elasticsearch.auditing.activating]]
== Activating auditing
=== Activating auditing
After the entities have been set up and providing the `AuditorAware` - or `ReactiveAuditorAware` - the Auditing must be activated by setting the `@EnableElasticsearchAuditing` on a configuration class:
@@ -3,8 +3,10 @@
This chapter illustrates configuration and usage of supported Elasticsearch client implementations.
Spring Data Elasticsearch operates upon an Elasticsearch client (provided by Elasticsearch client libraries) that is connected to a single Elasticsearch node or a cluster.
Although the Elasticsearch Client can be used directly to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of xref:elasticsearch/template.adoc[Elasticsearch Operations] and xref:elasticsearch/repositories/elasticsearch-repositories.adoc[Elasticsearch Repositories].
Spring Data Elasticsearch operates upon an Elasticsearch client (provided by Elasticsearch client libraries) that is
connected to a single Elasticsearch node or a cluster.
Although the Elasticsearch Client can be used directly to work with the cluster, applications using Spring Data
Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
[[elasticsearch.clients.restclient]]
== Imperative Rest Client
@@ -21,41 +23,33 @@ public class MyClientConfig extends ElasticsearchConfiguration {
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() <.>
.connectedTo("localhost:9200")
return ClientConfiguration.builder() <.>
.connectedTo("localhost:9200")
.build();
}
}
----
<.> for a detailed description of the builder methods see xref:elasticsearch/clients.adoc#elasticsearch.clients.configuration[Client Configuration]
<.> for a detailed description of the builder methods see <<elasticsearch.clients.configuration>>
====
The `ElasticsearchConfiguration` class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
The following beans can then be injected in other Spring components:
====
[source,java]
----
import org.springframework.beans.factory.annotation.Autowired;@Autowired
@Autowired
ElasticsearchOperations operations; <.>
@Autowired
@Autowired
ElasticsearchClient elasticsearchClient; <.>
@Autowired
RestClient restClient; <.>
@Autowired
JsonpMapper jsonpMapper; <.>
----
<.> an implementation of `ElasticsearchOperations`
<.> the `co.elastic.clients.elasticsearch.ElasticsearchClient` that is used.
<.> the low level `RestClient` from the Elasticsearch libraries
<.> the `JsonpMapper` user by the Elasticsearch `Transport`
====
Basically one should just use the `ElasticsearchOperations` to interact with the Elasticsearch cluster.
@@ -77,17 +71,14 @@ public class MyClientConfig extends ReactiveElasticsearchConfiguration {
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() <.>
.connectedTo("localhost:9200")
.connectedTo("localhost:9200")
.build();
}
}
----
<.> for a detailed description of the builder methods see xref:elasticsearch/clients.adoc#elasticsearch.clients.configuration[Client Configuration]
<.> for a detailed description of the builder methods see <<elasticsearch.clients.configuration>>
====
The `ReactiveElasticsearchConfiguration` class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
The following beans can then be injected in other Spring components:
====
@@ -96,14 +87,11 @@ The following beans can then be injected in other Spring components:
@Autowired
ReactiveElasticsearchOperations operations; <.>
@Autowired
@Autowired
ReactiveElasticsearchClient elasticsearchClient; <.>
@Autowired
RestClient restClient; <.>
@Autowired
JsonpMapper jsonpMapper; <.>
----
the following can be injected:
@@ -112,12 +100,103 @@ the following can be injected:
<.> the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient` that is used.
This is a reactive implementation based on the Elasticsearch client implementation.
<.> the low level `RestClient` from the Elasticsearch libraries
<.> the `JsonpMapper` user by the Elasticsearch `Transport`
====
Basically one should just use the `ReactiveElasticsearchOperations` to interact with the Elasticsearch cluster.
When using repositories, this instance is used under the hood as well.
[[elasticsearch.clients.resthighlevelclient]]
== High Level REST Client (deprecated)
[CAUTION]
====
The Elasticsearch Java RestHighLevelClient is deprecated, but still can be configured like shown (make sure to read
<<elasticsearch-migration-guide-4.4-5.0.old-client>> as well).
It should only be used to access an Elasticsearch cluster running version 7, even with the compatibility headers set
there are cases where the `RestHighLevelClient` cannot read the responses sent from a version 8 cluster.
====
.RestHighLevelClient
====
[source,java]
----
import org.springframework.data.elasticsearch.client.erhlc.AbstractElasticsearchConfiguration;
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder() <1>
.connectedTo("localhost:9200")
.build();
return RestClients.create(clientConfiguration).rest(); <2>
}
}
// ...
@Autowired
RestHighLevelClient highLevelClient;
RestClient lowLevelClient = highLevelClient.lowLevelClient(); <3>
----
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<2> Create the RestHighLevelClient.
<3> It is also possible to obtain the `lowLevelRest()` client.
====
[[elasticsearch.clients.reactive]]
== Reactive Client (deprecated)
The `org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient` is a non official driver based on `WebClient`.
It uses the request/response objects provided by the Elasticsearch core project.
Calls are directly operated on the reactive stack, **not** wrapping async (thread pool bound) responses into reactive types.
[CAUTION]
====
This was the first reactive implementation Spring Data Elasticsearch provided, but now is deprecated in favour
of the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient`
which uses the functionality offered by the new Elasticsearch client libraries.
====
.Reactive REST Client (deprecated)
====
[source,java]
----
import org.springframework.data.elasticsearch.client.erhlc.AbstractReactiveElasticsearchConfiguration;
@Configuration
public class ReactiveRestClientConfig extends AbstractReactiveElasticsearchConfiguration {
@Override
@Bean
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder() <.>
.connectedTo("localhost:9200") //
.build();
return ReactiveRestClients.create(clientConfiguration);
}
}
// ...
Mono<IndexResponse> response = client.index(request ->
request.index("spring-data")
.id(randomID())
.source(singletonMap("feature", "reactive-client"));
);
----
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
====
[[elasticsearch.clients.configuration]]
== Client Configuration
@@ -150,7 +229,7 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
return headers;
})
.withClientConfigurer( <.>
ElasticsearchHttpClientConfigurationCallback.from(clientBuilder -> {
ElasticsearchClientConfigurationCallback.from(clientBuilder -> {
// ...
return clientBuilder;
}))
@@ -161,7 +240,7 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
<.> Define default headers, if they need to be customized
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<.> Optionally enable SSL.There exist overloads of this function that can take a `SSLContext` or as an alternative the fingerprint of the certificate as it is output by Elasticsearch 8 on startup.
<.> Optionally enable SSL. There exist overloads of this function that can take a `SSLContext` or as an alternative the fingerprint of the certificate as it is output by Elasticsearch 8 on startup.
<.> Optionally set a proxy.
<.> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
<.> Set the connection timeout.
@@ -169,7 +248,8 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
<.> Optionally set headers.
<.> Add basic authentication.
<.> A `Supplier<HttpHeaders>` function can be specified which is called every time before a request is sent to Elasticsearch - here, as an example, the current time is written in a header.
<.> a function to configure the created client (see xref:elasticsearch/clients.adoc#elasticsearch.clients.configuration.callbacks[Client configuration callbacks]), can be added multiple times.
<.> a function to configure the created client (see <<elasticsearch.clients.configuration.callbacks>>), can be added
multiple times.
====
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens.
@@ -178,8 +258,8 @@ If this is used in the reactive setup, the supplier function *must not* block!
[[elasticsearch.clients.configuration.callbacks]]
=== Client configuration callbacks
The `ClientConfiguration` class offers the most common parameters to configure the client.
In the case this is not enough, the user can add callback functions by using the `withClientConfigurer(ClientConfigurationCallback<?>)` method.
The `ClientConfiguration` class offers the most common parameters to configure the client. In the case this is not
enough, the user can add callback functions by using the `withClientConfigurer(ClientConfigurationCallback<?>)` method.
The following callbacks are provided:
@@ -192,7 +272,6 @@ This callback provides a `org.elasticsearch.client.RestClientBuilder` that can b
[source,java]
----
ClientConfiguration.builder()
.connectedTo("localhost:9200", "localhost:9291")
.withClientConfigurer(ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
// configure the Elasticsearch RestClient
return restClientBuilder;
@@ -211,7 +290,6 @@ used by the `RestClient`.
[source,java]
----
ClientConfiguration.builder()
.connectedTo("localhost:9200", "localhost:9291")
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
// configure the HttpAsyncClient
return httpAsyncClientBuilder;
@@ -220,11 +298,44 @@ ClientConfiguration.builder()
----
====
[[elasticsearch.clients.configuration.headers]]
=== Elasticsearch 7 compatibility headers
When using the deprecated `RestHighLevelClient` and accessing an Elasticsearch cluster that is running on version 8, it is necessary to set the compatibility headers
https://www.elastic.co/guide/en/elasticsearch/reference/8.0/rest-api-compatibility.html[see Elasticsearch
documentation].
For the imperative client this must be done by setting the default headers, for the reactive code this must be done using a header supplier:
CAUTION: Even when these headers are set, there are cases where the response returned from the cluster cannot be
parsed with the client. This is not an error in Spring Data Elasticsearch.
====
[source,java]
----
HttpHeaders compatibilityHeaders = new HttpHeaders();
compatibilityHeaders.add("Accept", "application/vnd.elasticsearch+json;compatible-with=7");
compatibilityHeaders.add("Content-Type", "application/vnd.elasticsearch+json;"
+ "compatible-with=7");
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.withProxy("localhost:8080")
.withBasicAuth("elastic","hcraescitsale")
.withDefaultHeaders(compatibilityHeaders) // this variant for imperative code
.withHeaders(() -> compatibilityHeaders) // this variant for reactive code
.build();
----
====
[[elasticsearch.clients.logging]]
== Client Logging
To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level needs to be turned on as outlined in the snippet below.
This can be enabled in the Elasticsearch client by setting the level of the `tracer` package to "trace" (see
To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level
needs to be turned on as outlined in the snippet below. This can be enabled in the Elasticsearch client by setting
the level of the `tracer` package to "trace" (see
https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-rest-low-usage-logging.html)
.Enable transport layer logging
@@ -1,7 +1,5 @@
include::{commons}@data-commons::page$entity-callbacks.adoc[]
[[elasticsearch.entity-callbacks]]
== Store specific EntityCallbacks
= Elasticsearch EntityCallbacks
Spring Data Elasticsearch uses the `EntityCallback` API internally for its auditing support and reacts on the following callbacks:
@@ -52,7 +52,7 @@ public class Statement {
return routing;
}
public void setRouting(String routing) {
public void setRouting(Routing routing) {
this.routing = routing;
}
@@ -112,7 +112,7 @@ public class Statement {
}
}
----
<.> for routing related info see xref:elasticsearch/routing.adoc[Routing values]
<.> for routing related info see <<elasticsearch.routing>>
<.> a question can have answers and comments
<.> an answer can have votes
<.> the `JoinField` property is used to combine the name (_question_, _answer_, _comment_ or _vote_) of the relation with the parent id.
@@ -199,7 +199,7 @@ void init() {
repository.save(
Statement.builder()
.withText("+1 for the sun")
.withRouting(savedWeather.getId())
,withRouting(savedWeather.getId())
.withRelation(new JoinField<>("vote", sunnyAnswer.getId())) <5>
.build());
}
@@ -208,13 +208,13 @@ void init() {
<2> the first answer to the question
<3> the second answer
<4> a comment to the question
<5> a vote for the first answer, this needs to have the routing set to the weather document, see xref:elasticsearch/routing.adoc[Routing values].
<5> a vote for the first answer, this needs to have the routing set to the weather document, see <<elasticsearch.routing>>.
====
[[elasticsearch.jointype.retrieving]]
== Retrieving data
Currently native queries must be used to query the data, so there is no support from standard repository methods. xref:repositories/custom-implementations.adoc[] can be used instead.
Currently native queries must be used to query the data, so there is no support from standard repository methods. <<repositories.custom-implementations>> can be used instead.
The following code shows as an example how to retrieve all entries that have a _vote_ (which must be _answers_, because only answers can have a vote) using an `ElasticsearchOperations` instance:
@@ -226,7 +226,6 @@ SearchHits<Statement> hasVotes() {
Query query = NativeQuery.builder()
.withQuery(co.elastic.clients.elasticsearch._types.query_dsl.Query.of(qb -> qb
.hasChild(hc -> hc
.type("answer")
.queryName("vote")
.query(matchAllQueryAsQuery())
.scoreMode(ChildScoreMode.None)
@@ -6,12 +6,9 @@ This section describes breaking changes from version 3.2.x to 4.0.x and how remo
[[elasticsearch-migration-guide-3.2-4.0.jackson-removal]]
== Removal of the used Jackson Mapper
One of the changes in version 4.0.x is that Spring Data Elasticsearch does not use the Jackson Mapper anymore to map an entity to the JSON representation needed for Elasticsearch (see xref:elasticsearch/object-mapping.adoc[Elasticsearch Object Mapping]).
In version 3.2.x the Jackson Mapper was the default that was used.
It was possible to switch to the meta-model based converter (named `ElasticsearchEntityMapper`) by explicitly configuring it (xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model[Meta Model Object Mapping]).
One of the changes in version 4.0.x is that Spring Data Elasticsearch does not use the Jackson Mapper anymore to map an entity to the JSON representation needed for Elasticsearch (see <<elasticsearch.mapping>>). In version 3.2.x the Jackson Mapper was the default that was used. It was possible to switch to the meta-model based converter (named `ElasticsearchEntityMapper`) by explicitly configuring it (<<elasticsearch.mapping.meta-model>>).
In version 4.0.x the meta-model based converter is the only one that is available and does not need to be configured explicitly.
If you had a custom configuration to enable the meta-model converter by providing a bean like this:
In version 4.0.x the meta-model based converter is the only one that is available and does not need to be configured explicitly. If you had a custom configuration to enable the meta-model converter by providing a bean like this:
[source,java]
----
@@ -31,16 +28,13 @@ public EntityMapper entityMapper() {
You now have to remove this bean, the `ElasticsearchEntityMapper` interface has been removed.
.Entity configuration
Some users had custom Jackson annotations on the entity class, for example in order to define a custom name for the mapped document in Elasticsearch or to configure date conversions.
These are not taken into account anymore.
The needed functionality is now provided with Spring Data Elasticsearch's `@Field` annotation.
Please see xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model.annotations[Mapping Annotation Overview] for detailed information.
Some users had custom Jackson annotations on the entity class, for example in order to define a custom name for the mapped document in Elasticsearch or to configure date conversions. These are not taken into account anymore. The needed functionality is now provided with Spring Data Elasticsearch's `@Field` annotation. Please see <<elasticsearch.mapping.meta-model.annotations>> for detailed information.
[[elasticsearch-migration-guide-3.2-4.0.implicit-index-name]]
== Removal of implicit index name from query objects
In 3.2.x the different query classes like `IndexQuery` or `SearchQuery` had properties that were taking the index name or index names that they were operating upon.If these were not set, the passed in entity was inspected to retrieve the index name that was set in the `@Document` annotation. +
In 4.0.x the index name(s) must now be provided in an additional parameter of type `IndexCoordinates`.By separating this, it now is possible to use one query object against different indices.
In 3.2.x the different query classes like `IndexQuery` or `SearchQuery` had properties that were taking the index name or index names that they were operating upon. If these were not set, the passed in entity was inspected to retrieve the index name that was set in the `@Document` annotation. +
In 4.0.x the index name(s) must now be provided in an additional parameter of type `IndexCoordinates`. By separating this, it now is possible to use one query object against different indices.
So for example the following code:
@@ -136,9 +130,6 @@ Mapping types were removed from Elasticsearch 7, they still exist as deprecated
* The `SearchQuery` interface has been merged into it's base interface `Query`, so it's occurrences can just be replaced with `Query`.
* The method `org.springframework.data.elasticsearch.core.ElasticsearchOperations.query(SearchQuery query, ResultsExtractor<T> resultsExtractor);` and the `org.springframework.data.elasticsearch.core.ResultsExtractor` interface have been removed.
These could be used to parse the result from Elasticsearch for cases in which the response mapping done with the Jackson based mapper was not enough.
Since version 4.0, there are the new xref:elasticsearch/template.adoc#elasticsearch.operations.searchresulttypes[Search Result Types] to return the information from an Elasticsearch response, so there is no need to expose this low level functionality.
* The method `org.springframework.data.elasticsearch.core.ElasticsearchOperations.query(SearchQuery query, ResultsExtractor<T> resultsExtractor);` and the `org.springframework.data.elasticsearch.core.ResultsExtractor` interface have been removed. These could be used to parse the result from Elasticsearch for cases in which the response mapping done with the Jackson based mapper was not enough. Since version 4.0, there are the new <<elasticsearch.operations.searchresulttypes>> to return the information from an Elasticsearch response, so there is no need to expose this low level functionality.
* The low level methods `startScroll`, `continueScroll` and `clearScroll` have been removed from the `ElasticsearchOperations` interface.
For low level scroll API access, there now are `searchScrollStart`, `searchScrollContinue` and `searchScrollClear` methods on the `ElasticsearchRestTemplate` class.
* The low level methods `startScroll`, `continueScroll` and `clearScroll` have been removed from the `ElasticsearchOperations` interface. For low level scroll API access, there now are `searchScrollStart`, `searchScrollContinue` and `searchScrollClear` methods on the `ElasticsearchRestTemplate` class.
@@ -54,7 +54,7 @@ If a refresh policy is set, then it will be used by the repositories as well.
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.refresh-policy.configuration]]
==== Refresh configuration
When configuring Spring Data Elasticsearch like described in xref:elasticsearch/clients.adoc[Elasticsearch Clients] by using `ElasticsearchConfigurationSupport`, `AbstractElasticsearchConfiguration` or `AbstractReactiveElasticsearchConfiguration` the refresh policy will be initialized to `null`.
When configuring Spring Data Elasticsearch like described in <<elasticsearch.clients>> by using `ElasticsearchConfigurationSupport`, `AbstractElasticsearchConfiguration` or `AbstractReactiveElasticsearchConfiguration` the refresh policy will be initialized to `null`.
Previously the reactive code initialized this to `IMMEDIATE`, now reactive and non-reactive code show the same behaviour.
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.method-return-types]]
@@ -15,7 +15,7 @@ For the user that means, that some enum classes that were used are replaced by e
Places where classes are used that cannot easily be replaced, this usage is marked as deprecated, we are working on replacements.
Check the sections on xref:migration-guides/migration-guide-4.2-4.3.adoc#elasticsearch-migration-guide-4.2-4.3.deprecations[Deprecations] and xref:migration-guides/migration-guide-4.2-4.3.adoc#elasticsearch-migration-guide-4.2-4.3.breaking-changes[Breaking Changes] for further details.
Check the sections on <<elasticsearch-migration-guide-4.2-4.3.deprecations>> and <<elasticsearch-migration-guide-4.2-4.3.breaking-changes>> for further details.
====
[[elasticsearch-migration-guide-4.2-4.3.deprecations]]
@@ -18,7 +18,6 @@ As there now are multiple implementations using different client libraries the `
[[elasticsearch-migration-guide-4.3-4.4.breaking-changes.1]]
=== Removal of deprecated classes
[[org-springframework-data-elasticsearch-core-elasticsearchtemplate-has-been-removed]]
==== `org.springframework.data.elasticsearch.core.ElasticsearchTemplate` has been removed
As of version 4.4 Spring Data Elasticsearch does not use the `TransportClient` from Elasticsearch anymore (which itself is deprecated since Elasticsearch 7.0).
@@ -39,7 +38,7 @@ The `ReactiveElasticsearchTemplate`, when created directly or by Spring Boot con
This could cause performance issues on heavy indexing and was different than the default behaviour of Elasticsearch.
This has been changed to that now the default refresh policy is NONE.
When the
`ReactiveElasticsearchTemplate` was provided by using the configuration like described in xref:elasticsearch/clients.adoc#elasticsearch.clients.reactiverestclient[Reactive REST Client] the default refresh policy already was set to NONE.
`ReactiveElasticsearchTemplate` was provided by using the configuration like described in <<elasticsearch.clients.reactive>> the default refresh policy already was set to NONE.
[[elasticsearch-migration-guide-4.3-4.4.new-clients]]
== New Elasticsearch client
@@ -76,7 +75,7 @@ public class SpringdataElasticTestApplication {
====
Remove Spring Data Elasticsearch related properties from your application configuration.
If Spring Data Elasticsearch was configured using a programmatic configuration (see xref:elasticsearch/clients.adoc[Elasticsearch Clients]), remove these beans from the Spring application context.
If Spring Data Elasticsearch was configured using a programmatic configuration (see <<elasticsearch.clients>>), remove these beans from the Spring application context.
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to.dependencies]]
==== Add dependencies
@@ -6,15 +6,16 @@ This section describes breaking changes from version 4.4.x to 5.0.x and how remo
[[elasticsearch-migration-guide-4.4-4.5.deprecations]]
== Deprecations
[[custom-trace-level-logging]]
=== Custom trace level logging
Logging by setting the property `logging.level.org.springframework.data.elasticsearch.client.WIRE=trace` is deprecated now, the Elasticsearch `RestClient` provides a better solution that can be activated by setting the logging level of the `tracer` package to "trace".
Logging by setting the property `logging.level.org.springframework.data.elasticsearch.client.WIRE=trace` is
deprecated now, the Elasticsearch `RestClient` provides a better solution that can be activated by setting the
logging level of the `tracer` package to "trace".
[[elasticsearch-migration-guide-4.4-4.5.deprecations.package]]
=== `org.springframework.data.elasticsearch.client.erhlc` package
See xref:migration-guides/migration-guide-4.4-5.0.adoc#elasticsearch-migration-guide-4.4-5.0.breaking-changes-packages[Package changes], all classes in this package have been deprecated, as the default client implementations to use are the ones based on the new Java Client from Elasticsearch, see xref:migration-guides/migration-guide-4.4-5.0.adoc#elasticsearch-migration-guide-4.4-5.0.new-clients[New Elasticsearch client]
See <<elasticsearch-migration-guide-4.4-5.0.breaking-changes-packages>>, all classes in this package have been deprecated, as the default client implementations to use are the ones based on the new Java Client from Elasticsearch, see <<elasticsearch-migration-guide-4.4-5.0.new-clients>>
[[elasticsearch-migration-guide-4.4-4.5.deprecations.code]]
=== Removal of deprecated code
@@ -49,7 +50,7 @@ Also the reactive implementation that was provided up to now has been moved here
If you are using `ElasticsearchRestTemplate` directly and not the `ElasticsearchOperations` interface you'll need to adjust your imports as well.
When working with the `NativeSearchQuery` class, you'll need to switch to the `NativeQuery` class, which can take a
`Query` instance coming from the new Elasticsearch client libraries.
`Query` instance comign from the new Elasticsearch client libraries.
You'll find plenty of examples in the test code.
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes-records]]
@@ -18,10 +18,9 @@ Adaptions are necessary when this enum was used at other places than as a proper
[[elasticsearch-migration-guide-5.0-5.1.deprecations]]
== Deprecations
[[template-functions]]
=== template functions
The functions in the `IndexOperations` and `ReactiverIndexOperations` to manage index templates that were introduced in Spring Data Elasticsearch 4.1
have been deprecated. They were using the old Elasticsearch API that was deprecated in Elasticsearch version 7.8.
Please use the new functions that are based on the composable index template API instead.
Please use the new functions that are based on the compsable index template API instead.
@@ -2,7 +2,7 @@
= Miscellaneous Elasticsearch Operation Support
This chapter covers additional support for Elasticsearch operations that cannot be directly accessed via the repository interface.
It is recommended to add those operations as custom implementation as described in xref:repositories/custom-implementations.adoc[] .
It is recommended to add those operations as custom implementation as described in <<repositories.custom-implementations>> .
[[elasticsearc.misc.index.settings]]
== Index settings
@@ -50,7 +50,7 @@ class Entity {
[[elasticsearch.misc.mappings]]
== Index Mapping
When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model.annotations[Mapping Annotation Overview], especially the `@Field` annotation.
When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <<elasticsearch.mapping.meta-model.annotations>>, especially the `@Field` annotation.
In addition to that it is possible to add the `@Mapping` annotation to a class.
This annotation has the following properties:
@@ -181,7 +181,7 @@ interface SampleEntityRepository extends Repository<SampleEntity, String> {
[[elasticsearch.misc.sorts]]
== Sort options
In addition to the default sort options described in xref:repositories/query-methods-details.adoc#repositories.paging-and-sorting[Paging and Sorting], Spring Data Elasticsearch provides the class `org.springframework.data.elasticsearch.core.query.Order` which derives from `org.springframework.data.domain.Sort.Order`.
In addition to the default sort options described in <<repositories.paging-and-sorting>>, Spring Data Elasticsearch provides the class `org.springframework.data.elasticsearch.core.query.Order` which derives from `org.springframework.data.domain.Sort.Order`.
It offers additional parameters that can be sent to Elasticsearch when specifying the sorting of the result (see https://www.elastic.co/guide/en/elasticsearch/reference/7.15/sort-search-results.html).
There also is the `org.springframework.data.elasticsearch.core.query.GeoDistanceOrder` class which can be used to have the result of a search operation ordered by geographical distance.
@@ -365,8 +365,9 @@ operations.putScript( <.>
To use a search template in a search query, Spring Data Elasticsearch provides the `SearchTemplateQuery`, an implementation of the `org.springframework.data.elasticsearch.core.query.Query` interface.
In the following code, we will add a call using a search template query to a custom repository implementation (see
xref:repositories/custom-implementations.adoc[]) as an example how this can be integrated into a repository call.
In the following code, we will add a call using a search template query to a custom repository implementation (see
<<repositories.custom-implementations>>) as
an example how this can be integrated into a repository call.
We first define the custom repository fragment interface:
@@ -398,7 +399,7 @@ public class PersonCustomRepositoryImpl implements PersonCustomRepository {
var query = SearchTemplateQuery.builder() <.>
.withId("person-firstname") <.>
.withParams(
Map.of( <.>
Map.of( <.>
"firstName", firstName,
"from", pageable.getOffset(),
"size", pageable.getPageSize()
@@ -418,35 +419,3 @@ public class PersonCustomRepositoryImpl implements PersonCustomRepository {
<.> The parameters are passed in a `Map<String,Object>`
<.> Do the search in the same way as with the other query types.
====
[[elasticsearch.misc.nested-sort]]
== Nested sort
Spring Data Elasticsearch supports sorting within nested objects (https://www.elastic.co/guide/en/elasticsearch/reference/8.9/sort-search-results.html#nested-sorting)
The following example, taken from the `org.springframework.data.elasticsearch.core.query.sort.NestedSortIntegrationTests` class, shows how to define the nested sort.
====
[source,java]
----
var filter = StringQuery.builder("""
{ "term": {"movies.actors.sex": "m"} }
""").build();
var order = new org.springframework.data.elasticsearch.core.query.Order(Sort.Direction.DESC,
"movies.actors.yearOfBirth")
.withNested(
Nested.builder("movies")
.withNested(
Nested.builder("movies.actors")
.withFilter(filter)
.build())
.build());
var query = Query.findAll().addSort(Sort.by(order));
----
====
About the filter query: It is not possible to use a `CriteriaQuery` here, as this query would be converted into a Elasticsearch nested query which does not work in the filter context. So only `StringQuery` or `NativeQuery` can be used here. When using one of these, like the term query above, the Elasticsearch field names must be used, so take care, when these are redefined with the `@Field(name="...")` definition.
For the definition of the order path and the nested paths, the Java entity property names should be used.
@@ -1,35 +1,11 @@
[[new-features]]
= What's new
[[new-features.5-3-0]]
== New in Spring Data Elasticsearch 5.3
* Upgrade to Elasticsearch 8.13.2.
* Add support for highlight queries in highlighting.
* Add shard statistics to the `SearchHit` class.
* Add support for multi search template API.
* Add support for SpEL in @Query.
* Add support for field aliases in the index mapping.
* Add support for has_child and has_parent queries.
[[new-features.5-2-0]]
== New in Spring Data Elasticsearch 5.2
* Upgrade to Elasticsearch 8.11.1
* The `JsonpMapper` for Elasticsearch is now configurable and provided as bean.
* Improved AOT runtime hints for Elasticsearch client library classes.
* Add Kotlin extensions and repository coroutine support.
* Introducing `VersionConflictException` class thrown in case thatElasticsearch reports an 409 error with a version conflict.
* Enable MultiField annotation on property getter
* Support nested sort option
* Improved scripted und runtime field support
* Improved refresh policy support
[[new-features.5-1-0]]
== New in Spring Data Elasticsearch 5.1
* Upgrade to Elasticsearch 8.7.1
* Allow specification of the TLS certificate when connecting to an Elasticsearch 8 cluster
* Upgrade to Elasticsearch 8.7.0
* Allow specification of the TLS certificate when connecting to an Elasticsearch 8 cluster
[[new-features.5-0-0]]
== New in Spring Data Elasticsearch 5.0
@@ -78,7 +54,7 @@
* Upgrade to Elasticsearch 7.6.2.
* Deprecation of `TransportClient` usage.
* Implements most of the mapping-types available for the index mappings.
* Removal of the Jackson `ObjectMapper`, now using the xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model[MappingElasticsearchConverter]
* Removal of the Jackson `ObjectMapper`, now using the <<elasticsearch.mapping.meta-model,MappingElasticsearchConverter>>
* Cleanup of the API in the `*Operations` interfaces, grouping and renaming methods so that they match the Elasticsearch API, deprecating the old methods, aligning with other Spring Data modules.
* Introduction of `SearchHit<T>` class to represent a found document together with the relevant result metadata for this document (i.e. _sortValues_).
* Introduction of the `SearchHits<T>` class to represent a whole search result together with the metadata for the complete search result (i.e. _max_score_).
@@ -92,7 +68,7 @@
* Secured Elasticsearch cluster support with Basic Authentication and SSL transport.
* Upgrade to Elasticsearch 6.8.1.
* Reactive programming support with xref:elasticsearch/repositories/reactive-elasticsearch-repositories.adoc[Reactive Elasticsearch Repositories] and xref:.
* Introduction of the xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model[ElasticsearchEntityMapper] as an alternative to the Jackson `ObjectMapper`.
* Reactive programming support with <<elasticsearch.reactive.operations>> and <<elasticsearch.reactive.repositories>>.
* Introduction of the <<elasticsearch.mapping.meta-model,ElasticsearchEntityMapper>> as an alternative to the Jackson `ObjectMapper`.
* Field name customization in `@Field`.
* Support for Delete by Query.
@@ -25,11 +25,11 @@ The most important attributes are (check the API documentation for the complete
This can contain a SpEL template expression like `"log-#{T(java.time.LocalDate).now().toString()}"`
** `createIndex`: flag whether to create an index on repository bootstrapping.
Default value is _true_.
See xref:elasticsearch/repositories/elasticsearch-repositories.adoc#elasticsearch.repositories.autocreation[Automatic creation of indices with the corresponding mapping]
See <<elasticsearch.repositories.autocreation>>
* `@Id`: Applied at the field level to mark the field used for identity purpose.
* `@Transient`, `@ReadOnlyProperty`, `@WriteOnlyProperty`: see the following section xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model.annotations.read-write[Controlling which properties are written to and read from Elasticsearch] for detailed information.
* `@Transient`, `@ReadOnlyProperty`, `@WriteOnlyProperty`: see the following section <<elasticsearch.mapping.meta-model.annotations.read-write>> for detailed information.
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database.
Constructor arguments are mapped by name to the key values in the retrieved Document.
* `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions (the following list is not complete, check the annotation Javadoc for a complete reference):
@@ -38,8 +38,8 @@ Constructor arguments are mapped by name to the key values in the retrieved Docu
See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types].
If the field type is not specified, it defaults to `FieldType.Auto`.
This means, that no mapping entry is written for the property and that Elasticsearch will add a mapping entry dynamically when the first data for this property is stored (check the Elasticsearch documentation for dynamic mapping rules).
** `format`: One or more built-in date formats, see the next section xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model.annotations.date-formats[Date format mapping].
** `pattern`: One or more custom date formats, see the next section xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model.annotations.date-formats[Date format mapping].
** `format`: One or more built-in date formats, see the next section <<elasticsearch.mapping.meta-model.annotations.date-formats>>.
** `pattern`: One or more custom date formats, see the next section <<elasticsearch.mapping.meta-model.annotations.date-formats>>.
** `store`: Flag whether the original field value should be store in Elasticsearch, default value is _false_.
** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom analyzers and normalizer.
* `@GeoPoint`: Marks a field as _geo_point_ datatype.
@@ -56,10 +56,10 @@ This section details the annotations that define if the value of a property is w
`@Transient`: A property annotated with this annotation will not be written to the mapping, it's value will not be sent to Elasticsearch and when documents are returned from Elasticsearch, this property will not be set in the resulting entity.
`@ReadOnlyProperty`: A property with this annotation will not have its value written to Elasticsearch, but when returning data, the property will be filled with the value returned in the document from Elasticsearch.
`@ReadOnlyProperty`: A property with this annotaiton will not have its value written to Elasticsearch, but when returning data, the proeprty will be filled with the value returned in the document from Elasticsearch.
One use case for this are runtime fields defined in the index mapping.
`@WriteOnlyProperty`: A property with this annotation will have its value stored in Elasticsearch but will not be set with any value when reading document.
`@WriteOnlyProperty`: A property with this annotaiton will have its value stored in Elasticsearch but will not be set with any value when reading document.
This can be used for example for synthesized fields which should go into the Elasticsearch index but are not used elsewhere.
[[elasticsearch.mapping.meta-model.annotations.date-formats]]
@@ -71,7 +71,7 @@ This paragraph describes the use of
There are two attributes of the `@Field` annotation that define which date format information is written to the mapping (also see https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats[Elasticsearch Built In Formats] and https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#custom-date-formats[Elasticsearch Custom Date Formats])
The `format` attribute is used to define at least one of the predefined formats.
The `format` attributes is used to define at least one of the predefined formats.
If it is not defined, then a default value of __date_optional_time_ and _epoch_millis_ is used.
The `pattern` attribute can be used to add additional custom format strings.
@@ -158,7 +158,7 @@ Supported classes for the type `<T>` are `Integer`, `Long`, `Float`, `Double`, `
Without further configuration, Spring Data Elasticsearch will use the property name of an object as field name in Elasticsearch.
This can be changed for individual field by using the `@Field` annotation on that property.
It is also possible to define a `FieldNamingStrategy` in the configuration of the client (xref:elasticsearch/clients.adoc[Elasticsearch Clients]).
It is also possible to define a `FieldNamingStrategy` in the configuration of the client (<<elasticsearch.clients>>).
If for example a `SnakeCaseFieldNamingStrategy` is configured, the property _sampleProperty_ of the object would be mapped to _sample_property_ in Elasticsearch.
A `FieldNamingStrategy` applies to all entities; it can be overwritten by setting a specific name with `@Field` on a property.
@@ -187,12 +187,12 @@ public String getProperty() {
[[elasticsearch.mapping.meta-model.annotations.misc]]
==== Other property annotations
[[indexedindexname]]
===== @IndexedIndexName
This annotation can be set on a String property of an entity.
This property will not be written to the mapping, it will not be stored in Elasticsearch and its value will not be read from an Elasticsearch document.
After an entity is persisted, for example with a call to `ElasticsearchOperations.save(T entity)`, the entity returned from that call will contain the name of the index that an entity was saved to in that property.
After an entity is persisted, for example with a call to `ElasticsearchOperations.save(T entity)`, the entity
returned from that call will contain the name of the index that an entity was saved to in that property.
This is useful when the index name is dynamically set by a bean, or when writing to a write alias.
Putting some value into such a property does not set the index into which an entity is stored!
@@ -260,13 +260,12 @@ public class Person {
NOTE: Type hints will not be written for nested Objects unless the properties type is `Object`, an interface or the actual value type does not match the properties declaration.
[[disabling-type-hints]]
===== Disabling Type Hints
It may be necessary to disable writing of type hints when the index that should be used already exists without having the type hints defined in its mapping and with the mapping mode set to strict.
In this case, writing the type hint will produce an error, as the field cannot be added automatically.
Type hints can be disabled for the whole application by overriding the method `writeTypeHints()` in a configuration class derived from `AbstractElasticsearchConfiguration` (see xref:elasticsearch/clients.adoc[Elasticsearch Clients]).
Type hints can be disabled for the whole application by overriding the method `writeTypeHints()` in a configuration class derived from `AbstractElasticsearchConfiguration` (see <<elasticsearch.clients>>).
As an alternative they can be disabled for a single index with the `@Document` annotation:
@@ -350,7 +349,7 @@ The following GeoJson types are implemented:
[[elasticsearch.mapping.meta-model.rules.collections]]
==== Collections
For values inside Collections apply the same mapping rules as for aggregate roots when it comes to _type hints_ and xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model.conversions[Custom Conversions].
For values inside Collections apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <<elasticsearch.mapping.meta-model.conversions>>.
.Collections
====
@@ -378,7 +377,7 @@ public class Person {
[[elasticsearch.mapping.meta-model.rules.maps]]
==== Maps
For values inside Maps apply the same mapping rules as for aggregate roots when it comes to _type hints_ and xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model.conversions[Custom Conversions].
For values inside Maps apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <<elasticsearch.mapping.meta-model.conversions>>.
However the Map key needs to a String to be processed by Elasticsearch.
.Collections
@@ -413,7 +412,7 @@ public class Person {
[[elasticsearch.mapping.meta-model.conversions]]
=== Custom Conversions
Looking at the `Configuration` from the xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model[previous section] `ElasticsearchCustomConversions` allows registering specific rules for mapping domain and simple types.
Looking at the `Configuration` from the <<elasticsearch.mapping.meta-model, previous section>> `ElasticsearchCustomConversions` allows registering specific rules for mapping domain and simple types.
.Meta Model Object Mapping Configuration
====
@@ -422,6 +421,7 @@ Looking at the `Configuration` from the xref:elasticsearch/object-mapping.adoc#e
@Configuration
public class Config extends ElasticsearchConfiguration {
@NonNull
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() //
@@ -438,7 +438,7 @@ public class Config extends ElasticsearchConfiguration {
@WritingConverter <2>
static class AddressToMap implements Converter<Address, Map<String, Object>> {
@Override
public Map<String, Object> convert(Address source) {
@@ -1,7 +1,7 @@
[[elasticsearch.operations]]
= Elasticsearch Operations
Spring Data Elasticsearch uses several interfaces to define the operations that can be called against an Elasticsearch index (for a description of the reactive interfaces see xref:elasticsearch/reactive-template.adoc[]).
Spring Data Elasticsearch uses several interfaces to define the operations that can be called against an Elasticsearch index (for a description of the reactive interfaces see <<elasticsearch.reactive.operations>>).
* `IndexOperations` defines actions on index level like creating or deleting an index.
* `DocumentOperations` defines actions to store, update and retrieve entities based on their id.
@@ -21,12 +21,12 @@ The default implementations of the interfaces offer:
====
.Index management and automatic creation of indices and mappings.
The `IndexOperations` interface and the provided implementation which can be obtained from an `ElasticsearchOperations` instance - for example with a call to `operations.indexOps(clazz)`- give the user the ability to create indices, put mappings or store template and alias information in the Elasticsearch cluster.
Details of the index that will be created can be set by using the `@Setting` annotation, refer to xref:elasticsearch/misc.adoc#elasticsearc.misc.index.settings[Index settings] for further information.
Details of the index that will be created can be set by using the `@Setting` annotation, refer to <<elasticsearc.misc.index.settings>> for further information.
**None of these operations are done automatically** by the implementations of `IndexOperations` or `ElasticsearchOperations`.
It is the user's responsibility to call the methods.
There is support for automatic creation of indices and writing the mappings when using Spring Data Elasticsearch repositories, see xref:elasticsearch/repositories/elasticsearch-repositories.adoc#elasticsearch.repositories.autocreation[Automatic creation of indices with the corresponding mapping]
There is support for automatic creation of indices and writing the mappings when using Spring Data Elasticsearch repositories, see <<elasticsearch.repositories.autocreation>>
====
@@ -34,7 +34,7 @@ There is support for automatic creation of indices and writing the mappings when
== Usage examples
The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller.
The example assumes that `Person` is a class that is annotated with `@Document`, `@Id` etc (see xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model.annotations[Mapping Annotation Overview]).
The example assumes that `Person` is a class that is annotated with `@Document`, `@Id` etc (see <<elasticsearch.mapping.meta-model.annotations>>).
.ElasticsearchOperations usage
====
@@ -73,6 +73,7 @@ The id is read from the returned entity, as it might have been null in the `pers
To see the full possibilities of `ElasticsearchOperations` please refer to the API documentation.
include::reactive-elasticsearch-operations.adoc[leveloffset=+1]
[[elasticsearch.operations.searchresulttypes]]
== Search Result Types
@@ -125,7 +126,7 @@ Almost all of the methods defined in the `SearchOperations` and `ReactiveSearchO
=== CriteriaQuery
`CriteriaQuery` based queries allow the creation of queries to search for data without knowing the syntax or basics of Elasticsearch queries.
They allow the user to build queries by simply chaining and combining `Criteria` objects that specify the criteria the searched documents must fulfill.
They allow the user to build queries by simply chaining and combining `Criteria` objects that specifiy the criteria the searched documents must fulfill.
NOTE: when talking about AND or OR when combining criteria keep in mind, that in Elasticsearch AND are converted to a **must** condition and OR to a **should**
@@ -213,14 +214,14 @@ Using `StringQuery` may be appropriate if you already have an Elasticsearch quer
`NativeQuery` is the class to use when you have a complex query, or a query that cannot be expressed by using the `Criteria` API, for example when building queries and using aggregates.
It allows to use all the different `co.elastic.clients.elasticsearch._types.query_dsl.Query` implementations from the Elasticsearch library therefore named "native".
The following code shows how to search for persons with a given `firstName` and for the found documents have a terms aggregation that counts the number of occurrences of the `lastName` for these persons:
The following code shows how to search for persons with a given firstname and for the found documents have a terms aggregation that counts the number of occurences of the lastnames for these persons:
====
[source,java]
----
Query query = NativeQuery.builder()
.withAggregation("lastNames", Aggregation.of(a -> a
.terms(ta -> ta.field("lastName").size(10))))
.terms(ta -> ta.field("last-name").size(10))))
.withQuery(q -> q
.match(m -> m
.field("firstName")
@@ -234,8 +235,8 @@ SearchHits<Person> searchHits = operations.search(query, Person.class);
----
====
[[elasticsearch.operations.searchtemplatequery]]
[[elasticsearch.operations.searchtemplateScOp§query]]
=== SearchTemplateQuery
This is a special implementation of the `Query` interface to be used in combination with a stored search template.
See xref:elasticsearch/misc.adoc#elasticsearch.misc.searchtemplates[Search Template support] for further information.
This is a special implementation of the `Query` interface to be used in combination with a stored search template.
See <<elasticsearch.misc.searchtemplates>> for further information.
@@ -12,10 +12,10 @@ class Book {
@Id
private String id;
@Field(type = FieldType.Text)
@Field(type = FieldType.text)
private String name;
@Field(type = FieldType.Text)
@Field(type = FieldType.text)
private String summary;
@Field(type = FieldType.Integer)
@@ -29,13 +29,13 @@ class Book {
[[elasticsearch.repositories.autocreation]]
== Automatic creation of indices with the corresponding mapping
The `@Document` annotation has an argument `createIndex`.
If this argument is set to true - which is the default value - Spring Data Elasticsearch will during bootstrapping the repository support on application startup check if the index defined by the `@Document` annotation exists.
The `@Document` annotation has an argument `createIndex`. If this argument is set to true - which is the default value - Spring Data Elasticsearch will during bootstrapping the repository support on application startup check if the index defined by the `@Document` annotation exists.
If it does not exist, the index will be created and the mappings derived from the entity's annotations (see xref:elasticsearch/object-mapping.adoc[Elasticsearch Object Mapping]) will be written to the newly created index.
Details of the index that will be created can be set by using the `@Setting` annotation, refer to xref:elasticsearch/misc.adoc#elasticsearc.misc.index.settings[Index settings] for further information.
If it does not exist, the index will be created and the mappings derived from the entity's annotations (see <<elasticsearch.mapping>>) will be written to the newly created index. Details of the index that will be created can be set by using the `@Setting` annotation, refer to <<elasticsearc.misc.index.settings>> for further information.
include::elasticsearch-repository-queries.adoc[leveloffset=+1]
include::reactive-elasticsearch-repositories.adoc[leveloffset=+1]
[[elasticsearch.repositories.annotations]]
== Annotations for repository methods
@@ -43,7 +43,7 @@ Details of the index that will be created can be set by using the `@Setting` ann
[[elasticsearch.repositories.annotations.highlight]]
=== @Highlight
The `@Highlight` annotation on a repository method defines for which fields of the returned entity highlighting should be included.To search for some text in a `Book` 's name or summary and have the found data highlighted, the following repository method can be used:
The `@Highlight` annotation on a repository method defines for which fields of the returned entity highlighting should be included. To search for some text in a `Book` 's name or summary and have the found data highlighted, the following repository method can be used:
====
[source,java]
@@ -125,17 +125,51 @@ class ProductService {
<1> The `EnableElasticsearchRepositories` annotation activates the Repository support.
If no base package is configured, it will use the one of the configuration class it is put on.
<2> Provide a Bean named `elasticsearchTemplate` of type `ElasticsearchOperations` by using one of the configurations shown in the xref:elasticsearch/template.adoc[Elasticsearch Operations] chapter.
<2> Provide a Bean named `elasticsearchTemplate` of type `ElasticsearchOperations` by using one of the configurations shown in the <<elasticsearch.operations>> chapter.
<3> Let Spring inject the Repository bean into your class.
====
[[elasticsearch.cdi]]
== Elasticsearch Repositories using CDI
The Spring Data Elasticsearch repositories can also be set up using CDI functionality.
.Spring Data Elasticsearch repositories using CDI
====
[source,java]
----
class ElasticsearchTemplateProducer {
@Produces
@ApplicationScoped
public ElasticsearchOperations createElasticsearchTemplate() {
// ... <1>
}
}
class ProductService {
private ProductRepository repository; <2>
public Page<Product> findAvailableBookByName(String name, Pageable pageable) {
return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
}
@Inject
public void setRepository(ProductRepository repository) {
this.repository = repository;
}
}
----
<1> Create a component by using the same calls as are used in the <<elasticsearch.operations>> chapter.
<2> Let the CDI framework inject the Repository into your class.
====
[[elasticsearch.namespace]]
== Spring Namespace
The Spring Data Elasticsearch module contains a custom namespace allowing definition of repository beans as well as elements for instantiating a `ElasticsearchServer` .
Using the `repositories` element looks up Spring Data repositories as described in xref:repositories/create-instances.adoc[].
Using the `repositories` element looks up Spring Data repositories as described in <<repositories.create-instances>> .
.Setting up Elasticsearch repositories using Namespace
====
@@ -10,12 +10,12 @@ The Elasticsearch module supports all basic query building feature as string que
=== Declared queries
Deriving the query from the method name is not always sufficient and/or may result in unreadable method names.
In this case one might make use of the `@Query` annotation (see xref:elasticsearch/repositories/elasticsearch-repository-queries.adoc#elasticsearch.query-methods.at-query[Using @Query Annotation] ).
In this case one might make use of the `@Query` annotation (see <<elasticsearch.query-methods.at-query>> ).
[[elasticsearch.query-methods.criterions]]
== Query creation
Generally the query creation mechanism for Elasticsearch works as described in xref:repositories/query-methods-details.adoc[].
Generally the query creation mechanism for Elasticsearch works as described in <<repositories.query-methods>>.
Here's a short example of what a Elasticsearch query method translates into:
.Query creation from method names
@@ -137,7 +137,7 @@ A list of supported keywords for Elasticsearch is shown below.
| `GreaterThanEqual`
| `findByPriceGreaterThanEqual`
| `findByPriceGreaterThan`
| `{ "query" : {
"bool" : {
"must" : [
@@ -316,7 +316,7 @@ Repository methods can be defined to have the following return types for returni
.Declare query on the method using the `@Query` annotation.
====
The arguments passed to the method can be inserted into placeholders in the query string. The placeholders are of the form `?0`, `?1`, `?2` etc. for the first, second, third parameter and so on.
The arguments passed to the method can be inserted into placeholders in the query string. the placeholders are of the form `?0`, `?1`, `?2` etc. for the first, second, third parameter and so on.
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@@ -361,202 +361,3 @@ would make an https://www.elastic.co/guide/en/elasticsearch/reference/current/qu
}
----
====
[[elasticsearch.query-methods.at-query.spel]]
=== Using SpEL Expressions
.Declare query on the method using the `@Query` annotation with SpEL expression.
====
https://docs.spring.io/spring-framework/reference/core/expressions.html[SpEL expression] is also supported when defining query in `@Query`.
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"term":{
"name": "#{#name}"
}
}
]
}
}
""")
Page<Book> findByName(String name, Pageable pageable);
}
----
If for example the function is called with the parameter _John_, it would produce the following query body:
[source,json]
----
{
"bool":{
"must":[
{
"term":{
"name": "John"
}
}
]
}
}
----
====
.accessing parameter property.
====
Supposing that we have the following class as query parameter type:
[source,java]
----
public record QueryParameter(String value) {
}
----
It's easy to access the parameter by `#` symbol, then reference the property `value` with a simple `.`:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"term":{
"name": "#{#parameter.value}"
}
}
]
}
}
""")
Page<Book> findByName(QueryParameter parameter, Pageable pageable);
}
----
We can pass `new QueryParameter("John")` as the parameter now, and it will produce the same query string as above.
====
.accessing bean property.
====
https://docs.spring.io/spring-framework/reference/core/expressions/language-ref/bean-references.html[Bean property] is also supported to access. Given that there is a bean named `queryParameter` of type `QueryParameter`, we can access the bean with symbol `@` rather than `#`, and there is no need to declare a parameter of type `QueryParameter` in the query method:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"term":{
"name": "#{@queryParameter.value}"
}
}
]
}
}
""")
Page<Book> findByName(Pageable pageable);
}
----
====
.SpEL and `Collection` param.
====
`Collection` parameter is also supported and is as easy to use as normal `String`, such as the following `terms` query:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"terms":{
"name": #{#names}
}
}
]
}
}
""")
Page<Book> findByName(Collection<String> names, Pageable pageable);
}
----
NOTE: collection values should not be quoted when declaring the elasticsearch json query.
A collection of `names` like `List.of("name1", "name2")` will produce the following terms query:
[source,json]
----
{
"bool":{
"must":[
{
"terms":{
"name": ["name1", "name2"]
}
}
]
}
}
----
====
.access property in the `Collection` param.
====
https://docs.spring.io/spring-framework/reference/core/expressions/language-ref/collection-projection.html[SpEL Collection Projection] is convenient to use when values in the `Collection` parameter is not plain `String`:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"terms":{
"name": #{#parameters.![value]}
}
}
]
}
}
""")
Page<Book> findByName(Collection<QueryParameter> parameters, Pageable pageable);
}
----
This will extract all the `value` property values as a new `Collection` from `QueryParameter` collection, thus takes the same effect as above.
====
.alter parameter name by using `@Param`
====
When accessing the parameter by SpEL, it's also useful to alter the parameter name to another one by `@Param` annotation in Sping Data:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"terms":{
"name": #{#another.![value]}
}
}
]
}
}
""")
Page<Book> findByName(@Param("another") Collection<QueryParameter> parameters, Pageable pageable);
}
----
====
@@ -10,7 +10,7 @@ Spring Data Elasticsearch supports routing definitions on storing and retrieving
[[elasticsearch.routing.join-types]]
== Routing on join-types
When using join-types (see xref:elasticsearch/join-types.adoc[Join-Type implementation]), Spring Data Elasticsearch will automatically use the `parent` property of the entity's `JoinField` property as the value for the routing.
When using join-types (see <<elasticsearch.jointype>>), Spring Data Elasticsearch will automatically use the `parent` property of the entity's `JoinField` property as the value for the routing.
This is correct for all the use-cases where the parent-child relationship has just one level.
If it is deeper, like a child-parent-grandparent relationship - like in the above example from _vote_ -> _answer_ -> _question_ - then the routing needs to explicitly specified by using the techniques described in the next section (the _vote_ needs the _question.id_ as routing value).
@@ -0,0 +1,20 @@
[[elasticsearch.migration]]
= Appendix E: Migration Guides
// line breaks required otherwise the TOC breaks due to joining of first/last lines.
:leveloffset: +1
include::elasticsearch-migration-guide-3.2-4.0.adoc[]
include::elasticsearch-migration-guide-4.0-4.1.adoc[]
include::elasticsearch-migration-guide-4.1-4.2.adoc[]
include::elasticsearch-migration-guide-4.2-4.3.adoc[]
include::elasticsearch-migration-guide-4.3-4.4.adoc[]
include::elasticsearch-migration-guide-4.4-5.0.adoc[]
include::elasticsearch-migration-guide-5.0-5.1.adoc[]
:leveloffset: -1
@@ -5,13 +5,18 @@
The `ReactiveElasticsearchTemplate` is the default implementation of `ReactiveElasticsearchOperations`.
[[elasticsearch.reactive.operations]]
== Reactive Elasticsearch Operations
To get started the `ReactiveElasticsearchOperations` needs to know about the actual client to work with.
Please see xref:elasticsearch/clients.adoc#elasticsearch.clients.reactiverestclient[Reactive Rest Client] for details on the client and how to configure it.
Please see <<elasticsearch.clients.reactiverestclient>> for details on the client and how to configure it.
[[elasticsearch.reactive.operations.usage]]
== Reactive Operations Usage
=== Reactive Operations Usage
`ReactiveElasticsearchOperations` lets you save, find and delete your domain objects and map those objects to documents stored in Elasticsearch.
`ReactiveElasticsearchOperations` lets you save, find and delete your domain objects and map those objects to documents
stored
in Elasticsearch.
Consider the following:
@@ -56,8 +61,8 @@ The above outputs the following sequence on the console.
> QjWCWWcBXiLAnp77ksfR
> 0
----
<.> Insert a new `Person` document into the _marvel_ index . The `id` is generated on server side and set into the instance returned.
<.> Insert a new `Person` document into the _marvel_ index . The `id` is generated on server
side and set into the instance returned.
<.> Lookup the `Person` with matching `id` in the _marvel_ index.
<.> Delete the `Person` with matching `id`, extracted from the given instance, in the _marvel_ index.
<.> Count the total number of documents in the _marvel_ index.
@@ -1,9 +1,11 @@
[[elasticsearch.reactive.repositories]]
= Reactive Elasticsearch Repositories
Reactive Elasticsearch repository support builds on the core repository support explained in xref:repositories.adoc[] utilizing operations provided via xref:elasticsearch/reactive-template.adoc[] executed by a xref:elasticsearch/clients.adoc#elasticsearch.clients.reactiverestclient[Reactive REST Client].
Reactive Elasticsearch repository support builds on the core repository support explained in <<repositories>> utilizing
operations provided via <<elasticsearch.reactive.operations>> executed by a <<elasticsearch.clients.reactive>>.
Spring Data Elasticsearch reactive repository support uses https://projectreactor.io/[Project Reactor] as its reactive composition library of choice.
Spring Data Elasticsearch reactive repository support uses https://projectreactor.io/[Project Reactor] as its reactive
composition library of choice.
There are 3 main interfaces to be used:
@@ -21,27 +21,17 @@ import java.util.Map;
/**
* @author Peter-Josef Meisch
* @author Illia Ulianov
* @since 4.1
*/
public class BulkFailureException extends DataRetrievalFailureException {
private final Map<String, FailureDetails> failedDocuments;
private final Map<String, String> failedDocuments;
public BulkFailureException(String msg, Map<String, FailureDetails> failedDocuments) {
public BulkFailureException(String msg, Map<String, String> failedDocuments) {
super(msg);
this.failedDocuments = failedDocuments;
}
public Map<String, FailureDetails> getFailedDocuments() {
public Map<String, String> getFailedDocuments() {
return failedDocuments;
}
/**
* Details about a document saving failure.
*
* @author Illia Ulianov
* @since 5.2
*/
public record FailureDetails(Integer status, String errorMessage) {
}
}
@@ -39,173 +39,40 @@ public enum DateFormat {
basic_t_time("'T'HHmmss.SSSXXX"), //
basic_t_time_no_millis("'T'HHmmssXXX"), //
basic_week_date("YYYY'W'wwe"), // week-based-year!
/**
* @since 5.3
*/
strict_basic_week_date("YYYY'W'wwe"), // week-based-year!
basic_week_date_time("YYYY'W'wwe'T'HHmmss.SSSX"), // here Elasticsearch uses a different zone format
/**
* @since 5.3
*/
strict_basic_week_date_time("YYYY'W'wwe'T'HHmmss.SSSX"), // here Elasticsearch uses a different zone format
basic_week_date_time_no_millis("YYYY'W'wwe'T'HHmmssX"), //
/**
* @since 5.3
*/
strict_basic_week_date_time_no_millis("YYYY'W'wwe'T'HHmmssX"), //
date("uuuu-MM-dd"), //
/**
* @since 5.3
*/
strict_date("uuuu-MM-dd"), //
date_hour("uuuu-MM-dd'T'HH"), //
/**
* @since 5.3
*/
strict_date_hour("uuuu-MM-dd'T'HH"), //
date_hour_minute("uuuu-MM-dd'T'HH:mm"), //
/**
* @since 5.3
*/
strict_date_hour_minute("uuuu-MM-dd'T'HH:mm"), //
date_hour_minute_second("uuuu-MM-dd'T'HH:mm:ss"), //
/**
* @since 5.3
*/
strict_date_hour_minute_second("uuuu-MM-dd'T'HH:mm:ss"), //
date_hour_minute_second_fraction("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
/**
* @since 5.3
*/
strict_date_hour_minute_second_fraction("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
date_hour_minute_second_millis("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
/**
* @since 5.3
*/
strict_date_hour_minute_second_millis("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), //
/**
* @since 5.3
*/
strict_date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), //
strict_date_optional_time_nanos("uuuu-MM-dd['T'HH:mm:ss.SSSSSSXXX]"), //
date_time("uuuu-MM-dd'T'HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_date_time("uuuu-MM-dd'T'HH:mm:ss.SSSXXX"), //
date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in its implementation
/**
* @since 5.3
*/
strict_date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in its implementation
epoch_millis("epoch_millis"), //
epoch_second("epoch_second"), //
hour("HH"), //
/**
* @since 5.3
*/
strict_hour("HH"), //
hour_minute("HH:mm"), //
/**
* @since 5.3
*/
strict_hour_minute("HH:mm"), //
hour_minute_second("HH:mm:ss"), //
/**
* @since 5.3
*/
strict_hour_minute_second("HH:mm:ss"), //
hour_minute_second_fraction("HH:mm:ss.SSS"), //
/**
* @since 5.3
*/
strict_hour_minute_second_fraction("HH:mm:ss.SSS"), //
hour_minute_second_millis("HH:mm:ss.SSS"), //
/**
* @since 5.3
*/
strict_hour_minute_second_millis("HH:mm:ss.SSS"), //
ordinal_date("uuuu-DDD"), //
/**
* @since 5.3
*/
strict_ordinal_date("uuuu-DDD"), //
ordinal_date_time("uuuu-DDD'T'HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_ordinal_date_time("uuuu-DDD'T'HH:mm:ss.SSSXXX"), //
ordinal_date_time_no_millis("uuuu-DDD'T'HH:mm:ssXXX"), //
/**
* @since 5.3
*/
strict_ordinal_date_time_no_millis("uuuu-DDD'T'HH:mm:ssXXX"), //
time("HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_time("HH:mm:ss.SSSXXX"), //
time_no_millis("HH:mm:ssXXX"), //
/**
* @since 5.3
*/
strict_time_no_millis("HH:mm:ssXXX"), //
t_time("'T'HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_t_time("'T'HH:mm:ss.SSSXXX"), //
t_time_no_millis("'T'HH:mm:ssXXX"), //
/**
* @since 5.3
*/
strict_t_time_no_millis("'T'HH:mm:ssXXX"), //
week_date("YYYY-'W'ww-e"), //
/**
* @since 5.3
*/
strict_week_date("YYYY-'W'ww-e"), //
week_date_time("YYYY-'W'ww-e'T'HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_week_date_time("YYYY-'W'ww-e'T'HH:mm:ss.SSSXXX"), //
week_date_time_no_millis("YYYY-'W'ww-e'T'HH:mm:ssXXX"), //
/**
* @since 5.3
*/
strict_week_date_time_no_millis("YYYY-'W'ww-e'T'HH:mm:ssXXX"), //
weekyear(""), // no TemporalAccessor available for these 3
/**
* @since 5.3
*/
strict_weekyear(""), // no TemporalAccessor available for these 3
weekyear_week(""), //
/**
* @since 5.3
*/
strict_weekyear_week(""), //
weekyear_week_day(""), //
/**
* @since 5.3
*/
strict_strict_weekyear_week_day(""), //
year("uuuu"), //
/**
* @since 5.3
*/
strict_year("uuuu"), //
year_month("uuuu-MM"), //
/**
* @since 5.3
*/
strict_year_month("uuuu-MM"), //
year_month_day("uuuu-MM-dd"), //
/**
* @since 5.3
*/
strict_year_month_day("uuuu-MM-dd"); //
year_month_day("uuuu-MM-dd"); //
private final String pattern;
@@ -58,13 +58,6 @@ public @interface Document {
*/
boolean createIndex() default true;
/**
* If true, the index mapping will be written on repository bootstrapping even when the index already exists. This
* allows for automatically updating the mapping with new properties. Changes on existing properties will lead to an
* error from the Elasticsearch server.
*/
boolean alwaysWriteMapping() default false;
/**
* Configuration of version management.
*/
@@ -61,31 +61,7 @@ public enum FieldType {
/** since 4.2 */
Wildcard("wildcard"), //
/** @since 4.2 */
Dense_Vector("dense_vector"), //
/**
* @since 5.2
*/
Constant_Keyword("constant_keyword"), //
/**
* @since 5.2
*/
Alias("alias"), //
/**
* @since 5.2
*/
Version("version"), //
/**
* @since 5.2
*/
Murmur3("murmur3"), //
/**
* @since 5.2
*/
Match_Only_Text("match_only_text"), //
/**
* @since 5.2
*/
Annotated_Text("annotated_text") //
Dense_Vector("dense_vector") //
;
private final String mappedName;
@@ -21,7 +21,6 @@ import java.lang.annotation.RetentionPolicy;
/**
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.0
*/
@Documented
@@ -60,8 +59,6 @@ public @interface HighlightParameters {
int numberOfFragments() default -1;
Query highlightQuery() default @Query;
String order() default "";
int phraseLimit() default -1;
@@ -15,6 +15,9 @@
*/
package org.springframework.data.elasticsearch.annotations;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Transient;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -22,10 +25,10 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to mark a String property of an entity to be filled with the name of the index where the entity was stored
* after it is indexed into Elasticsearch. This can be used when the name of the index is dynamically created or when a
* document was indexed into a write alias.
* <p>
* Annotation to mark a String property of an entity to be filled with the name of the index where the entity was
* stored after it is indexed into Elasticsearch. This can be used when the name of the index is dynamically created
* or when a document was indexed into a write alias.
*
* This can not be used to specify the index where an entity should be written to.
*
* @author Peter-Josef Meisch
@@ -74,14 +74,7 @@ public @interface Mapping {
*/
String runtimeFieldsPath() default "";
/**
* field alias definitions to be written to the index mapping
*
* @since 5.3
*/
MappingAlias[] aliases() default {};
enum Detection {
DEFAULT, TRUE, FALSE
DEFAULT, TRUE, FALSE;
}
}
@@ -1,45 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Defines a field alias in the index mapping.
*
* @author Peter-Josef Meisch
* @since 5.3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface MappingAlias {
/**
* the name of the alias.
*/
String name();
/**
* the path of the alias.
*/
String path();
}
@@ -30,7 +30,7 @@ import java.lang.annotation.Target;
* @author Peter-Josef Meisch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Documented
public @interface MultiField {
@@ -3,7 +3,6 @@ package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.*;
/**
* Marks a property to be populated with the result of a scripted field retrieved from an Elasticsearch response.
* @author Ryan Murfitt
*/
@Retention(RetentionPolicy.RUNTIME)
@@ -25,11 +25,11 @@ import org.springframework.data.util.ReactiveWrappers;
*/
public class ElasticsearchAotPredicates {
public static final Predicate<ReactiveWrappers.ReactiveLibrary> IS_REACTIVE_LIBRARY_AVAILABLE = (
public static final Predicate<ReactiveWrappers.ReactiveLibrary> IS_REACTIVE_LIBARARY_AVAILABLE = (
lib) -> ReactiveWrappers.isAvailable(lib);
public static boolean isReactorPresent() {
return IS_REACTIVE_LIBRARY_AVAILABLE.test(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
return IS_REACTIVE_LIBARARY_AVAILABLE.test(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
}
}
@@ -27,18 +27,21 @@ import org.springframework.data.elasticsearch.client.elc.EntityAsMap;
import org.springframework.data.elasticsearch.core.event.AfterConvertCallback;
import org.springframework.data.elasticsearch.core.event.AfterLoadCallback;
import org.springframework.data.elasticsearch.core.event.AfterSaveCallback;
import org.springframework.data.elasticsearch.core.event.AuditingEntityCallback;
import org.springframework.data.elasticsearch.core.event.BeforeConvertCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveAfterConvertCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveAfterLoadCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveAfterSaveCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveAuditingEntityCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveBeforeConvertCallback;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.lang.Nullable;
/**
* @author Peter-Josef Meisch
* @since 5.1
*/
public class SpringDataElasticsearchRuntimeHints implements RuntimeHintsRegistrar {
public class ElasticsearchRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
@@ -67,5 +70,6 @@ public class SpringDataElasticsearchRuntimeHints implements RuntimeHintsRegistra
// properties needed to log the different versions
hints.resources().registerPattern("versions.properties");
hints.resources().registerPattern("co/elastic/clients/version.properties");
}
}
@@ -137,6 +137,7 @@ public interface ClientConfiguration {
* Returns the {@link java.time.Duration connect timeout}.
*
* @see java.net.Socket#connect(SocketAddress, int)
* @see io.netty.channel.ChannelOption#CONNECT_TIMEOUT_MILLIS
*/
Duration getConnectTimeout();
@@ -144,6 +145,8 @@ public interface ClientConfiguration {
* Returns the {@link java.time.Duration socket timeout} which is typically applied as SO-timeout/read timeout.
*
* @see java.net.Socket#setSoTimeout(int)
* @see io.netty.handler.timeout.ReadTimeoutHandler
* @see io.netty.handler.timeout.WriteTimeoutHandler
*/
Duration getSocketTimeout();
@@ -233,15 +236,6 @@ public interface ClientConfiguration {
*/
TerminalClientConfigurationBuilder usingSsl();
/**
* Connects using https if flag is true.
*
* @param flag whether to use https in the connection
* @return the {@link TerminalClientConfigurationBuilder}
* @since 5.3
*/
TerminalClientConfigurationBuilder usingSsl(boolean flag);
/**
* Connect via {@literal https} using the given {@link SSLContext}.<br />
* <strong>NOTE</strong> You need to leave out the protocol in
@@ -300,6 +294,7 @@ public interface ClientConfiguration {
* @param timeout the timeout to use. Must not be {@literal null}.
* @return the {@link TerminalClientConfigurationBuilder}
* @see java.net.Socket#connect(SocketAddress, int)
* @see io.netty.channel.ChannelOption#CONNECT_TIMEOUT_MILLIS
*/
TerminalClientConfigurationBuilder withConnectTimeout(Duration timeout);
@@ -320,6 +315,8 @@ public interface ClientConfiguration {
* @param timeout the timeout to use. Must not be {@literal null}.
* @return the {@link TerminalClientConfigurationBuilder}
* @see java.net.Socket#setSoTimeout(int)
* @see io.netty.handler.timeout.ReadTimeoutHandler
* @see io.netty.handler.timeout.WriteTimeoutHandler
*/
TerminalClientConfigurationBuilder withSocketTimeout(Duration timeout);
@@ -25,6 +25,7 @@ import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint;
import org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder;
import org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder;
@@ -58,6 +59,7 @@ class ClientConfigurationBuilder
@Nullable private String pathPrefix;
@Nullable private String proxy;
private Supplier<HttpHeaders> headersSupplier = HttpHeaders::new;
@Deprecated private final HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
private final List<ClientConfiguration.ClientConfigurationCallback<?>> clientConfigurers = new ArrayList<>();
/*
@@ -105,13 +107,6 @@ class ClientConfigurationBuilder
return this;
}
@Override
public TerminalClientConfigurationBuilder usingSsl(boolean flag) {
this.useSsl = flag;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder#usingSsl(javax.net.ssl.SSLContext)
@@ -247,7 +242,7 @@ class ClientConfigurationBuilder
}
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, caFingerprint, soTimeout, connectTimeout,
pathPrefix, hostnameVerifier, proxy, clientConfigurers, headersSupplier);
pathPrefix, hostnameVerifier, proxy, httpClientConfigurer, clientConfigurers, headersSupplier);
}
private static InetSocketAddress parse(String hostAndPort) {
@@ -0,0 +1,195 @@
/*
* Copyright 2018-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* Logging Utility to log client requests and responses. Logs client requests and responses to Elasticsearch to a
* dedicated logger: {@code org.springframework.data.elasticsearch.client.WIRE} on trace level.
*
* @author Mark Paluch
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Oliver Drotbohm
* @since 3.2
* @deprecated since 5.0, Elasticsearch's RestClient has a trace level logging available.
*/
@Deprecated
public abstract class ClientLogger {
private static final Log WIRE_LOGGER = LogFactory.getLog("org.springframework.data.elasticsearch.client.WIRE");
private ClientLogger() {}
/**
* Returns {@literal true} if the logger is enabled.
*
* @return {@literal true} if the logger is enabled.
*/
public static boolean isEnabled() {
return WIRE_LOGGER.isTraceEnabled();
}
/**
* Log an outgoing HTTP request.
*
* @param logId the correlation id, see {@link #newLogId()}.
* @param method HTTP method
* @param endpoint URI
* @param parameters optional parameters.
*/
public static void logRequest(String logId, String method, String endpoint, Object parameters) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Sending request %s %s with parameters: %s", logId, method.toUpperCase(),
endpoint, parameters));
}
}
/**
* Log an outgoing HTTP request.
*
* @param logId the correlation id, see {@link #newLogId()}.
* @param method HTTP method
* @param endpoint URI
* @param parameters optional parameters.
* @param headers a String containing the headers
* @since 4.4
*/
public static void logRequest(String logId, String method, String endpoint, Object parameters, String headers) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Sending request%n%s %s%nParameters: %s%nHeaders: %s", logId,
method.toUpperCase(), endpoint, parameters, headers));
}
}
/**
* Log an outgoing HTTP request with a request body.
*
* @param logId the correlation id, see {@link #newLogId()}.
* @param method HTTP method
* @param endpoint URI
* @param parameters optional parameters.
* @param body body content supplier.
*/
public static void logRequest(String logId, String method, String endpoint, Object parameters,
Supplier<Object> body) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Sending request %s %s with parameters: %s%nRequest body: %s", logId,
method.toUpperCase(), endpoint, parameters, body.get()));
}
}
/**
* Log an outgoing HTTP request with a request body.
*
* @param logId the correlation id, see {@link #newLogId()}.
* @param method HTTP method
* @param endpoint URI
* @param parameters optional parameters.
* @param headers a String containing the headers
* @param body body content supplier.
* @since 4.4
*/
public static void logRequest(String logId, String method, String endpoint, Object parameters, String headers,
Supplier<Object> body) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Sending request%n%s %s%nParameters: %s%nHeaders: %s%nRequest body: %s",
logId, method.toUpperCase(), endpoint, parameters, headers, body.get()));
}
}
/**
* Log a raw HTTP response without logging the body.
*
* @param logId the correlation id, see {@link #newLogId()}.
* @param statusCode the HTTP status code.
*/
public static void logRawResponse(String logId, @Nullable Integer statusCode) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Received raw response: %d", logId, statusCode));
}
}
/**
* Log a raw HTTP response without logging the body.
*
* @param logId the correlation id, see {@link #newLogId()}.
* @param statusCode the HTTP status code.
* @param headers a String containing the headers
*/
public static void logRawResponse(String logId, @Nullable Integer statusCode, String headers) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Received response: %d%n%s", logId, statusCode, headers));
}
}
/**
* Log a raw HTTP response along with the body.
*
* @param logId the correlation id, see {@link #newLogId()}.
* @param statusCode the HTTP status code.
* @param body body content.
*/
public static void logResponse(String logId, Integer statusCode, String body) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Received response: %d%nResponse body: %s", logId, statusCode, body));
}
}
/**
* Log a raw HTTP response along with the body.
*
* @param logId the correlation id, see {@link #newLogId()}.
* @param statusCode the HTTP status code.
* @param headers a String containing the headers
* @param body body content.
* @since 4.4
*/
public static void logResponse(String logId, @Nullable Integer statusCode, String headers, String body) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Received response: %d%nHeaders: %s%nResponse body: %s", logId, statusCode,
headers, body));
}
}
/**
* Creates a new, unique correlation id to improve tracing across log events.
*
* @return a new, unique correlation id.
*/
public static String newLogId() {
if (!isEnabled()) {
return "-";
}
return ObjectUtils.getIdentityHexString(new Object());
}
}
@@ -49,13 +49,15 @@ class DefaultClientConfiguration implements ClientConfiguration {
@Nullable private final String pathPrefix;
@Nullable private final HostnameVerifier hostnameVerifier;
@Nullable private final String proxy;
private final HttpClientConfigCallback httpClientConfigurer;
private final Supplier<HttpHeaders> headersSupplier;
private final List<ClientConfigurationCallback<?>> clientConfigurers;
DefaultClientConfiguration(List<InetSocketAddress> hosts, HttpHeaders headers, boolean useSsl,
@Nullable SSLContext sslContext, @Nullable String caFingerprint, Duration soTimeout, Duration connectTimeout,
@Nullable String pathPrefix, @Nullable HostnameVerifier hostnameVerifier, @Nullable String proxy,
List<ClientConfigurationCallback<?>> clientConfigurers, Supplier<HttpHeaders> headersSupplier) {
HttpClientConfigCallback httpClientConfigurer, List<ClientConfigurationCallback<?>> clientConfigurers,
Supplier<HttpHeaders> headersSupplier) {
this.hosts = List.copyOf(hosts);
this.headers = headers;
@@ -67,6 +69,7 @@ class DefaultClientConfiguration implements ClientConfiguration {
this.pathPrefix = pathPrefix;
this.hostnameVerifier = hostnameVerifier;
this.proxy = proxy;
this.httpClientConfigurer = httpClientConfigurer;
this.clientConfigurers = clientConfigurers;
this.headersSupplier = headersSupplier;
}
@@ -1,70 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.elc;
import java.util.function.Consumer;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.lang.Nullable;
/**
* An abstract class that serves as a base for query processors. It provides a common interface and basic functionality
* for query processing.
*
* @author Aouichaoui Youssef
* @since 5.3
*/
public abstract class AbstractQueryProcessor {
/**
* Convert a spring-data-elasticsearch {@literal query} to an Elasticsearch {@literal query}.
*
* @param query spring-data-elasticsearch {@literal query}.
* @param queryConverter correct mapped field names and the values to the converted values.
* @return an Elasticsearch {@literal query}.
*/
@Nullable
static co.elastic.clients.elasticsearch._types.query_dsl.Query getEsQuery(@Nullable Query query,
@Nullable Consumer<Query> queryConverter) {
if (query == null) {
return null;
}
if (queryConverter != null) {
queryConverter.accept(query);
}
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = null;
if (query instanceof CriteriaQuery criteriaQuery) {
esQuery = CriteriaQueryProcessor.createQuery(criteriaQuery.getCriteria());
} else if (query instanceof StringQuery stringQuery) {
esQuery = Queries.wrapperQueryAsQuery(stringQuery.getSource());
} else if (query instanceof NativeQuery nativeQuery) {
if (nativeQuery.getQuery() != null) {
esQuery = nativeQuery.getQuery();
} else if (nativeQuery.getSpringDataQuery() != null) {
esQuery = getEsQuery(nativeQuery.getSpringDataQuery(), queryConverter);
}
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
}
return esQuery;
}
}
@@ -16,6 +16,7 @@
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient;
import co.elastic.clients.transport.ElasticsearchTransport;
import org.elasticsearch.client.RestClient;
@@ -39,4 +40,9 @@ public class AutoCloseableElasticsearchClient extends ElasticsearchClient implem
public void close() throws Exception {
transport.close();
}
@Override
public ElasticsearchClusterClient cluster() {
return super.cluster();
}
}
@@ -39,7 +39,6 @@ import org.springframework.data.elasticsearch.core.geo.GeoBox;
import org.springframework.data.elasticsearch.core.geo.GeoJson;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.utils.geohash.Geohash;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
@@ -51,7 +50,6 @@ import org.springframework.util.Assert;
* filter.
*
* @author Peter-Josef Meisch
* @author Junghoon Ban
* @since 4.4
*/
class CriteriaFilterProcessor {
@@ -70,17 +68,10 @@ class CriteriaFilterProcessor {
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
if (chainedCriteria.isOr()) {
Collection<? extends Query> queriesForEntries = queriesForEntries(chainedCriteria);
if (!queriesForEntries.isEmpty()) {
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
queriesForEntries.forEach(boolQueryBuilder::should);
filterQueries.add(new Query(boolQueryBuilder.build()));
}
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
queriesForEntries(chainedCriteria).forEach(boolQueryBuilder::should);
filterQueries.add(new Query(boolQueryBuilder.build()));
} else if (chainedCriteria.isNegating()) {
Assert.notNull(criteria.getField(), "criteria must have a field");
Collection<? extends Query> negatingFilters = buildNegatingFilter(criteria.getField().getName(),
criteria.getFilterCriteriaEntries());
filterQueries.addAll(negatingFilters);
@@ -124,7 +115,6 @@ class CriteriaFilterProcessor {
private static Collection<? extends Query> queriesForEntries(Criteria criteria) {
Assert.notNull(criteria.getField(), "criteria must have a field");
String fieldName = criteria.getField().getName();
Assert.notNull(fieldName, "Unknown field");
@@ -179,17 +169,17 @@ class CriteriaFilterProcessor {
Assert.isTrue(values[1] instanceof String || values[1] instanceof Distance,
"Second element of a geo distance filter must be a text or a Distance");
String dist = (values[1] instanceof Distance distance) ? extractDistanceString(distance) : (String) values[1];
String dist = (values[1] instanceof Distance) ? extractDistanceString((Distance) values[1]) : (String) values[1];
return QueryBuilders.geoDistance() //
.field(fieldName) //
.distance(dist) //
.distanceType(GeoDistanceType.Plane) //
.location(location -> {
if (values[0] instanceof GeoPoint loc) {
if (values[0]instanceof GeoPoint loc) {
location.latlon(latlon -> latlon.lat(loc.getLat()).lon(loc.getLon()));
} else if (values[0] instanceof Point point) {
GeoPoint loc = GeoPoint.fromPoint(point);
} else if (values[0] instanceof Point) {
GeoPoint loc = GeoPoint.fromPoint((Point) values[0]);
location.latlon(latlon -> latlon.lat(loc.getLat()).lon(loc.getLon()));
} else {
String loc = (String) values[0];
@@ -230,8 +220,8 @@ class CriteriaFilterProcessor {
"single-element of boundedBy filter must be type of GeoBox or Box");
GeoBox geoBBox;
if (value instanceof Box box) {
geoBBox = GeoBox.fromBox(box);
if (value instanceof Box) {
geoBBox = GeoBox.fromBox((Box) value);
} else {
geoBBox = (GeoBox) value;
}
@@ -254,7 +244,7 @@ class CriteriaFilterProcessor {
Assert.isTrue(allElementsAreOfType(values, GeoPoint.class) || allElementsAreOfType(values, String.class),
" both elements of boundedBy filter must be type of GeoPoint or text(format lat,lon or geohash)");
if (values[0] instanceof GeoPoint topLeft) {
if (values[0]instanceof GeoPoint topLeft) {
GeoPoint bottomRight = (GeoPoint) values[1];
queryBuilder.boundingBox(bb -> bb //
.tlbr(tlbr -> tlbr //
@@ -276,10 +266,7 @@ class CriteriaFilterProcessor {
.tlbr(tlbr -> tlbr //
.topLeft(glb -> {
if (isGeoHash) {
// although the builder in 8.13.2 supports geohash, the server throws an error, so we convert to a
// lat,lon string here
glb.text(Geohash.toLatLon(topLeft));
// glb.geohash(gh -> gh.geohash(topLeft));
glb.geohash(gh -> gh.geohash(topLeft));
} else {
glb.text(topLeft);
}
@@ -287,8 +274,7 @@ class CriteriaFilterProcessor {
}) //
.bottomRight(glb -> {
if (isGeoHash) {
glb.text(Geohash.toLatLon(bottomRight));
// glb.geohash(gh -> gh.geohash(bottomRight));
glb.geohash(gh -> gh.geohash(bottomRight));
} else {
glb.text(bottomRight);
}
@@ -16,27 +16,21 @@
package org.springframework.data.elasticsearch.client.elc;
import static org.springframework.data.elasticsearch.client.elc.Queries.*;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
import static org.springframework.util.StringUtils.*;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.query_dsl.ChildScoreMode;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.search.InnerHits;
import co.elastic.clients.json.JsonData;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.Field;
import org.springframework.data.elasticsearch.core.query.HasChildQuery;
import org.springframework.data.elasticsearch.core.query.HasParentQuery;
import org.springframework.data.elasticsearch.core.query.InnerHitsQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -48,7 +42,7 @@ import org.springframework.util.Assert;
* @author Ezequiel Antúnez Camacho
* @since 4.4
*/
class CriteriaQueryProcessor extends AbstractQueryProcessor {
class CriteriaQueryProcessor {
/**
* creates a query from the criteria
@@ -116,18 +110,11 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
}
}
var filterQuery = CriteriaFilterProcessor.createQuery(criteria);
if (shouldQueries.isEmpty() && mustNotQueries.isEmpty() && mustQueries.isEmpty()) {
if (filterQuery.isEmpty()) {
return null;
}
// we need something to add the filter to
mustQueries.add(Query.of(qb -> qb.matchAll(m -> m)));
return null;
}
return new Query.Builder().bool(boolQueryBuilder -> {
Query query = new Query.Builder().bool(boolQueryBuilder -> {
if (!shouldQueries.isEmpty()) {
boolQueryBuilder.should(shouldQueries);
@@ -141,10 +128,10 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
boolQueryBuilder.must(mustQueries);
}
filterQuery.ifPresent(boolQueryBuilder::filter);
return boolQueryBuilder;
}).build();
return query;
}
@Nullable
@@ -187,12 +174,6 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
.scoreMode(ChildScoreMode.Avg));
}
if (criteria.isNegating() && criteria.isOr()) {
final Query query = queryBuilder.build();
queryBuilder = new Query.Builder();
queryBuilder.bool(mnqb -> mnqb.mustNot(query));
}
return queryBuilder.build();
}
@@ -246,7 +227,7 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
queryBuilder.queryString(queryStringQuery(fieldName, '*' + searchText, true, boost));
break;
case EXPRESSION:
queryBuilder.queryString(queryStringQuery(fieldName, Objects.requireNonNull(value).toString(), boost));
queryBuilder.queryString(queryStringQuery(fieldName, value.toString(), boost));
break;
case LESS:
queryBuilder //
@@ -278,7 +259,6 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
break;
case BETWEEN:
Object[] ranges = (Object[]) value;
Assert.notNull(value, "value for a between condition must not be null");
queryBuilder //
.range(rb -> {
rb.field(fieldName);
@@ -302,10 +282,10 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
.boost(boost)); //
break;
case MATCHES:
queryBuilder.match(matchQuery(fieldName, Objects.requireNonNull(value).toString(), Operator.Or, boost));
queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.Or, boost));
break;
case MATCHES_ALL:
queryBuilder.match(matchQuery(fieldName, Objects.requireNonNull(value).toString(), Operator.And, boost));
queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.And, boost));
break;
case IN:
@@ -354,35 +334,9 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
queryBuilder //
.regexp(rb -> rb //
.field(fieldName) //
.value(Objects.requireNonNull(value).toString()) //
.value(value.toString()) //
.boost(boost)); //
break;
case HAS_CHILD:
if (value instanceof HasChildQuery query) {
queryBuilder.hasChild(hcb -> hcb
.type(query.getType())
.query(getEsQuery(query.getQuery(), null))
.innerHits(getInnerHits(query.getInnerHitsQuery()))
.ignoreUnmapped(query.getIgnoreUnmapped())
.minChildren(query.getMinChildren())
.maxChildren(query.getMaxChildren())
.scoreMode(scoreMode(query.getScoreMode())));
} else {
throw new CriteriaQueryException("value for " + fieldName + " is not a has_child query");
}
break;
case HAS_PARENT:
if (value instanceof HasParentQuery query) {
queryBuilder.hasParent(hpb -> hpb
.parentType(query.getParentType())
.query(getEsQuery(query.getQuery(), null))
.innerHits(getInnerHits(query.getInnerHitsQuery()))
.ignoreUnmapped(query.getIgnoreUnmapped())
.score(query.getScore()));
} else {
throw new CriteriaQueryException("value for " + fieldName + " is not a has_parent query");
}
break;
default:
throw new CriteriaQueryException("Could not build query for " + entry);
}
@@ -405,7 +359,7 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
if (item != null) {
if (!sb.isEmpty()) {
if (sb.length() > 0) {
sb.append(' ');
}
sb.append('"');
@@ -437,19 +391,4 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
return sb.toString();
}
/**
* Convert a spring-data-elasticsearch {@literal inner_hits} to an Elasticsearch {@literal inner_hits} query.
*
* @param query spring-data-elasticsearch {@literal inner_hits}.
* @return an Elasticsearch {@literal inner_hits} query.
*/
@Nullable
private static InnerHits getInnerHits(@Nullable InnerHitsQuery query) {
if (query == null) {
return null;
}
return InnerHits.of(iqb -> iqb.from(query.getFrom()).size(query.getSize()).name(query.getName()));
}
}
@@ -49,7 +49,6 @@ import org.springframework.util.Assert;
* {@link org.springframework.data.elasticsearch.core.document.Document}
*
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.4
*/
final class DocumentAdapters {
@@ -74,7 +73,7 @@ final class DocumentAdapters {
Map<String, SearchDocumentResponse> innerHits = new LinkedHashMap<>();
hit.innerHits().forEach((name, innerHitsResult) -> {
// noinspection ReturnOfNull
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, null, null,
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, null,
searchDocument -> null, jsonpMapper));
});
@@ -116,8 +115,8 @@ final class DocumentAdapters {
if (source == null) {
document = Document.from(hitFieldsAsMap);
} else {
if (source instanceof EntityAsMap entityAsMap) {
document = Document.from(entityAsMap);
if (source instanceof EntityAsMap) {
document = Document.from((EntityAsMap) source);
} else if (source instanceof JsonData jsonData) {
document = Document.from(jsonData.to(EntityAsMap.class));
} else {
@@ -16,7 +16,6 @@
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.TransportOptions;
@@ -25,6 +24,8 @@ import co.elastic.clients.transport.Version;
import co.elastic.clients.transport.rest_client.RestClientOptions;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.Arrays;
@@ -34,10 +35,12 @@ import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.http.*;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.message.BasicHeader;
@@ -47,6 +50,7 @@ import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.ClientLogger;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -57,18 +61,15 @@ import org.springframework.util.Assert;
* @author Peter-Josef Meisch
* @since 4.4
*/
@SuppressWarnings("unused")
public final class ElasticsearchClients {
/**
* Name of whose value can be used to correlate log messages for this request.
*/
private static final String LOG_ID_ATTRIBUTE = ElasticsearchClients.class.getName() + ".LOG_ID";
private static final String X_SPRING_DATA_ELASTICSEARCH_CLIENT = "X-SpringDataElasticsearch-Client";
public static final String IMPERATIVE_CLIENT = "imperative";
public static final String REACTIVE_CLIENT = "reactive";
private static final String IMPERATIVE_CLIENT = "imperative";
private static final String REACTIVE_CLIENT = "reactive";
private static final JsonpMapper DEFAULT_JSONP_MAPPER = new JacksonJsonpMapper();
// region reactive client
/**
* Creates a new {@link ReactiveElasticsearchClient}
*
@@ -79,7 +80,7 @@ public final class ElasticsearchClients {
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
return createReactive(getRestClient(clientConfiguration), null, DEFAULT_JSONP_MAPPER);
return createReactive(getRestClient(clientConfiguration), null);
}
/**
@@ -94,24 +95,7 @@ public final class ElasticsearchClients {
Assert.notNull(clientConfiguration, "ClientConfiguration must not be null!");
return createReactive(getRestClient(clientConfiguration), transportOptions, DEFAULT_JSONP_MAPPER);
}
/**
* Creates a new {@link ReactiveElasticsearchClient}
*
* @param clientConfiguration configuration options, must not be {@literal null}.
* @param transportOptions options to be added to each request.
* @param jsonpMapper the JsonpMapper to use
* @return the {@link ReactiveElasticsearchClient}
*/
public static ReactiveElasticsearchClient createReactive(ClientConfiguration clientConfiguration,
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
Assert.notNull(clientConfiguration, "ClientConfiguration must not be null!");
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
return createReactive(getRestClient(clientConfiguration), transportOptions, jsonpMapper);
return createReactive(getRestClient(clientConfiguration), transportOptions);
}
/**
@@ -121,7 +105,7 @@ public final class ElasticsearchClients {
* @return the {@link ReactiveElasticsearchClient}
*/
public static ReactiveElasticsearchClient createReactive(RestClient restClient) {
return createReactive(restClient, null, DEFAULT_JSONP_MAPPER);
return createReactive(restClient, null);
}
/**
@@ -132,29 +116,10 @@ public final class ElasticsearchClients {
* @return the {@link ReactiveElasticsearchClient}
*/
public static ReactiveElasticsearchClient createReactive(RestClient restClient,
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
Assert.notNull(restClient, "restClient must not be null");
var transport = getElasticsearchTransport(restClient, REACTIVE_CLIENT, transportOptions, jsonpMapper);
return createReactive(transport);
@Nullable TransportOptions transportOptions) {
return new ReactiveElasticsearchClient(getElasticsearchTransport(restClient, REACTIVE_CLIENT, transportOptions));
}
/**
* Creates a new {@link ReactiveElasticsearchClient} that uses the given {@link ElasticsearchTransport}.
*
* @param transport the transport to use
* @return the {@link ElasticsearchClient
*/
public static ReactiveElasticsearchClient createReactive(ElasticsearchTransport transport) {
Assert.notNull(transport, "transport must not be null");
return new ReactiveElasticsearchClient(transport);
}
// endregion
// region imperative client
/**
* Creates a new imperative {@link ElasticsearchClient}
*
@@ -162,7 +127,7 @@ public final class ElasticsearchClients {
* @return the {@link ElasticsearchClient}
*/
public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration) {
return createImperative(getRestClient(clientConfiguration), null, DEFAULT_JSONP_MAPPER);
return createImperative(getRestClient(clientConfiguration), null);
}
/**
@@ -174,7 +139,7 @@ public final class ElasticsearchClients {
*/
public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration,
TransportOptions transportOptions) {
return createImperative(getRestClient(clientConfiguration), transportOptions, DEFAULT_JSONP_MAPPER);
return createImperative(getRestClient(clientConfiguration), transportOptions);
}
/**
@@ -184,7 +149,7 @@ public final class ElasticsearchClients {
* @return the {@link ElasticsearchClient}
*/
public static ElasticsearchClient createImperative(RestClient restClient) {
return createImperative(restClient, null, DEFAULT_JSONP_MAPPER);
return createImperative(restClient, null);
}
/**
@@ -192,51 +157,17 @@ public final class ElasticsearchClients {
*
* @param restClient the RestClient to use
* @param transportOptions options to be added to each request.
* @param jsonpMapper the mapper for the transport to use
* @return the {@link ElasticsearchClient}
*/
public static ElasticsearchClient createImperative(RestClient restClient, @Nullable TransportOptions transportOptions,
JsonpMapper jsonpMapper) {
public static ElasticsearchClient createImperative(RestClient restClient,
@Nullable TransportOptions transportOptions) {
Assert.notNull(restClient, "restClient must not be null");
ElasticsearchTransport transport = getElasticsearchTransport(restClient, IMPERATIVE_CLIENT, transportOptions,
jsonpMapper);
return createImperative(transport);
}
/**
* Creates a new {@link ElasticsearchClient} that uses the given {@link ElasticsearchTransport}.
*
* @param transport the transport to use
* @return the {@link ElasticsearchClient
*/
public static AutoCloseableElasticsearchClient createImperative(ElasticsearchTransport transport) {
Assert.notNull(transport, "transport must not be null");
ElasticsearchTransport transport = getElasticsearchTransport(restClient, IMPERATIVE_CLIENT, transportOptions);
return new AutoCloseableElasticsearchClient(transport);
}
// endregion
// region low level RestClient
private static RestClientOptions.Builder getRestClientOptionsBuilder(@Nullable TransportOptions transportOptions) {
if (transportOptions instanceof RestClientOptions restClientOptions) {
return restClientOptions.toBuilder();
}
var builder = new RestClientOptions.Builder(RequestOptions.DEFAULT.toBuilder());
if (transportOptions != null) {
transportOptions.headers().forEach(header -> builder.addHeader(header.getKey(), header.getValue()));
transportOptions.queryParameters().forEach(builder::setParameter);
builder.onWarnings(transportOptions.onWarnings());
}
return builder;
}
/**
* Creates a low level {@link RestClient} for the given configuration.
@@ -272,6 +203,13 @@ public final class ElasticsearchClients {
clientConfiguration.getHostNameVerifier().ifPresent(clientBuilder::setSSLHostnameVerifier);
clientBuilder.addInterceptorLast(new CustomHeaderInjector(clientConfiguration.getHeadersSupplier()));
if (ClientLogger.isEnabled()) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
clientBuilder.addInterceptorLast((HttpRequestInterceptor) interceptor);
clientBuilder.addInterceptorLast((HttpResponseInterceptor) interceptor);
}
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
Duration connectTimeout = clientConfiguration.getConnectTimeout();
@@ -308,55 +246,44 @@ public final class ElasticsearchClients {
}
return builder;
}
// endregion
// region Elasticsearch transport
/**
* Creates an {@link ElasticsearchTransport} that will use the given client that additionally is customized with a
* header to contain the clientType
*
* @param restClient the client to use
* @param clientType the client type to pass in each request as header
* @param transportOptions options for the transport
* @param jsonpMapper mapper for the transport
* @return ElasticsearchTransport
*/
public static ElasticsearchTransport getElasticsearchTransport(RestClient restClient, String clientType,
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
Assert.notNull(restClient, "restClient must not be null");
Assert.notNull(clientType, "clientType must not be null");
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
private static ElasticsearchTransport getElasticsearchTransport(RestClient restClient, String clientType,
@Nullable TransportOptions transportOptions) {
TransportOptions.Builder transportOptionsBuilder = transportOptions != null ? transportOptions.toBuilder()
: new RestClientOptions(RequestOptions.DEFAULT).toBuilder();
RestClientOptions.Builder restClientOptionsBuilder = getRestClientOptionsBuilder(transportOptions);
ContentType jsonContentType = Version.VERSION == null ? ContentType.APPLICATION_JSON
: ContentType.create("application/vnd.elasticsearch+json",
new BasicNameValuePair("compatible-with", String.valueOf(Version.VERSION.major())));
Consumer<String> setHeaderIfNotPresent = header -> {
if (restClientOptionsBuilder.build().headers().stream() //
if (transportOptionsBuilder.build().headers().stream() //
.noneMatch((h) -> h.getKey().equalsIgnoreCase(header))) {
// need to add the compatibility header, this is only done automatically when not passing in custom options.
// code copied from RestClientTransport as it is not available outside the package
restClientOptionsBuilder.addHeader(header, jsonContentType.toString());
transportOptionsBuilder.addHeader(header, jsonContentType.toString());
}
};
setHeaderIfNotPresent.accept("Content-Type");
setHeaderIfNotPresent.accept("Accept");
restClientOptionsBuilder.addHeader(X_SPRING_DATA_ELASTICSEARCH_CLIENT, clientType);
TransportOptions transportOptionsWithHeader = transportOptionsBuilder
.addHeader(X_SPRING_DATA_ELASTICSEARCH_CLIENT, clientType).build();
return new RestClientTransport(restClient, jsonpMapper, restClientOptionsBuilder.build());
// we need to create our own objectMapper that keeps null values in order to provide the storeNullValue
// functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get
// into this mapper, so we can safely keep them here.
var objectMapper = (new ObjectMapper())
.configure(SerializationFeature.INDENT_OUTPUT, false)
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
return new RestClientTransport(restClient, new JacksonJsonpMapper(objectMapper), transportOptionsWithHeader);
}
// endregion
private static List<String> formattedHosts(List<InetSocketAddress> hosts, boolean useSsl) {
return hosts.stream().map(it -> (useSsl ? "https" : "http") + "://" + it.getHostString() + ':' + it.getPort())
return hosts.stream().map(it -> (useSsl ? "https" : "http") + "://" + it.getHostString() + ":" + it.getPort())
.collect(Collectors.toList());
}
@@ -367,12 +294,78 @@ public final class ElasticsearchClients {
.toArray(org.apache.http.Header[]::new);
}
/**
* Logging interceptors for Elasticsearch client logging.
*
* @see ClientLogger
* @since 4.4
* @deprecated since 5.0
*/
@Deprecated
private static class HttpLoggingInterceptor implements HttpResponseInterceptor, HttpRequestInterceptor {
@Override
public void process(HttpRequest request, HttpContext context) throws IOException {
String logId = (String) context.getAttribute(LOG_ID_ATTRIBUTE);
if (logId == null) {
logId = ClientLogger.newLogId();
context.setAttribute(LOG_ID_ATTRIBUTE, logId);
}
String headers = Arrays.stream(request.getAllHeaders())
.map(header -> header.getName()
+ ((header.getName().equals("Authorization")) ? ": *****" : ": " + header.getValue()))
.collect(Collectors.joining(", ", "[", "]"));
if (request instanceof HttpEntityEnclosingRequest entityRequest
&& ((HttpEntityEnclosingRequest) request).getEntity() != null) {
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
entity.writeTo(buffer);
if (!entity.isRepeatable()) {
entityRequest.setEntity(new ByteArrayEntity(buffer.toByteArray()));
}
ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "",
headers, buffer::toString);
} else {
ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "",
headers);
}
}
@Override
public void process(HttpResponse response, HttpContext context) throws IOException {
String logId = (String) context.getAttribute(LOG_ID_ATTRIBUTE);
String headers = Arrays.stream(response.getAllHeaders())
.map(header -> header.getName()
+ ((header.getName().equals("Authorization")) ? ": *****" : ": " + header.getValue()))
.collect(Collectors.joining(", ", "[", "]"));
// no way of logging the body, in this callback, it is not read yet, later there is no callback possibility in
// RestClient or RestClientTransport
ClientLogger.logRawResponse(logId, response.getStatusLine().getStatusCode(), headers);
}
}
/**
* Interceptor to inject custom supplied headers.
*
* @since 4.4
*/
private record CustomHeaderInjector(Supplier<HttpHeaders> headersSupplier) implements HttpRequestInterceptor {
private static class CustomHeaderInjector implements HttpRequestInterceptor {
public CustomHeaderInjector(Supplier<HttpHeaders> headersSupplier) {
this.headersSupplier = headersSupplier;
}
private final Supplier<HttpHeaders> headersSupplier;
@Override
public void process(HttpRequest request, HttpContext context) {
@@ -16,9 +16,6 @@
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.TransportOptions;
import co.elastic.clients.transport.rest_client.RestClientOptions;
@@ -31,14 +28,9 @@ import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
/**
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
* connection using the Elasticsearch Client. This class exposes different parts of the setup as Spring beans. Deriving
* classes must provide the {@link ClientConfiguration} to use.
* connection using the Elasticsearch Client.
*
* @author Peter-Josef Meisch
* @since 4.4
@@ -50,11 +42,11 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
*
* @return configuration, must not be {@literal null}
*/
@Bean(name = "elasticsearchClientConfiguration")
@Bean(name="elasticsearchClientConfiguration")
public abstract ClientConfiguration clientConfiguration();
/**
* Provides the underlying low level Elasticsearch RestClient.
* Provides the underlying low level RestClient.
*
* @param clientConfiguration configuration for the client, must not be {@literal null}
* @return RestClient
@@ -67,35 +59,18 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
return ElasticsearchClients.getRestClient(clientConfiguration);
}
/**
* Provides the Elasticsearch transport to be used. The default implementation uses the {@link RestClient} bean and
* the {@link JsonpMapper} bean provided in this class.
*
* @return the {@link ElasticsearchTransport}
* @since 5.2
*/
@Bean
public ElasticsearchTransport elasticsearchTransport(RestClient restClient, JsonpMapper jsonpMapper) {
Assert.notNull(restClient, "restClient must not be null");
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
return ElasticsearchClients.getElasticsearchTransport(restClient, ElasticsearchClients.IMPERATIVE_CLIENT,
transportOptions(), jsonpMapper);
}
/**
* Provides the {@link ElasticsearchClient} to be used.
*
* @param transport the {@link ElasticsearchTransport} to use
* @param restClient the low level RestClient to use
* @return ElasticsearchClient instance
*/
@Bean
public ElasticsearchClient elasticsearchClient(ElasticsearchTransport transport) {
public ElasticsearchClient elasticsearchClient(RestClient restClient) {
Assert.notNull(transport, "transport must not be null");
Assert.notNull(restClient, "restClient must not be null");
return ElasticsearchClients.createImperative(transport);
return ElasticsearchClients.createImperative(restClient, transportOptions());
}
/**
@@ -114,23 +89,6 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
return template;
}
/**
* Provides the JsonpMapper bean that is used in the {@link #elasticsearchTransport(RestClient, JsonpMapper)} method.
*
* @return the {@link JsonpMapper} to use
* @since 5.2
*/
@Bean
public JsonpMapper jsonpMapper() {
// we need to create our own objectMapper that keeps null values in order to provide the storeNullValue
// functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get
// into this mapper, so we can safely keep them here.
var objectMapper = (new ObjectMapper())
.configure(SerializationFeature.INDENT_OUTPUT, false)
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
return new JacksonJsonpMapper(objectMapper);
}
/**
* @return the options that should be added to every request. Must not be {@literal null}
*/
@@ -32,7 +32,6 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.ResourceNotFoundException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.VersionConflictException;
/**
* Simple {@link PersistenceExceptionTranslator} for Elasticsearch. Convert the given runtime exception to an
@@ -40,7 +39,6 @@ import org.springframework.data.elasticsearch.VersionConflictException;
* appropriate: any other exception may have resulted from user code, and should not be translated.
*
* @author Peter-Josef Meisch
* @author Junghoon Ban
* @since 4.4
*/
public class ElasticsearchExceptionTranslator implements PersistenceExceptionTranslator {
@@ -60,7 +58,7 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
*/
public RuntimeException translateException(Throwable throwable) {
RuntimeException runtimeException = throwable instanceof RuntimeException ex ? ex
RuntimeException runtimeException = throwable instanceof RuntimeException ? (RuntimeException) throwable
: new RuntimeException(throwable.getMessage(), throwable);
RuntimeException potentiallyTranslatedException = translateExceptionIfPossible(runtimeException);
@@ -70,7 +68,9 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
@Override
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
checkForConflictException(ex);
if (isSeqNoConflict(ex)) {
return new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict", ex);
}
if (ex instanceof ElasticsearchException elasticsearchException) {
@@ -93,10 +93,6 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
return new ResourceNotFoundException(errorReason);
}
if (response.status() == 409) {
}
String body = JsonUtils.toJson(response, jsonpMapper);
if (errorType != null && errorType.contains("validation_exception")) {
@@ -114,7 +110,7 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
return null;
}
private void checkForConflictException(Throwable exception) {
private boolean isSeqNoConflict(Throwable exception) {
Integer status = null;
String message = null;
@@ -122,17 +118,14 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
status = responseException.getResponse().getStatusLine().getStatusCode();
message = responseException.getMessage();
} else if (exception.getCause() != null) {
checkForConflictException(exception.getCause());
return isSeqNoConflict(exception.getCause());
}
if (status != null && message != null) {
if (status == 409 && message.contains("type\":\"version_conflict_engine_exception"))
if (message.contains("version conflict, required seqNo")) {
throw new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict",
exception);
} else if (message.contains("version conflict, current version [")) {
throw new VersionConflictException("Version conflict", exception);
}
return status == 409 && message.contains("type\":\"version_conflict_engine_exception")
&& message.contains("version conflict, required seqNo");
}
return false;
}
}
@@ -29,7 +29,6 @@ import co.elastic.clients.transport.Version;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -51,7 +50,13 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
@@ -65,8 +70,6 @@ import org.springframework.util.Assert;
*
* @author Peter-Josef Meisch
* @author Hamid Rahimi
* @author Illia Ulianov
* @author Haibo Liu
* @since 4.4
*/
public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
@@ -136,7 +139,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
public <T> T get(String id, Class<T> clazz, IndexCoordinates index) {
GetRequest getRequest = requestConverter.documentGetRequest(elasticsearchConverter.convertId(id),
routingResolver.getRouting(), index);
routingResolver.getRouting(), index, false);
GetResponse<EntityAsMap> getResponse = execute(client -> client.get(getRequest, EntityAsMap.class));
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
@@ -170,11 +173,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
doBulkOperation(queries, bulkOptions, index);
}
@Override
public ByQueryResponse delete(DeleteQuery query, Class<?> clazz) {
return delete(query, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {
@@ -188,18 +186,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
return responseConverter.byQueryResponse(response);
}
@Override
public ByQueryResponse delete(DeleteQuery query, Class<?> clazz, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
clazz, index, getRefreshPolicy());
DeleteByQueryResponse response = execute(client -> client.deleteByQuery(request));
return responseConverter.byQueryResponse(response);
}
@Override
public UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index) {
@@ -236,16 +222,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Object queryObject = query.getObject();
if (queryObject != null) {
query.setObject(entityOperations.updateIndexedObject(
queryObject,
new IndexedObjectInformation(
indexResponse.id(),
indexResponse.index(),
indexResponse.seqNo(),
indexResponse.primaryTerm(),
indexResponse.version()),
elasticsearchConverter,
routingResolver));
query.setObject(updateIndexedObject(queryObject, new IndexedObjectInformation(indexResponse.id(),
indexResponse.index(), indexResponse.seqNo(), indexResponse.primaryTerm(), indexResponse.version())));
}
return indexResponse.id();
@@ -257,9 +235,9 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(id, "id must not be null");
Assert.notNull(index, "index must not be null");
ExistsRequest request = requestConverter.documentExistsRequest(id, routingResolver.getRouting(), index);
GetRequest request = requestConverter.documentGetRequest(id, routingResolver.getRouting(), index, true);
return execute(client -> client.exists(request)).value();
return execute(client -> client.get(request, EntityAsMap.class)).found();
}
@Override
@@ -449,10 +427,13 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(queries, "queries must not be null");
Assert.notNull(clazz, "clazz must not be null");
int size = queries.size();
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
for (Query query : queries) {
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, getIndexCoordinatesFor(clazz)));
}
// noinspection unchecked
return multiSearch(queries, Collections.nCopies(size, clazz), Collections.nCopies(size, index))
.stream().map(searchHits -> (SearchHits<T>) searchHits)
return doMultiSearch(multiSearchQueryParameters).stream().map(searchHits -> (SearchHits<T>) searchHits)
.collect(Collectors.toList());
}
@@ -463,7 +444,14 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(classes, "classes must not be null");
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
return multiSearch(queries, classes, classes.stream().map(this::getIndexCoordinatesFor).toList());
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
Iterator<Class<?>> it = classes.iterator();
for (Query query : queries) {
Class<?> clazz = it.next();
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, getIndexCoordinatesFor(clazz)));
}
return doMultiSearch(multiSearchQueryParameters);
}
@Override
@@ -475,7 +463,14 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(index, "index must not be null");
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
return multiSearch(queries, classes, Collections.nCopies(queries.size(), index));
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
Iterator<Class<?>> it = classes.iterator();
for (Query query : queries) {
Class<?> clazz = it.next();
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, index));
}
return doMultiSearch(multiSearchQueryParameters);
}
@Override
@@ -492,50 +487,16 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Iterator<Class<?>> it = classes.iterator();
Iterator<IndexCoordinates> indexesIt = indexes.iterator();
Assert.isTrue(!queries.isEmpty(), "queries should have at least 1 query");
boolean isSearchTemplateQuery = queries.get(0) instanceof SearchTemplateQuery;
for (Query query : queries) {
Assert.isTrue((query instanceof SearchTemplateQuery) == isSearchTemplateQuery,
"SearchTemplateQuery can't be mixed with other types of query in multiple search");
Class<?> clazz = it.next();
IndexCoordinates index = indexesIt.next();
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, index));
}
return multiSearch(multiSearchQueryParameters, isSearchTemplateQuery);
}
private List<SearchHits<?>> multiSearch(List<MultiSearchQueryParameter> multiSearchQueryParameters,
boolean isSearchTemplateQuery) {
return isSearchTemplateQuery ? doMultiTemplateSearch(multiSearchQueryParameters.stream()
.map(p -> new MultiSearchTemplateQueryParameter((SearchTemplateQuery) p.query, p.clazz, p.index))
.toList())
: doMultiSearch(multiSearchQueryParameters);
}
private List<SearchHits<?>> doMultiTemplateSearch(
List<MultiSearchTemplateQueryParameter> mSearchTemplateQueryParameters) {
MsearchTemplateRequest request = requestConverter.searchMsearchTemplateRequest(mSearchTemplateQueryParameters,
routingResolver.getRouting());
MsearchTemplateResponse<EntityAsMap> response = execute(
client -> client.msearchTemplate(request, EntityAsMap.class));
List<MultiSearchResponseItem<EntityAsMap>> responseItems = response.responses();
Assert.isTrue(mSearchTemplateQueryParameters.size() == responseItems.size(),
"number of response items does not match number of requests");
int size = mSearchTemplateQueryParameters.size();
List<Class<?>> classes = mSearchTemplateQueryParameters
.stream().map(MultiSearchTemplateQueryParameter::clazz).collect(Collectors.toList());
List<IndexCoordinates> indices = mSearchTemplateQueryParameters
.stream().map(MultiSearchTemplateQueryParameter::index).collect(Collectors.toList());
return getSearchHitsFromMsearchResponse(size, classes, indices, responseItems);
return doMultiSearch(multiSearchQueryParameters);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private List<SearchHits<?>> doMultiSearch(List<MultiSearchQueryParameter> multiSearchQueryParameters) {
MsearchRequest request = requestConverter.searchMsearchRequest(multiSearchQueryParameters,
@@ -547,37 +508,22 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.isTrue(multiSearchQueryParameters.size() == responseItems.size(),
"number of response items does not match number of requests");
int size = multiSearchQueryParameters.size();
List<Class<?>> classes = multiSearchQueryParameters
.stream().map(MultiSearchQueryParameter::clazz).collect(Collectors.toList());
List<IndexCoordinates> indices = multiSearchQueryParameters
.stream().map(MultiSearchQueryParameter::index).collect(Collectors.toList());
List<SearchHits<?>> searchHitsList = new ArrayList<>(multiSearchQueryParameters.size());
return getSearchHitsFromMsearchResponse(size, classes, indices, responseItems);
}
/**
* {@link MsearchResponse} and {@link MsearchTemplateResponse} share the same {@link MultiSearchResponseItem}
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private List<SearchHits<?>> getSearchHitsFromMsearchResponse(int size, List<Class<?>> classes,
List<IndexCoordinates> indices, List<MultiSearchResponseItem<EntityAsMap>> responseItems) {
List<SearchHits<?>> searchHitsList = new ArrayList<>(size);
Iterator<Class<?>> clazzIter = classes.iterator();
Iterator<IndexCoordinates> indexIter = indices.iterator();
Iterator<MultiSearchQueryParameter> queryIterator = multiSearchQueryParameters.iterator();
Iterator<MultiSearchResponseItem<EntityAsMap>> responseIterator = responseItems.iterator();
while (clazzIter.hasNext() && indexIter.hasNext()) {
while (queryIterator.hasNext()) {
MultiSearchQueryParameter queryParameter = queryIterator.next();
MultiSearchResponseItem<EntityAsMap> responseItem = responseIterator.next();
if (responseItem.isResult()) {
Class clazz = clazzIter.next();
IndexCoordinates index = indexIter.next();
Class clazz = queryParameter.clazz;
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz,
index);
queryParameter.index);
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(clazz,
index);
queryParameter.index);
SearchHits<?> searchHits = callback.doWith(
SearchDocumentResponseBuilder.from(responseItem.result(), getEntityCreator(documentCallback), jsonpMapper));
@@ -585,8 +531,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
searchHitsList.add(searchHits);
} else {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn(String.format("multisearch response contains failure: %s",
responseItem.failure().error().reason()));
LOGGER
.warn(String.format("multisearch responsecontains failure: {}", responseItem.failure().error().reason()));
}
}
}
@@ -600,12 +546,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
record MultiSearchQueryParameter(Query query, Class<?> clazz, IndexCoordinates index) {
}
/**
* value class combining the information needed for a single query in a template multisearch request.
*/
record MultiSearchTemplateQueryParameter(SearchTemplateQuery query, Class<?> clazz, IndexCoordinates index) {
}
@Override
public String openPointInTime(IndexCoordinates index, Duration keepAlive, Boolean ignoreUnavailable) {
@@ -698,11 +638,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
return NativeQuery.builder().withQuery(qb -> qb.ids(iq -> iq.values(ids))).build();
}
@Override
public BaseQueryBuilder queryBuilderWithIds(List<String> ids) {
return NativeQuery.builder().withIds(ids);
}
/**
* extract the list of {@link IndexedObjectInformation} from a {@link BulkResponse}.
*
@@ -712,11 +647,11 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
protected List<IndexedObjectInformation> checkForBulkOperationFailure(BulkResponse bulkResponse) {
if (bulkResponse.errors()) {
Map<String, BulkFailureException.FailureDetails> failedDocuments = new HashMap<>();
Map<String, String> failedDocuments = new HashMap<>();
for (BulkResponseItem item : bulkResponse.items()) {
if (item.error() != null) {
failedDocuments.put(item.id(), new BulkFailureException.FailureDetails(item.status(), item.error().reason()));
failedDocuments.put(item.id(), item.error().reason());
}
}
throw new BulkFailureException(
@@ -35,17 +35,14 @@ import org.springframework.util.StringUtils;
* {@link co.elastic.clients.elasticsearch.core.search.Highlight}.
*
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.4
*/
class HighlightQueryBuilder {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private final RequestConverter requestConverter;
HighlightQueryBuilder(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, RequestConverter requestConverter) {
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
this.mappingContext = mappingContext;
this.requestConverter = requestConverter;
}
public co.elastic.clients.elasticsearch.core.search.Highlight getHighlight(Highlight highlight,
@@ -55,7 +52,7 @@ class HighlightQueryBuilder {
// in the old implementation we could use one addParameters method, but in the new Elasticsearch client
// the builder for highlight and highlightfield share no code
addParameters(highlight.getParameters(), highlightBuilder, type);
addParameters(highlight.getParameters(), highlightBuilder);
for (HighlightField highlightField : highlight.getFields()) {
String mappedName = mapFieldName(highlightField.getName(), type);
@@ -72,7 +69,7 @@ class HighlightQueryBuilder {
* the builder for highlight and highlight fields don't share code, so we have these two methods here that basically are almost copies
*/
private void addParameters(HighlightParameters parameters,
co.elastic.clients.elasticsearch.core.search.Highlight.Builder builder, @Nullable Class<?> type) {
co.elastic.clients.elasticsearch.core.search.Highlight.Builder builder) {
if (StringUtils.hasLength(parameters.getBoundaryChars())) {
builder.boundaryChars(parameters.getBoundaryChars());
@@ -106,10 +103,6 @@ class HighlightQueryBuilder {
builder.numberOfFragments(parameters.getNumberOfFragments());
}
if (parameters.getHighlightQuery() != null) {
builder.highlightQuery(requestConverter.getQuery(parameters.getHighlightQuery(), type));
}
if (StringUtils.hasLength(parameters.getOrder())) {
builder.order(highlighterOrder(parameters.getOrder()));
}
@@ -181,10 +174,6 @@ class HighlightQueryBuilder {
builder.numberOfFragments(parameters.getNumberOfFragments());
}
if (parameters.getHighlightQuery() != null) {
builder.highlightQuery(requestConverter.getQuery(parameters.getHighlightQuery(), type));
}
if (StringUtils.hasLength(parameters.getOrder())) {
builder.order(highlighterOrder(parameters.getOrder()));
}
@@ -26,8 +26,8 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
@@ -60,7 +60,7 @@ import org.springframework.util.Assert;
public class IndicesTemplate extends ChildTemplate<ElasticsearchTransport, ElasticsearchIndicesClient>
implements IndexOperations {
private static final Log LOGGER = LogFactory.getLog(IndicesTemplate.class);
private static final Logger LOGGER = LoggerFactory.getLogger(IndicesTemplate.class);
// we need a cluster client as well because ES has put some methods from the indices API into the cluster client
// (component templates)
@@ -19,6 +19,7 @@ import co.elastic.clients.json.JsonpMapper;
import jakarta.json.stream.JsonGenerator;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.logging.Log;
@@ -43,13 +44,17 @@ final class JsonUtils {
mapper.serialize(object, generator);
generator.close();
String json = "{}";
json = baos.toString(StandardCharsets.UTF_8);
try {
json = baos.toString("UTF-8");
} catch (UnsupportedEncodingException e) {
LOGGER.warn("could not read json", e);
}
return json;
}
@Nullable
public static String queryToJson(@Nullable co.elastic.clients.elasticsearch._types.query_dsl.Query query,
JsonpMapper mapper) {
public static String queryToJson(@Nullable co.elastic.clients.elasticsearch._types.query_dsl.Query query, JsonpMapper mapper) {
if (query == null) {
return null;
@@ -47,8 +47,8 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
private final Map<String, Aggregation> aggregations = new LinkedHashMap<>();
@Nullable private Suggester suggester;
@Nullable private FieldCollapse fieldCollapse;
private final List<SortOptions> sortOptions = new ArrayList<>();
private final Map<String, JsonData> searchExtensions = new LinkedHashMap<>();
private List<SortOptions> sortOptions = new ArrayList<>();
private Map<String, JsonData> searchExtensions = new LinkedHashMap<>();
@Nullable private org.springframework.data.elasticsearch.core.query.Query springDataQuery;
@Nullable private KnnQuery knnQuery;
@@ -0,0 +1,174 @@
/*
* Copyright 2022-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.LatLonGeoLocation;
import co.elastic.clients.elasticsearch._types.query_dsl.IdsQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchAllQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryStringQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.WildcardQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.WrapperQuery;
import co.elastic.clients.util.ObjectBuilder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.function.Function;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Utility class simplifying the creation of some more complex queries and type.
*
* @author Peter-Josef Meisch
* @since 4.4
* @deprecated since 5.1, use {@link Queries} instead.
*/
@Deprecated(forRemoval = true)
public final class QueryBuilders {
private QueryBuilders() {}
public static IdsQuery idsQuery(List<String> ids) {
Assert.notNull(ids, "ids must not be null");
return IdsQuery.of(i -> i.values(ids));
}
public static Query idsQueryAsQuery(List<String> ids) {
Assert.notNull(ids, "ids must not be null");
Function<Query.Builder, ObjectBuilder<Query>> builder = b -> b.ids(idsQuery(ids));
return builder.apply(new Query.Builder()).build();
}
public static MatchQuery matchQuery(String fieldName, String query, @Nullable Operator operator,
@Nullable Float boost) {
Assert.notNull(fieldName, "fieldName must not be null");
Assert.notNull(query, "query must not be null");
return MatchQuery.of(mb -> mb.field(fieldName).query(FieldValue.of(query)).operator(operator).boost(boost));
}
public static Query matchQueryAsQuery(String fieldName, String query, @Nullable Operator operator,
@Nullable Float boost) {
Function<Query.Builder, ObjectBuilder<Query>> builder = b -> b.match(matchQuery(fieldName, query, operator, boost));
return builder.apply(new Query.Builder()).build();
}
public static MatchAllQuery matchAllQuery() {
return MatchAllQuery.of(b -> b);
}
public static Query matchAllQueryAsQuery() {
Function<Query.Builder, ObjectBuilder<Query>> builder = b -> b.matchAll(matchAllQuery());
return builder.apply(new Query.Builder()).build();
}
public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Float boost) {
return queryStringQuery(fieldName, query, null, null, boost);
}
public static QueryStringQuery queryStringQuery(String fieldName, String query, Operator defaultOperator,
@Nullable Float boost) {
return queryStringQuery(fieldName, query, null, defaultOperator, boost);
}
public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Boolean analyzeWildcard,
@Nullable Float boost) {
return queryStringQuery(fieldName, query, analyzeWildcard, null, boost);
}
public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Boolean analyzeWildcard,
@Nullable Operator defaultOperator, @Nullable Float boost) {
Assert.notNull(fieldName, "fieldName must not be null");
Assert.notNull(query, "query must not be null");
return QueryStringQuery.of(qs -> qs.fields(fieldName).query(query).analyzeWildcard(analyzeWildcard)
.defaultOperator(defaultOperator).boost(boost));
}
public static TermQuery termQuery(String fieldName, String value) {
Assert.notNull(fieldName, "fieldName must not be null");
Assert.notNull(value, "value must not be null");
return TermQuery.of(t -> t.field(fieldName).value(FieldValue.of(value)));
}
public static Query termQueryAsQuery(String fieldName, String value) {
Function<Query.Builder, ObjectBuilder<Query>> builder = q -> q.term(termQuery(fieldName, value));
return builder.apply(new Query.Builder()).build();
}
public static WildcardQuery wildcardQuery(String field, String value) {
Assert.notNull(field, "field must not be null");
Assert.notNull(value, "value must not be null");
return WildcardQuery.of(w -> w.field(field).wildcard(value));
}
public static Query wildcardQueryAsQuery(String field, String value) {
Function<Query.Builder, ObjectBuilder<Query>> builder = q -> q.wildcard(wildcardQuery(field, value));
return builder.apply(new Query.Builder()).build();
}
public static Query wrapperQueryAsQuery(String query) {
Function<Query.Builder, ObjectBuilder<Query>> builder = q -> q.wrapper(wrapperQuery(query));
return builder.apply(new Query.Builder()).build();
}
public static WrapperQuery wrapperQuery(String query) {
Assert.notNull(query, "query must not be null");
String encodedValue = Base64.getEncoder().encodeToString(query.getBytes(StandardCharsets.UTF_8));
return WrapperQuery.of(wq -> wq.query(encodedValue));
}
public static LatLonGeoLocation latLon(GeoPoint geoPoint) {
Assert.notNull(geoPoint, "geoPoint must not be null");
return latLon(geoPoint.getLat(), geoPoint.getLon());
}
public static LatLonGeoLocation latLon(double lat, double lon) {
return LatLonGeoLocation.of(_0 -> _0.lat(lat).lon(lon));
}
}
@@ -128,13 +128,6 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
return Mono.fromFuture(transport.performRequestAsync(request, endpoint, transportOptions));
}
public Mono<BooleanResponse> exists(ExistsRequest request) {
Assert.notNull(request, "request must not be null");
return Mono.fromFuture(transport.performRequestAsync(request, ExistsRequest._ENDPOINT, transportOptions));
}
public <T, P> Mono<UpdateResponse<T>> update(UpdateRequest<T, P> request, Class<T> clazz) {
Assert.notNull(request, "request must not be null");
@@ -15,9 +15,6 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.TransportOptions;
import co.elastic.clients.transport.rest_client.RestClientOptions;
@@ -32,8 +29,7 @@ import org.springframework.util.Assert;
/**
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
* connection using the {@link ReactiveElasticsearchClient}. This class exposes different parts of the setup as Spring
* beans. Deriving * classes must provide the {@link ClientConfiguration} to use.
* connection using the {@link ReactiveElasticsearchClient}.
*
* @author Peter-Josef Meisch
* @since 4.4
@@ -45,7 +41,7 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
*
* @return configuration, must not be {@literal null}
*/
@Bean(name = "elasticsearchClientConfiguration")
@Bean(name="elasticsearchClientConfiguration")
public abstract ClientConfiguration clientConfiguration();
/**
@@ -62,35 +58,18 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
return ElasticsearchClients.getRestClient(clientConfiguration);
}
/**
* Provides the Elasticsearch transport to be used. The default implementation uses the {@link RestClient} bean and
* the {@link JsonpMapper} bean provided in this class.
*
* @return the {@link ElasticsearchTransport}
* @since 5.2
*/
@Bean
public ElasticsearchTransport elasticsearchTransport(RestClient restClient, JsonpMapper jsonpMapper) {
Assert.notNull(restClient, "restClient must not be null");
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
return ElasticsearchClients.getElasticsearchTransport(restClient, ElasticsearchClients.REACTIVE_CLIENT,
transportOptions(), jsonpMapper);
}
/**
* Provides the {@link ReactiveElasticsearchClient} instance used.
*
* @param transport the ElasticsearchTransport to use
* @param restClient the low level RestClient to use
* @return ReactiveElasticsearchClient instance.
*/
@Bean
public ReactiveElasticsearchClient reactiveElasticsearchClient(ElasticsearchTransport transport) {
public ReactiveElasticsearchClient reactiveElasticsearchClient(RestClient restClient) {
Assert.notNull(transport, "transport must not be null");
Assert.notNull(restClient, "restClient must not be null");
return ElasticsearchClients.createReactive(transport);
return ElasticsearchClients.createReactive(restClient, transportOptions());
}
/**
@@ -109,18 +88,6 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
return template;
}
/**
* Provides the JsonpMapper that is used in the {@link #elasticsearchTransport(RestClient, JsonpMapper)} method and
* exposes it as a bean.
*
* @return the {@link JsonpMapper} to use
* @since 5.2
*/
@Bean
public JsonpMapper jsonpMapper() {
return new JacksonJsonpMapper();
}
/**
* @return the options that should be added to every request. Must not be {@literal null}
*/
@@ -15,16 +15,16 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import static co.elastic.clients.util.ApiTypeHelper.*;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
import static co.elastic.clients.util.ApiTypeHelper.DANGEROUS_disableRequiredPropertiesCheck;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.result;
import co.elastic.clients.elasticsearch._types.Result;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
import co.elastic.clients.elasticsearch.core.get.GetResult;
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.transport.Version;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
@@ -38,33 +38,22 @@ import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.BulkFailureException;
import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.client.UnsupportedBackendOperation;
import org.springframework.data.elasticsearch.core.AbstractReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.AggregationContainer;
import org.springframework.data.elasticsearch.core.IndexedObjectInformation;
import org.springframework.data.elasticsearch.core.MultiGetItem;
import org.springframework.data.elasticsearch.core.ReactiveIndexOperations;
import org.springframework.data.elasticsearch.core.*;
import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.document.SearchDocument;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
@@ -79,13 +68,11 @@ import org.springframework.util.StringUtils;
* Elasticsearch client.
*
* @author Peter-Josef Meisch
* @author Illia Ulianov
* @author Junghoon Ban
* @since 4.4
*/
public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearchTemplate {
private static final Log LOGGER = LogFactory.getLog(ReactiveElasticsearchTemplate.class);
private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveElasticsearchTemplate.class);
private final ReactiveElasticsearchClient client;
private final RequestConverter requestConverter;
@@ -113,7 +100,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
getRefreshPolicy());
return Mono.just(entity) //
.zipWith(//
Mono.from(execute(client -> client.index(indexRequest))) //
Mono.from(execute((ClientCallback<Publisher<IndexResponse>>) client -> client.index(indexRequest))) //
.map(indexResponse -> new IndexResponseMetaData(indexResponse.id(), //
indexResponse.index(), //
indexResponse.seqNo(), //
@@ -143,16 +130,13 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
.flatMap(indexAndResponse -> {
T savedEntity = entities.entityAt(indexAndResponse.getT1());
BulkResponseItem response = indexAndResponse.getT2();
var updatedEntity = entityOperations.updateIndexedObject(
savedEntity, new IndexedObjectInformation( //
response.id(), //
response.index(), //
response.seqNo(), //
response.primaryTerm(), //
response.version()),
converter,
routingResolver);
return maybeCallbackAfterSave(updatedEntity, index);
updateIndexedObject(savedEntity, new IndexedObjectInformation( //
response.id(), //
response.index(), //
response.seqNo(), //
response.primaryTerm(), //
response.version()));
return maybeCallbackAfterSave(savedEntity, index);
});
});
}
@@ -163,11 +147,11 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
Assert.notNull(id, "id must not be null");
Assert.notNull(index, "index must not be null");
ExistsRequest existsRequest = requestConverter.documentExistsRequest(id, routingResolver.getRouting(), index);
GetRequest getRequest = requestConverter.documentGetRequest(id, routingResolver.getRouting(), index, true);
return Mono.from(execute(
((ClientCallback<Publisher<BooleanResponse>>) client -> client.exists(existsRequest))))
.map(BooleanResponse::value) //
((ClientCallback<Publisher<GetResponse<EntityAsMap>>>) client -> client.get(getRequest, EntityAsMap.class))))
.map(GetResult::found) //
.onErrorReturn(NoSuchIndexException.class, false);
}
@@ -178,16 +162,9 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
entityType, index, getRefreshPolicy());
return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse);
}
@Override
public Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
entityType, index, getRefreshPolicy());
return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse);
return Mono
.from(execute((ClientCallback<Publisher<DeleteByQueryResponse>>) client -> client.deleteByQuery(request)))
.map(responseConverter::byQueryResponse);
}
@Override
@@ -197,10 +174,10 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
Assert.notNull(entityType, "entityType must not be null");
Assert.notNull(index, "index must not be null");
GetRequest getRequest = requestConverter.documentGetRequest(id, routingResolver.getRouting(), index);
GetRequest getRequest = requestConverter.documentGetRequest(id, routingResolver.getRouting(), index, false);
Mono<GetResponse<EntityAsMap>> getResponse = Mono
.from(execute(client -> client.get(getRequest, EntityAsMap.class)));
Mono<GetResponse<EntityAsMap>> getResponse = Mono.from(execute(
(ClientCallback<Publisher<GetResponse<EntityAsMap>>>) client -> client.get(getRequest, EntityAsMap.class)));
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(converter, entityType, index);
return getResponse.flatMap(response -> callback.toEntity(DocumentAdapters.from(response)));
@@ -215,7 +192,9 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
true);
return Mono.from(execute( //
client -> client.reindex(reindexRequestES))).map(responseConverter::reindexResponse);
(ClientCallback<Publisher<co.elastic.clients.elasticsearch.core.ReindexResponse>>) client -> client
.reindex(reindexRequestES)))
.map(responseConverter::reindexResponse);
}
@Override
@@ -227,7 +206,8 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
false);
return Mono.from(execute( //
client -> client.reindex(reindexRequestES)))
(ClientCallback<Publisher<co.elastic.clients.elasticsearch.core.ReindexResponse>>) client -> client
.reindex(reindexRequestES)))
.flatMap(response -> (response.task() == null)
? Mono.error(
new UnsupportedBackendOperation("ElasticsearchClient did not return a task id on submit request"))
@@ -243,10 +223,13 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
UpdateRequest<Document, ?> request = requestConverter.documentUpdateRequest(updateQuery, index, getRefreshPolicy(),
routingResolver.getRouting());
return Mono.from(execute(client -> client.update(request, Document.class))).flatMap(response -> {
UpdateResponse.Result result = result(response.result());
return result == null ? Mono.empty() : Mono.just(UpdateResponse.of(result));
});
return Mono.from(execute(
(ClientCallback<Publisher<co.elastic.clients.elasticsearch.core.UpdateResponse<Document>>>) client -> client
.update(request, Document.class)))
.flatMap(response -> {
UpdateResponse.Result result = result(response.result());
return result == null ? Mono.empty() : Mono.just(UpdateResponse.of(result));
});
}
@Override
@@ -277,12 +260,12 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
private Mono<BulkResponse> checkForBulkOperationFailure(BulkResponse bulkResponse) {
if (bulkResponse.errors()) {
Map<String, BulkFailureException.FailureDetails> failedDocuments = new HashMap<>();
Map<String, String> failedDocuments = new HashMap<>();
for (BulkResponseItem item : bulkResponse.items()) {
if (item.error() != null) {
failedDocuments.put(item.id(), new BulkFailureException.FailureDetails(item.status(), item.error().reason()));
failedDocuments.put(item.id(), item.error().reason());
}
}
BulkFailureException exception = new BulkFailureException(
@@ -309,7 +292,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
private Mono<String> doDelete(DeleteRequest request) {
return Mono.from(execute(client -> client.delete(request))) //
return Mono.from(execute((ClientCallback<Publisher<DeleteResponse>>) client -> client.delete(request))) //
.flatMap(deleteResponse -> {
if (deleteResponse.result() == Result.NotFound) {
return Mono.empty();
@@ -328,7 +311,8 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(converter, clazz, index);
Publisher<MgetResponse<EntityAsMap>> response = execute(client -> client.mget(request, EntityAsMap.class));
Publisher<MgetResponse<EntityAsMap>> response = execute(
(ClientCallback<Publisher<MgetResponse<EntityAsMap>>>) client -> client.mget(request, EntityAsMap.class));
return Mono.from(response)//
.flatMapMany(it -> Flux.fromIterable(DocumentAdapters.from(it))) //
@@ -380,14 +364,14 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
BiFunction<PitSearchAfter, Throwable, Publisher<?>> asyncError = (psa, ex) -> {
if (LOGGER.isErrorEnabled()) {
LOGGER.error("Error during pit/search_after", ex);
LOGGER.error(String.format("Error during pit/search_after"), ex);
}
return cleanupPit(psa);
};
Function<PitSearchAfter, Publisher<?>> asyncCancel = psa -> {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("pit/search_after was cancelled");
LOGGER.warn(String.format("pit/search_after was cancelled"));
}
return cleanupPit(psa);
};
@@ -399,8 +383,8 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
SearchRequest firstSearchRequest = requestConverter.searchRequest(baseQuery, routingResolver.getRouting(),
clazz, index, false, true);
return Mono.from(execute(client -> client.search(firstSearchRequest, EntityAsMap.class)))
.expand(entityAsMapSearchResponse -> {
return Mono.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client -> client
.search(firstSearchRequest, EntityAsMap.class))).expand(entityAsMapSearchResponse -> {
var hits = entityAsMapSearchResponse.hits().hits();
if (CollectionUtils.isEmpty(hits)) {
@@ -412,7 +396,8 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
baseQuery.setSearchAfter(sortOptions);
SearchRequest followSearchRequest = requestConverter.searchRequest(baseQuery,
routingResolver.getRouting(), clazz, index, false, true);
return Mono.from(execute(client -> client.search(followSearchRequest, EntityAsMap.class)));
return Mono.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client -> client
.search(followSearchRequest, EntityAsMap.class)));
});
};
@@ -469,7 +454,9 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
SearchRequest searchRequest = requestConverter.searchRequest(query, routingResolver.getRouting(), entityType, index,
true);
return Mono.from(execute(client -> client.search(searchRequest, EntityAsMap.class)))
return Mono
.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client -> client.search(searchRequest,
EntityAsMap.class)))
.map(searchResponse -> searchResponse.hits().total() != null ? searchResponse.hits().total().value() : 0L);
}
@@ -478,7 +465,9 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
SearchRequest searchRequest = requestConverter.searchRequest(query, routingResolver.getRouting(), clazz, index,
false, false);
return Mono.from(execute(client -> client.search(searchRequest, EntityAsMap.class))) //
return Mono
.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client -> client.search(searchRequest,
EntityAsMap.class))) //
.flatMapIterable(entityAsMapSearchResponse -> entityAsMapSearchResponse.hits().hits()) //
.map(entityAsMapHit -> DocumentAdapters.from(entityAsMapHit, jsonpMapper));
}
@@ -487,7 +476,9 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
var request = requestConverter.searchTemplate(query, routingResolver.getRouting(), index);
return Mono.from(execute(client -> client.searchTemplate(request, EntityAsMap.class))) //
return Mono
.from(execute((ClientCallback<Publisher<SearchTemplateResponse<EntityAsMap>>>) client -> client
.searchTemplate(request, EntityAsMap.class))) //
.flatMapIterable(entityAsMapSearchResponse -> entityAsMapSearchResponse.hits().hits()) //
.map(entityAsMapHit -> DocumentAdapters.from(entityAsMapHit, jsonpMapper));
}
@@ -506,7 +497,9 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
SearchDocumentResponse.EntityCreator<T> entityCreator = searchDocument -> callback.toEntity(searchDocument)
.toFuture();
return Mono.from(execute(client -> client.search(searchRequest, EntityAsMap.class)))
return Mono
.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client -> client.search(searchRequest,
EntityAsMap.class)))
.map(searchResponse -> SearchDocumentResponseBuilder.from(searchResponse, entityCreator, jsonpMapper));
}
@@ -527,7 +520,9 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
Assert.notNull(ignoreUnavailable, "ignoreUnavailable must not be null");
var request = requestConverter.searchOpenPointInTimeRequest(index, keepAlive, ignoreUnavailable);
return Mono.from(execute(client -> client.openPointInTime(request))).map(OpenPointInTimeResponse::id);
return Mono
.from(execute((ClientCallback<Publisher<OpenPointInTimeResponse>>) client -> client.openPointInTime(request)))
.map(OpenPointInTimeResponse::id);
}
@Override
@@ -536,7 +531,9 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
Assert.notNull(pit, "pit must not be null");
ClosePointInTimeRequest request = requestConverter.searchClosePointInTime(pit);
return Mono.from(execute(client -> client.closePointInTime(request))).map(ClosePointInTimeResponse::succeeded);
return Mono
.from(execute((ClientCallback<Publisher<ClosePointInTimeResponse>>) client -> client.closePointInTime(request)))
.map(ClosePointInTimeResponse::succeeded);
}
// endregion
@@ -548,7 +545,8 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
Assert.notNull(script, "script must not be null");
var request = requestConverter.scriptPut(script);
return Mono.from(execute(client -> client.putScript(request))).map(PutScriptResponse::acknowledged);
return Mono.from(execute((ClientCallback<Publisher<PutScriptResponse>>) client -> client.putScript(request)))
.map(PutScriptResponse::acknowledged);
}
@Override
@@ -557,7 +555,8 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
Assert.notNull(name, "name must not be null");
var request = requestConverter.scriptGet(name);
return Mono.from(execute(client -> client.getScript(request))).mapNotNull(responseConverter::scriptResponse);
return Mono.from(execute((ClientCallback<Publisher<GetScriptResponse>>) client -> client.getScript(request)))
.mapNotNull(responseConverter::scriptResponse);
}
@Override
@@ -565,7 +564,8 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
Assert.notNull(name, "name must not be null");
var request = requestConverter.scriptDelete(name);
return Mono.from(execute(client -> client.deleteScript(request))).map(DeleteScriptResponse::acknowledged);
return Mono.from(execute((ClientCallback<Publisher<DeleteScriptResponse>>) client -> client.deleteScript(request)))
.map(DeleteScriptResponse::acknowledged);
}
// endregion
@@ -588,6 +588,22 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
})).map(infoResponse -> infoResponse.version().number());
}
@Override
@Deprecated
public <T> Publisher<T> execute(ReactiveElasticsearchOperations.ClientCallback<Publisher<T>> callback) {
throw new UnsupportedBackendOperation("direct execution on the WebClient is not supported for this class");
}
@Override
public <T> Publisher<T> executeWithIndicesClient(IndicesClientCallback<Publisher<T>> callback) {
throw new UnsupportedOperationException("not implemented");
}
@Override
public <T> Publisher<T> executeWithClusterClient(ClusterClientCallback<Publisher<T>> callback) {
throw new UnsupportedOperationException("not implemented");
}
@Override
public ReactiveIndexOperations indexOps(IndexCoordinates index) {
return new ReactiveIndicesTemplate(client.indices(), getReactiveClusterTemplate(), converter, index);
@@ -603,9 +619,9 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return getReactiveClusterTemplate();
}
/**
* @since 5.1
*/
/**
* @since 5.1
*/
private ReactiveClusterTemplate getReactiveClusterTemplate() {
return new ReactiveClusterTemplate(client.cluster(), converter);
}
@@ -617,16 +633,11 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
@Override
public Query idsQuery(List<String> ids) {
return NativeQuery.builder().withQuery(qb -> qb.ids(iq -> iq.values(ids))).build();
}
@Override
public BaseQueryBuilder queryBuilderWithIds(List<String> ids) {
return NativeQuery.builder().withIds(ids);
return NativeQuery.builder().withQuery(Queries.idsQueryAsQuery(ids)).build();
}
/**
* Callback interface to be used with {@link #execute(ReactiveElasticsearchTemplate.ClientCallback<>)} for operating
* Callback interface to be used with {@link #execute(ReactiveElasticsearchOperations.ClientCallback)} for operating
* directly on {@link ReactiveElasticsearchClient}.
*
* @param <T>
@@ -656,7 +667,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
*/
private RuntimeException translateException(Throwable throwable) {
RuntimeException runtimeException = throwable instanceof RuntimeException ex ? ex
RuntimeException runtimeException = throwable instanceof RuntimeException ? (RuntimeException) throwable
: new RuntimeException(throwable.getMessage(), throwable);
RuntimeException potentiallyTranslatedException = exceptionTranslator
.translateExceptionIfPossible(runtimeException);
@@ -19,9 +19,7 @@ import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
import static org.springframework.util.CollectionUtils.*;
import co.elastic.clients.elasticsearch._types.Conflicts;
import co.elastic.clients.elasticsearch._types.ExpandWildcard;
import co.elastic.clients.elasticsearch._types.InlineScript;
import co.elastic.clients.elasticsearch._types.NestedSortValue;
import co.elastic.clients.elasticsearch._types.OpType;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
@@ -44,7 +42,6 @@ import co.elastic.clients.elasticsearch.core.bulk.IndexOperation;
import co.elastic.clients.elasticsearch.core.bulk.UpdateOperation;
import co.elastic.clients.elasticsearch.core.mget.MultiGetOperation;
import co.elastic.clients.elasticsearch.core.msearch.MultisearchBody;
import co.elastic.clients.elasticsearch.core.msearch.MultisearchHeader;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.Rescore;
import co.elastic.clients.elasticsearch.core.search.SourceConfig;
@@ -55,7 +52,6 @@ import co.elastic.clients.elasticsearch.indices.update_aliases.Action;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.JsonpDeserializer;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.util.ObjectBuilder;
import jakarta.json.stream.JsonParser;
import java.io.ByteArrayInputStream;
@@ -68,16 +64,13 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.ScriptType;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.*;
@@ -108,13 +101,10 @@ import org.springframework.util.StringUtils;
* @author Sascha Woo
* @author cdalxndr
* @author scoobyzhang
* @author Haibo Liu
* @since 4.4
*/
@SuppressWarnings("ClassCanBeRecord")
class RequestConverter extends AbstractQueryProcessor {
private static final Log LOGGER = LogFactory.getLog(RequestConverter.class);
class RequestConverter {
// the default max result window size of Elasticsearch
public static final Integer INDEX_MAX_RESULT_WINDOW = 10_000;
@@ -157,6 +147,7 @@ class RequestConverter extends AbstractQueryProcessor {
aliasActions.getActions().forEach(aliasAction -> {
if (aliasAction instanceof AliasAction.Add add) {
var parameters = add.getParameters();
// noinspection DuplicatedCode
String[] parametersAliases = parameters.getAliases();
if (parametersAliases != null) {
for (String aliasName : parametersAliases) {
@@ -172,6 +163,7 @@ class RequestConverter extends AbstractQueryProcessor {
private Alias.Builder buildAlias(AliasActionParameters parameters, Alias.Builder aliasBuilder) {
// noinspection DuplicatedCode
if (parameters.getRouting() != null) {
aliasBuilder.routing(parameters.getRouting());
}
@@ -271,7 +263,36 @@ class RequestConverter extends AbstractQueryProcessor {
List<Action> actions = new ArrayList<>();
aliasActions.getActions().forEach(aliasAction -> {
var actionBuilder = getBuilder(aliasAction);
Action.Builder actionBuilder = new Action.Builder();
if (aliasAction instanceof AliasAction.Add add) {
AliasActionParameters parameters = add.getParameters();
actionBuilder.add(addActionBuilder -> {
addActionBuilder //
.indices(Arrays.asList(parameters.getIndices())) //
.isHidden(parameters.getHidden()) //
.isWriteIndex(parameters.getWriteIndex()) //
.routing(parameters.getRouting()) //
.indexRouting(parameters.getIndexRouting()) //
.searchRouting(parameters.getSearchRouting()); //
if (parameters.getAliases() != null) {
addActionBuilder.aliases(Arrays.asList(parameters.getAliases()));
}
Query filterQuery = parameters.getFilterQuery();
if (filterQuery != null) {
elasticsearchConverter.updateQuery(filterQuery, parameters.getFilterQueryClass());
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = getQuery(filterQuery, null);
if (esQuery != null) {
addActionBuilder.filter(esQuery);
}
}
return addActionBuilder;
});
}
if (aliasAction instanceof AliasAction.Remove remove) {
AliasActionParameters parameters = remove.getParameters();
@@ -300,49 +321,17 @@ class RequestConverter extends AbstractQueryProcessor {
return updateAliasRequestBuilder.build();
}
@NotNull
private Action.Builder getBuilder(AliasAction aliasAction) {
Action.Builder actionBuilder = new Action.Builder();
if (aliasAction instanceof AliasAction.Add add) {
AliasActionParameters parameters = add.getParameters();
actionBuilder.add(addActionBuilder -> {
addActionBuilder //
.indices(Arrays.asList(parameters.getIndices())) //
.isHidden(parameters.getHidden()) //
.isWriteIndex(parameters.getWriteIndex()) //
.routing(parameters.getRouting()) //
.indexRouting(parameters.getIndexRouting()) //
.searchRouting(parameters.getSearchRouting()); //
if (parameters.getAliases() != null) {
addActionBuilder.aliases(Arrays.asList(parameters.getAliases()));
}
Query filterQuery = parameters.getFilterQuery();
if (filterQuery != null) {
elasticsearchConverter.updateQuery(filterQuery, parameters.getFilterQueryClass());
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = getQuery(filterQuery, null);
if (esQuery != null) {
addActionBuilder.filter(esQuery);
}
}
return addActionBuilder;
});
}
return actionBuilder;
}
public PutMappingRequest indicesPutMappingRequest(IndexCoordinates indexCoordinates, Document mapping) {
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
Assert.notNull(mapping, "mapping must not be null");
return new PutMappingRequest.Builder()
.withJson(new StringReader(mapping.toJson()))
.index(Arrays.asList(indexCoordinates.getIndexNames()))
PutMappingRequest request = new PutMappingRequest.Builder() //
.withJson(new StringReader(mapping.toJson())) //
.index(Arrays.asList(indexCoordinates.getIndexNames())) //
.build();
return request;
}
public GetMappingRequest indicesGetMappingRequest(IndexCoordinates indexCoordinates) {
@@ -398,7 +387,10 @@ class RequestConverter extends AbstractQueryProcessor {
.order(putTemplateRequest.getOrder());
if (putTemplateRequest.getSettings() != null) {
Map<String, JsonData> settings = getTemplateParams(putTemplateRequest.getSettings().entrySet());
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
Map<String, JsonData> settings = putTemplateRequest.getSettings().entrySet().stream()
.collect(Collectors.toMap(keyMapper, valueMapper));
builder.settings(settings);
}
@@ -414,11 +406,15 @@ class RequestConverter extends AbstractQueryProcessor {
if (aliasActions != null) {
aliasActions.getActions().forEach(aliasAction -> {
AliasActionParameters parameters = aliasAction.getParameters();
// noinspection DuplicatedCode
String[] parametersAliases = parameters.getAliases();
if (parametersAliases != null) {
for (String aliasName : parametersAliases) {
builder.aliases(aliasName, aliasBuilder -> buildAlias(parameters, aliasBuilder));
builder.aliases(aliasName, aliasBuilder -> {
return buildAlias(parameters, aliasBuilder);
});
}
}
});
@@ -447,6 +443,7 @@ class RequestConverter extends AbstractQueryProcessor {
aliasActions.getActions().forEach(aliasAction -> {
if (aliasAction instanceof AliasAction.Add add) {
var parameters = add.getParameters();
// noinspection DuplicatedCode
String[] parametersAliases = parameters.getAliases();
if (parametersAliases != null) {
for (String aliasName : parametersAliases) {
@@ -635,7 +632,7 @@ class RequestConverter extends AbstractQueryProcessor {
Object queryObject = query.getObject();
if (queryObject != null) {
builder
builder
.id(StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject))
.document(elasticsearchConverter.mapObject(queryObject));
} else if (query.getSource() != null) {
@@ -778,27 +775,25 @@ class RequestConverter extends AbstractQueryProcessor {
return builder.build();
}
public GetRequest documentGetRequest(String id, @Nullable String routing, IndexCoordinates indexCoordinates) {
public GetRequest documentGetRequest(String id, @Nullable String routing, IndexCoordinates indexCoordinates,
boolean forExistsRequest) {
Assert.notNull(id, "id must not be null");
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
return GetRequest.of(grb -> grb //
.index(indexCoordinates.getIndexName()) //
.id(id) //
.routing(routing));
}
return GetRequest.of(grb -> {
grb //
.index(indexCoordinates.getIndexName()) //
.id(id) //
.routing(routing);
public co.elastic.clients.elasticsearch.core.ExistsRequest documentExistsRequest(String id, @Nullable String routing,
IndexCoordinates indexCoordinates) {
if (forExistsRequest) {
grb.source(scp -> scp.fetch(false));
}
Assert.notNull(id, "id must not be null");
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
return grb;
});
return co.elastic.clients.elasticsearch.core.ExistsRequest.of(erb -> erb
.index(indexCoordinates.getIndexName())
.id(id)
.routing(routing));
}
public <T> MgetRequest documentMgetRequest(Query query, Class<T> clazz, IndexCoordinates index) {
@@ -855,10 +850,11 @@ class RequestConverter extends AbstractQueryProcessor {
StringBuilder sb = new StringBuilder(remote.getScheme());
sb.append("://");
sb.append(remote.getHost());
sb.append(':');
sb.append(":");
sb.append(remote.getPort());
if (remote.getPathPrefix() != null) {
sb.append("");
sb.append(remote.getPathPrefix());
}
@@ -878,7 +874,7 @@ class RequestConverter extends AbstractQueryProcessor {
}
SourceFilter sourceFilter = source.getSourceFilter();
if (sourceFilter != null && sourceFilter.getIncludes() != null) {
if (sourceFilter != null) {
s.sourceFields(Arrays.asList(sourceFilter.getIncludes()));
}
return s;
@@ -964,78 +960,6 @@ class RequestConverter extends AbstractQueryProcessor {
});
}
public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nullable String routing, Class<?> clazz,
IndexCoordinates index, @Nullable RefreshPolicy refreshPolicy) {
Assert.notNull(query, "query must not be null");
Assert.notNull(index, "index must not be null");
return DeleteByQueryRequest.of(dqb -> {
dqb.index(Arrays.asList(index.getIndexNames())) //
.query(getQuery(query.getQuery(), clazz))//
.refresh(deleteByQueryRefresh(refreshPolicy))
.requestsPerSecond(query.getRequestsPerSecond())
.maxDocs(query.getMaxDocs())
.scroll(time(query.getScroll()))
.scrollSize(query.getScrollSize());
if (query.getRouting() != null) {
dqb.routing(query.getRouting());
} else if (StringUtils.hasText(routing)) {
dqb.routing(routing);
}
if (query.getQ() != null) {
dqb.q(query.getQ())
.analyzer(query.getAnalyzer())
.analyzeWildcard(query.getAnalyzeWildcard())
.defaultOperator(operator(query.getDefaultOperator()))
.df(query.getDf())
.lenient(query.getLenient());
}
if (query.getExpandWildcards() != null && !query.getExpandWildcards().isEmpty()) {
dqb.expandWildcards(expandWildcards(query.getExpandWildcards()));
}
if (query.getStats() != null && !query.getStats().isEmpty()) {
dqb.stats(query.getStats());
}
if (query.getSlices() != null) {
dqb.slices(sb -> sb.value(query.getSlices()));
}
if (query.getSort() != null) {
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntity(clazz);
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
if (!sortOptions.isEmpty()) {
dqb.sort(
sortOptions.stream()
.map(sortOption -> {
String order = "asc";
var sortField = sortOption.field();
if (sortField.order() != null) {
order = sortField.order().jsonValue();
}
return sortField.field() + ":" + order;
})
.collect(Collectors.toList()));
}
}
dqb.allowNoIndices(query.getAllowNoIndices())
.conflicts(conflicts(query.getConflicts()))
.ignoreUnavailable(query.getIgnoreUnavailable())
.preference(query.getPreference())
.requestCache(query.getRequestCache())
.searchType(searchType(query.getSearchType()))
.searchTimeout(time(query.getSearchTimeout()))
.terminateAfter(query.getTerminateAfter())
.timeout(time(query.getTimeout()))
.version(query.getVersion());
return dqb;
});
}
public UpdateRequest<Document, ?> documentUpdateRequest(UpdateQuery query, IndexCoordinates index,
@Nullable RefreshPolicy refreshPolicy, @Nullable String routing) {
@@ -1104,8 +1028,8 @@ class RequestConverter extends AbstractQueryProcessor {
int val;
try {
val = Integer.parseInt(waitForActiveShards);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("cannot parse ActiveShardCount[" + waitForActiveShards + ']', e);
} catch (NumberFormatException var3) {
throw new IllegalArgumentException("cannot parse ActiveShardCount[" + waitForActiveShards + "]", var3);
}
uqb.waitForActiveShards(wfa -> wfa.count(val));
}
@@ -1190,6 +1114,7 @@ class RequestConverter extends AbstractQueryProcessor {
IndexCoordinates indexCoordinates, boolean forCount, boolean forBatchedSearch,
@Nullable Long scrollTimeInMillis) {
String[] indexNames = indexCoordinates.getIndexNames();
Assert.notNull(query, "query must not be null");
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
@@ -1210,40 +1135,11 @@ class RequestConverter extends AbstractQueryProcessor {
builder.routing(routing);
}
addPostFilter(query, builder);
addFilter(query, builder);
return builder.build();
}
public MsearchTemplateRequest searchMsearchTemplateRequest(
List<ElasticsearchTemplate.MultiSearchTemplateQueryParameter> multiSearchTemplateQueryParameters,
@Nullable String routing) {
// basically the same stuff as in template search
return MsearchTemplateRequest.of(mtrb -> {
multiSearchTemplateQueryParameters.forEach(param -> {
var query = param.query();
mtrb.searchTemplates(stb -> stb
.header(msearchHeaderBuilder(query, param.index(), routing))
.body(bb -> {
bb //
.explain(query.getExplain()) //
.id(query.getId()) //
.source(query.getSource()) //
;
if (!CollectionUtils.isEmpty(query.getParams())) {
Map<String, JsonData> params = getTemplateParams(query.getParams().entrySet());
bb.params(params);
}
return bb;
}));
});
return mtrb;
});
}
public MsearchRequest searchMsearchRequest(
List<ElasticsearchTemplate.MultiSearchQueryParameter> multiSearchQueryParameters, @Nullable String routing) {
@@ -1255,7 +1151,25 @@ class RequestConverter extends AbstractQueryProcessor {
var query = param.query();
mrb.searches(sb -> sb //
.header(msearchHeaderBuilder(query, param.index(), routing)) //
.header(h -> {
h //
.index(Arrays.asList(param.index().getIndexNames())) //
.searchType(searchType(query.getSearchType())) //
.requestCache(query.getRequestCache()) //
;
if (StringUtils.hasText(query.getRoute())) {
h.routing(query.getRoute());
} else if (StringUtils.hasText(routing)) {
h.routing(routing);
}
if (query.getPreference() != null) {
h.preference(query.getPreference());
}
return h;
}) //
.body(bb -> {
bb //
.query(getQuery(query, param.clazz()))//
@@ -1273,10 +1187,9 @@ class RequestConverter extends AbstractQueryProcessor {
}
if (!isEmpty(query.getFields())) {
bb.fields(fb -> {
query.getFields().forEach(fb::field);
return fb;
});
var fieldAndFormats = query.getFields().stream()
.map(field -> FieldAndFormat.of(b -> b.field(field))).toList();
bb.fields(fieldAndFormats);
}
if (!isEmpty(query.getStoredFields())) {
@@ -1315,23 +1228,14 @@ class RequestConverter extends AbstractQueryProcessor {
Map<String, RuntimeField> runtimeMappings = new HashMap<>();
query.getRuntimeFields().forEach(runtimeField -> {
RuntimeField esRuntimeField = RuntimeField.of(rt -> {
RuntimeField.Builder rfb = rt
RuntimeField.Builder builder = rt
.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType()));
String script = runtimeField.getScript();
if (script != null) {
rfb
.script(s -> s
.inline(is -> {
is.source(script);
if (runtimeField.getParams() != null) {
is.params(TypeUtils.paramsMap(runtimeField.getParams()));
}
return is;
}));
builder = builder.script(s -> s.inline(is -> is.source(script)));
}
return rfb;
return builder;
});
runtimeMappings.put(runtimeField.getName(), esRuntimeField);
});
@@ -1340,15 +1244,15 @@ class RequestConverter extends AbstractQueryProcessor {
if (!isEmpty(query.getIndicesBoost())) {
bb.indicesBoost(query.getIndicesBoost().stream()
.map(indexBoost -> Map.of(indexBoost.getIndexName(), (double) indexBoost.getBoost()))
.map(indexBoost -> Map.of(indexBoost.getIndexName(), Double.valueOf(indexBoost.getBoost())))
.collect(Collectors.toList()));
}
query.getScriptedFields().forEach(scriptedField -> bb.scriptFields(scriptedField.getFieldName(),
sf -> sf.script(getScript(scriptedField.getScriptData()))));
if (query instanceof NativeQuery nativeQuery) {
prepareNativeSearch(nativeQuery, bb);
if (query instanceof NativeQuery) {
prepareNativeSearch((NativeQuery) query, bb);
}
return bb;
} //
@@ -1361,35 +1265,6 @@ class RequestConverter extends AbstractQueryProcessor {
});
}
/**
* {@link MsearchRequest} and {@link MsearchTemplateRequest} share the same {@link MultisearchHeader}
*/
private Function<MultisearchHeader.Builder, ObjectBuilder<MultisearchHeader>> msearchHeaderBuilder(Query query,
IndexCoordinates index, @Nullable String routing) {
return h -> {
var searchType = (query instanceof NativeQuery nativeQuery && nativeQuery.getKnnQuery() != null) ? null
: searchType(query.getSearchType());
h //
.index(Arrays.asList(index.getIndexNames())) //
.searchType(searchType) //
.requestCache(query.getRequestCache()) //
;
if (StringUtils.hasText(query.getRoute())) {
h.routing(query.getRoute());
} else if (StringUtils.hasText(routing)) {
h.routing(routing);
}
if (query.getPreference() != null) {
h.preference(query.getPreference());
}
return h;
};
}
private <T> void prepareSearchRequest(Query query, @Nullable String routing, @Nullable Class<T> clazz,
IndexCoordinates indexCoordinates, SearchRequest.Builder builder, boolean forCount, boolean forBatchedSearch) {
@@ -1399,15 +1274,12 @@ class RequestConverter extends AbstractQueryProcessor {
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntity(clazz);
var searchType = (query instanceof NativeQuery nativeQuery && nativeQuery.getKnnQuery() != null) ? null
: searchType(query.getSearchType());
builder //
.version(true) //
.trackScores(query.getTrackScores()) //
.allowNoIndices(query.getAllowNoIndices()) //
.source(getSourceConfig(query)) //
.searchType(searchType) //
.searchType(searchType(query.getSearchType())) //
.timeout(timeStringMs(query.getTimeout())) //
.requestCache(query.getRequestCache()) //
;
@@ -1449,7 +1321,8 @@ class RequestConverter extends AbstractQueryProcessor {
}
if (!isEmpty(query.getFields())) {
var fieldAndFormats = query.getFields().stream().map(field -> FieldAndFormat.of(b -> b.field(field))).toList();
var fieldAndFormats = query.getFields().stream()
.map(field -> FieldAndFormat.of(b -> b.field(field))).toList();
builder.fields(fieldAndFormats);
}
@@ -1458,7 +1331,7 @@ class RequestConverter extends AbstractQueryProcessor {
}
if (query.getIndicesOptions() != null) {
addIndicesOptions(builder, query.getIndicesOptions());
// new Elasticsearch client does not support the old Indices options, need to be adapted
}
if (query.isLimiting()) {
@@ -1474,8 +1347,8 @@ class RequestConverter extends AbstractQueryProcessor {
query.getScriptedFields().forEach(scriptedField -> builder.scriptFields(scriptedField.getFieldName(),
sf -> sf.script(getScript(scriptedField.getScriptData()))));
if (query instanceof NativeQuery nativeQuery) {
prepareNativeSearch(nativeQuery, builder);
if (query instanceof NativeQuery) {
prepareNativeSearch((NativeQuery) query, builder);
}
// query.getSort() must be checked after prepareNativeSearch as this already might hav a sort set that must have
// higher priority
@@ -1509,23 +1382,14 @@ class RequestConverter extends AbstractQueryProcessor {
Map<String, RuntimeField> runtimeMappings = new HashMap<>();
query.getRuntimeFields()
.forEach(runtimeField -> runtimeMappings.put(runtimeField.getName(), RuntimeField.of(rfb -> {
rfb.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType()));
.forEach(runtimeField -> runtimeMappings.put(runtimeField.getName(), RuntimeField.of(runtimeFieldBuilder -> {
runtimeFieldBuilder.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType()));
String script = runtimeField.getScript();
if (script != null) {
rfb
.script(s -> s
.inline(is -> {
is.source(script);
if (runtimeField.getParams() != null) {
is.params(TypeUtils.paramsMap(runtimeField.getParams()));
}
return is;
}));
runtimeFieldBuilder.script(s -> s.inline(is -> is.source(script)));
}
return rfb;
return runtimeFieldBuilder;
})));
builder.runtimeMappings(runtimeMappings);
}
@@ -1537,15 +1401,13 @@ class RequestConverter extends AbstractQueryProcessor {
} else if (forBatchedSearch) {
// request_cache is not allowed on scroll requests.
builder.requestCache(null);
// limit the number of documents in a batch if not already set in a pageable
if (query.getPageable().isUnpaged()) {
builder.size(query.getReactiveBatchSize());
}
// limit the number of documents in a batch
builder.size(query.getReactiveBatchSize());
}
if (!isEmpty(query.getIndicesBoost())) {
builder.indicesBoost(query.getIndicesBoost().stream()
.map(indexBoost -> Map.of(indexBoost.getIndexName(), (double) indexBoost.getBoost()))
.map(indexBoost -> Map.of(indexBoost.getIndexName(), Double.valueOf(indexBoost.getBoost())))
.collect(Collectors.toList()));
}
@@ -1556,33 +1418,6 @@ class RequestConverter extends AbstractQueryProcessor {
}
}
private void addIndicesOptions(SearchRequest.Builder builder, IndicesOptions indicesOptions) {
indicesOptions.getOptions().forEach(option -> {
switch (option) {
case ALLOW_NO_INDICES -> builder.allowNoIndices(true);
case IGNORE_UNAVAILABLE -> builder.ignoreUnavailable(true);
case IGNORE_THROTTLED -> builder.ignoreThrottled(true);
// the following ones aren't supported by the builder
case FORBID_ALIASES_TO_MULTIPLE_INDICES, FORBID_CLOSED_INDICES, IGNORE_ALIASES -> {
if (LOGGER.isWarnEnabled()) {
LOGGER
.warn(String.format("indices option %s is not supported by the Elasticsearch client.", option.name()));
}
}
}
});
builder.expandWildcards(indicesOptions.getExpandWildcards().stream()
.map(wildcardStates -> switch (wildcardStates) {
case OPEN -> ExpandWildcard.Open;
case CLOSED -> ExpandWildcard.Closed;
case HIDDEN -> ExpandWildcard.Hidden;
case ALL -> ExpandWildcard.All;
case NONE -> ExpandWildcard.None;
}).collect(Collectors.toList()));
}
private Rescore getRescore(RescorerQuery rescorerQuery) {
return Rescore.of(r -> r //
@@ -1601,7 +1436,7 @@ class RequestConverter extends AbstractQueryProcessor {
private void addHighlight(Query query, SearchRequest.Builder builder) {
Highlight highlight = query.getHighlightQuery()
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext(), this)
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
.getHighlight(highlightQuery.getHighlight(), highlightQuery.getType()))
.orElse(null);
@@ -1611,7 +1446,7 @@ class RequestConverter extends AbstractQueryProcessor {
private void addHighlight(Query query, MultisearchBody.Builder builder) {
Highlight highlight = query.getHighlightQuery()
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext(), this)
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
.getHighlight(highlightQuery.getHighlight(), highlightQuery.getType()))
.orElse(null);
@@ -1625,88 +1460,59 @@ class RequestConverter extends AbstractQueryProcessor {
private SortOptions getSortOptions(Sort.Order order, @Nullable ElasticsearchPersistentEntity<?> persistentEntity) {
SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.Desc : SortOrder.Asc;
Order.Mode mode = order.getDirection().isAscending() ? Order.Mode.min : Order.Mode.max;
Order.Mode mode = Order.DEFAULT_MODE;
String unmappedType = null;
String missing = null;
NestedSortValue nestedSortValue = null;
if (order instanceof Order o) {
mode = o.getMode();
unmappedType = o.getUnmappedType();
}
if (SortOptions.Kind.Score.jsonValue().equals(order.getProperty())) {
return SortOptions.of(so -> so.score(s -> s.order(sortOrder)));
}
} else {
ElasticsearchPersistentProperty property = (persistentEntity != null) //
? persistentEntity.getPersistentProperty(order.getProperty()) //
: null;
String fieldName = property != null ? property.getFieldName() : order.getProperty();
if (order instanceof Order o) {
Order.Mode finalMode = mode;
if (order instanceof GeoDistanceOrder geoDistanceOrder) {
if (o.getMode() != null) {
mode = o.getMode();
}
unmappedType = o.getUnmappedType();
missing = o.getMissing();
nestedSortValue = getNestedSort(o.getNested(), persistentEntity);
}
Order.Mode finalMode = mode;
String finalUnmappedType = unmappedType;
var finalNestedSortValue = nestedSortValue;
ElasticsearchPersistentProperty property = (persistentEntity != null) //
? persistentEntity.getPersistentProperty(order.getProperty()) //
: null;
String fieldName = property != null ? property.getFieldName() : order.getProperty();
if (order instanceof GeoDistanceOrder geoDistanceOrder) {
return getSortOptions(geoDistanceOrder, fieldName, finalMode);
}
var finalMissing = missing != null ? missing
: (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) ? "_first"
return SortOptions.of(so -> so //
.geoDistance(gd -> gd //
.field(fieldName) //
.location(loc -> loc.latlon(Queries.latLon(geoDistanceOrder.getGeoPoint()))) //
.distanceType(geoDistanceType(geoDistanceOrder.getDistanceType())).mode(sortMode(finalMode)) //
.order(sortOrder(geoDistanceOrder.getDirection())) //
.unit(distanceUnit(geoDistanceOrder.getUnit())) //
.ignoreUnmapped(geoDistanceOrder.getIgnoreUnmapped())));
} else {
String missing = (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) ? "_first"
: ((order.getNullHandling() == Sort.NullHandling.NULLS_LAST) ? "_last" : null);
String finalUnmappedType = unmappedType;
return SortOptions.of(so -> so //
.field(f -> {
f.field(fieldName) //
.order(sortOrder) //
.mode(sortMode(finalMode));
return SortOptions.of(so -> so //
.field(f -> {
f.field(fieldName) //
.order(sortOrder) //
.mode(sortMode(finalMode));
if (finalUnmappedType != null) {
FieldType fieldType = fieldType(finalUnmappedType);
if (finalUnmappedType != null) {
FieldType fieldType = fieldType(finalUnmappedType);
if (fieldType != null) {
f.unmappedType(fieldType);
}
}
if (fieldType != null) {
f.unmappedType(fieldType);
}
}
if (finalMissing != null) {
f.missing(fv -> fv //
.stringValue(finalMissing));
}
if (finalNestedSortValue != null) {
f.nested(finalNestedSortValue);
}
return f;
}));
}
@Nullable
private NestedSortValue getNestedSort(@Nullable Order.Nested nested,
@Nullable ElasticsearchPersistentEntity<?> persistentEntity) {
return (nested == null || persistentEntity == null) ? null
: NestedSortValue.of(b -> b //
.path(elasticsearchConverter.updateFieldNames(nested.getPath(), persistentEntity)) //
.maxChildren(nested.getMaxChildren()) //
.nested(getNestedSort(nested.getNested(), persistentEntity)) //
.filter(getQuery(nested.getFilter(), persistentEntity.getType())));
}
private static SortOptions getSortOptions(GeoDistanceOrder geoDistanceOrder, String fieldName, Order.Mode finalMode) {
return SortOptions.of(so -> so //
.geoDistance(gd -> gd //
.field(fieldName) //
.location(loc -> loc.latlon(Queries.latLon(geoDistanceOrder.getGeoPoint()))) //
.distanceType(geoDistanceType(geoDistanceOrder.getDistanceType())).mode(sortMode(finalMode)) //
.order(sortOrder(geoDistanceOrder.getDirection())) //
.unit(distanceUnit(geoDistanceOrder.getUnit())) //
.ignoreUnmapped(geoDistanceOrder.getIgnoreUnmapped())));
if (missing != null) {
f.missing(fv -> fv //
.stringValue(missing));
}
return f;
}));
}
}
}
@SuppressWarnings("DuplicatedCode")
@@ -1753,23 +1559,50 @@ class RequestConverter extends AbstractQueryProcessor {
}
@Nullable
co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullable Query query,
private co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullable Query query,
@Nullable Class<?> clazz) {
return getEsQuery(query, (q) -> elasticsearchConverter.updateQuery(q, clazz));
if (query == null) {
return null;
}
elasticsearchConverter.updateQuery(query, clazz);
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = null;
if (query instanceof CriteriaQuery) {
esQuery = CriteriaQueryProcessor.createQuery(((CriteriaQuery) query).getCriteria());
} else if (query instanceof StringQuery) {
esQuery = Queries.wrapperQueryAsQuery(((StringQuery) query).getSource());
} else if (query instanceof NativeQuery nativeQuery) {
if (nativeQuery.getQuery() != null) {
esQuery = nativeQuery.getQuery();
} else if (nativeQuery.getSpringDataQuery() != null) {
esQuery = getQuery(nativeQuery.getSpringDataQuery(), clazz);
}
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
}
return esQuery;
}
@SuppressWarnings("StatementWithEmptyBody")
private void addPostFilter(Query query, SearchRequest.Builder builder) {
private void addFilter(Query query, SearchRequest.Builder builder) {
// we only need to handle NativeQuery here. filter from a CriteriaQuery are added into the query and not as post
// filter anymore, StringQuery do not have post filters
if (query instanceof NativeQuery nativeQuery) {
if (query instanceof CriteriaQuery) {
CriteriaFilterProcessor.createQuery(((CriteriaQuery) query).getCriteria()).ifPresent(builder::postFilter);
} else if (query instanceof StringQuery) {
// no filter for StringQuery
} else if (query instanceof NativeQuery nativeQuery) {
if (nativeQuery.getFilter() != null) {
builder.postFilter(nativeQuery.getFilter());
} else if (nativeQuery.getSpringDataQuery() != null) {
addPostFilter(nativeQuery.getSpringDataQuery(), builder);
addFilter(nativeQuery.getSpringDataQuery(), builder);
}
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
}
}
@@ -1779,7 +1612,7 @@ class RequestConverter extends AbstractQueryProcessor {
Assert.notNull(query, "query must not be null");
Assert.notNull(index, "index must not be null");
return co.elastic.clients.elasticsearch._types.query_dsl.MoreLikeThisQuery
co.elastic.clients.elasticsearch._types.query_dsl.MoreLikeThisQuery moreLikeThisQuery = co.elastic.clients.elasticsearch._types.query_dsl.MoreLikeThisQuery
.of(q -> {
q.like(Like.of(l -> l.document(ld -> ld.index(index.getIndexName()).id(query.getId()))))
.fields(query.getFields());
@@ -1818,6 +1651,8 @@ class RequestConverter extends AbstractQueryProcessor {
return q;
});
return moreLikeThisQuery;
}
public OpenPointInTimeRequest searchOpenPointInTimeRequest(IndexCoordinates index, Duration keepAlive,
@@ -1853,8 +1688,7 @@ class RequestConverter extends AbstractQueryProcessor {
.id(query.getId()) //
.index(Arrays.asList(index.getIndexNames())) //
.preference(query.getPreference()) //
.searchType(searchType(query.getSearchType())) //
.source(query.getSource()) //
.searchType(searchType(query.getSearchType())).source(query.getSource()) //
;
if (query.getRoute() != null) {
@@ -1864,7 +1698,7 @@ class RequestConverter extends AbstractQueryProcessor {
}
var expandWildcards = query.getExpandWildcards();
if (expandWildcards != null && !expandWildcards.isEmpty()) {
if (!expandWildcards.isEmpty()) {
builder.expandWildcards(expandWildcards(expandWildcards));
}
@@ -1873,7 +1707,10 @@ class RequestConverter extends AbstractQueryProcessor {
}
if (!CollectionUtils.isEmpty(query.getParams())) {
Map<String, JsonData> params = getTemplateParams(query.getParams().entrySet());
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
Map<String, JsonData> params = query.getParams().entrySet().stream()
.collect(Collectors.toMap(keyMapper, valueMapper));
builder.params(params);
}
@@ -1881,14 +1718,6 @@ class RequestConverter extends AbstractQueryProcessor {
});
}
@NotNull
private Map<String, JsonData> getTemplateParams(Set<Map.Entry<String, Object>> query) {
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
return query.stream()
.collect(Collectors.toMap(keyMapper, valueMapper));
}
// endregion
public PutScriptRequest scriptPut(Script script) {
@@ -15,8 +15,9 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import static org.springframework.data.elasticsearch.client.elc.JsonUtils.*;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
import static org.springframework.data.elasticsearch.client.elc.JsonUtils.toJson;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.removePrefixFromJson;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.typeMapping;
import co.elastic.clients.elasticsearch._types.BulkIndexByScrollFailure;
import co.elastic.clients.elasticsearch._types.ErrorCause;
@@ -35,27 +36,18 @@ import co.elastic.clients.elasticsearch.indices.get_index_template.IndexTemplate
import co.elastic.clients.elasticsearch.indices.get_mapping.IndexMappingRecord;
import co.elastic.clients.json.JsonpMapper;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
import org.springframework.data.elasticsearch.core.IndexInformation;
import org.springframework.data.elasticsearch.core.MultiGetItem;
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.AliasData;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.index.TemplateData;
import org.springframework.data.elasticsearch.core.index.TemplateResponse;
import org.springframework.data.elasticsearch.core.index.TemplateResponseData;
import org.springframework.data.elasticsearch.core.index.*;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.StringQuery;
@@ -73,7 +65,7 @@ import org.springframework.util.Assert;
*/
class ResponseConverter {
private static final Log LOGGER = LogFactory.getLog(ResponseConverter.class);
private static final Logger LOGGER = LoggerFactory.getLogger(ResponseConverter.class);
private final JsonpMapper jsonpMapper;
@@ -129,7 +121,8 @@ class ResponseConverter {
.build();
}
private TemplateResponseData clusterGetComponentTemplateData(ComponentTemplateSummary componentTemplateSummary) {
private TemplateResponseData clusterGetComponentTemplateData(
ComponentTemplateSummary componentTemplateSummary) {
var mapping = typeMapping(componentTemplateSummary.mappings());
var settings = new Settings();
@@ -190,7 +183,7 @@ class ResponseConverter {
Map<String, IndexMappingRecord> mappings = getMappingResponse.result();
if (mappings == null || mappings.isEmpty()) {
if (mappings == null || mappings.size() == 0) {
return Document.create();
}
@@ -200,7 +193,7 @@ class ResponseConverter {
if (indexMappingRecord == null) {
if (mappings.size() != 1) {
LOGGER.warn(String.format("no mapping returned for index %s", indexCoordinates.getIndexName()));
LOGGER.warn("no mapping returned for index {}", indexCoordinates.getIndexName());
return Document.create();
}
String index = mappings.keySet().iterator().next();
@@ -333,7 +326,7 @@ class ResponseConverter {
}
private TemplateResponseData indexGetComponentTemplateData(IndexTemplateSummary indexTemplateSummary,
List<String> composedOf) {
List<String> composedOf) {
var mapping = typeMapping(indexTemplateSummary.mappings());
Function<IndexSettings, Settings> indexSettingsToSettings = indexSettings -> {
@@ -548,7 +541,7 @@ class ResponseConverter {
}
@Nullable
static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
private static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
if (errorCause != null) {
return new ElasticsearchErrorCause( //
@@ -15,8 +15,6 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.ShardFailure;
import co.elastic.clients.elasticsearch._types.ShardStatistics;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.SearchTemplateResponse;
@@ -38,7 +36,6 @@ import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.elasticsearch.core.SearchShardStatistics;
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
import org.springframework.data.elasticsearch.core.document.SearchDocument;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
@@ -55,7 +52,6 @@ import org.springframework.util.CollectionUtils;
* Factory class to create {@link SearchDocumentResponse} instances.
*
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.4
*/
class SearchDocumentResponseBuilder {
@@ -82,9 +78,8 @@ class SearchDocumentResponseBuilder {
Map<String, Aggregate> aggregations = responseBody.aggregations();
Map<String, List<Suggestion<EntityAsMap>>> suggest = responseBody.suggest();
var pointInTimeId = responseBody.pitId();
var shards = responseBody.shards();
return from(hitsMetadata, shards, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
}
/**
@@ -103,14 +98,13 @@ class SearchDocumentResponseBuilder {
Assert.notNull(entityCreator, "entityCreator must not be null");
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
var shards = response.shards();
var hitsMetadata = response.hits();
var scrollId = response.scrollId();
var aggregations = response.aggregations();
var suggest = response.suggest();
var pointInTimeId = response.pitId();
return from(hitsMetadata, shards, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
}
/**
@@ -126,8 +120,8 @@ class SearchDocumentResponseBuilder {
* @param jsonpMapper to map JsonData objects
* @return the {@link SearchDocumentResponse}
*/
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable ShardStatistics shards,
@Nullable String scrollId, @Nullable String pointInTimeId, @Nullable Map<String, Aggregate> aggregations,
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable String scrollId,
@Nullable String pointInTimeId, @Nullable Map<String, Aggregate> aggregations,
Map<String, List<Suggestion<EntityAsMap>>> suggestES, SearchDocumentResponse.EntityCreator<T> entityCreator,
JsonpMapper jsonpMapper) {
@@ -161,18 +155,8 @@ class SearchDocumentResponseBuilder {
Suggest suggest = suggestFrom(suggestES, entityCreator);
SearchShardStatistics shardStatistics = shards != null ? shardsFrom(shards) : null;
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchDocuments,
aggregationsContainer, suggest, shardStatistics);
}
private static SearchShardStatistics shardsFrom(ShardStatistics shards) {
List<ShardFailure> failures = shards.failures();
List<SearchShardStatistics.Failure> searchFailures = failures.stream().map(f -> SearchShardStatistics.Failure
.of(f.index(), f.node(), f.status(), f.shard(), null, ResponseConverter.toErrorCause(f.reason()))).toList();
return SearchShardStatistics.of(shards.failed(), shards.successful(), shards.total(), shards.skipped(),
searchFailures);
aggregationsContainer, suggest);
}
@Nullable
@@ -234,8 +218,9 @@ class SearchDocumentResponseBuilder {
var phraseSuggest = suggestionES.phrase();
var phraseSuggestOptions = phraseSuggest.options();
List<PhraseSuggestion.Entry.Option> options = new ArrayList<>();
phraseSuggestOptions.forEach(optionES -> options.add(new PhraseSuggestion.Entry.Option(optionES.text(),
optionES.highlighted(), optionES.score(), optionES.collateMatch())));
phraseSuggestOptions.forEach(optionES -> options
.add(new PhraseSuggestion.Entry.Option(optionES.text(), optionES.highlighted(), optionES.score(),
optionES.collateMatch())));
entries.add(new PhraseSuggestion.Entry(phraseSuggest.text(), phraseSuggest.offset(), phraseSuggest.length(),
options, null));
});
@@ -18,8 +18,6 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.*;
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch._types.query_dsl.ChildScoreMode;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
import co.elastic.clients.elasticsearch.core.search.BoundaryScanner;
import co.elastic.clients.elasticsearch.core.search.HighlighterEncoder;
import co.elastic.clients.elasticsearch.core.search.HighlighterFragmenter;
@@ -33,7 +31,6 @@ import co.elastic.clients.json.JsonData;
import java.io.StringReader;
import java.time.Duration;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -42,18 +39,14 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
import org.springframework.data.elasticsearch.core.query.HasChildQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
import org.springframework.data.elasticsearch.core.query.Order;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.query.types.ConflictsType;
import org.springframework.data.elasticsearch.core.query.types.OperatorType;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Utility to handle new Elasticsearch client type values.
@@ -491,62 +484,4 @@ final class TypeUtils {
return settings != null ? IndexSettings.of(b -> b.withJson(new StringReader(Document.from(settings).toJson())))
: null;
}
/**
* @since 5.2
*/
static Map<String, JsonData> paramsMap(Map<String, Object> params) {
Assert.notNull(params, "params must not be null");
Map<String, JsonData> mappedParams = new LinkedHashMap<>();
params.forEach((key, value) -> {
mappedParams.put(key, JsonData.of(value));
});
return mappedParams;
}
/**
* Convert a spring-data-elasticsearch operator to an Elasticsearch operator.
*
* @param operator spring-data-elasticsearch operator.
* @return an Elasticsearch Operator.
* @since 5.3
*/
@Nullable
static Operator operator(@Nullable OperatorType operator) {
return operator != null ? Operator.valueOf(operator.name()) : null;
}
/**
* Convert a spring-data-elasticsearch {@literal conflicts} to an Elasticsearch {@literal conflicts}.
*
* @param conflicts spring-data-elasticsearch {@literal conflicts}.
* @return an Elasticsearch {@literal conflicts}.
* @since 5.3
*/
@Nullable
static Conflicts conflicts(@Nullable ConflictsType conflicts) {
return conflicts != null ? Conflicts.valueOf(conflicts.name()) : null;
}
/**
* Convert a spring-data-elasticsearch {@literal scoreMode} to an Elasticsearch {@literal scoreMode}.
*
* @param scoreMode spring-data-elasticsearch {@literal scoreMode}.
* @return an Elasticsearch {@literal scoreMode}.
*/
static ChildScoreMode scoreMode(@Nullable HasChildQuery.ScoreMode scoreMode) {
if (scoreMode == null) {
return ChildScoreMode.None;
}
return switch (scoreMode) {
case Avg -> ChildScoreMode.Avg;
case Max -> ChildScoreMode.Max;
case Min -> ChildScoreMode.Min;
case Sum -> ChildScoreMode.Sum;
default -> ChildScoreMode.None;
};
}
}
@@ -15,10 +15,8 @@
*/
package org.springframework.data.elasticsearch.client.elc.aot;
import co.elastic.clients.elasticsearch._types.mapping.RuntimeFieldType;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch.indices.IndexSettings;
import co.elastic.clients.elasticsearch.indices.PutMappingRequest;
import java.util.Arrays;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
@@ -26,8 +24,6 @@ import org.springframework.aot.hint.TypeReference;
import org.springframework.lang.Nullable;
/**
* runtime hints for the Elasticsearch client libraries, as these do not provide any of their own.
*
* @author Peter-Josef Meisch
* @since 5.1
*/
@@ -35,22 +31,9 @@ public class ElasticsearchClientRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.reflection()
.registerType(TypeReference.of(IndexSettings.class), builder -> builder.withField("_DESERIALIZER")) //
.registerType(TypeReference.of(PutMappingRequest.class), builder -> builder.withField("_DESERIALIZER")) //
.registerType(TypeReference.of(RuntimeFieldType.class), builder -> builder.withField("_DESERIALIZER"))//
.registerType(TypeReference.of(TypeMapping.class), builder -> builder.withField("_DESERIALIZER")) //
;
hints.serialization() //
.registerType(org.apache.http.impl.auth.BasicScheme.class) //
.registerType(org.apache.http.impl.auth.RFC2617Scheme.class) //
.registerType(java.util.HashMap.class) //
;
hints.resources() //
.registerPattern("co/elastic/clients/version.properties") //
;
// needed for the http client used by the Elasticsearch client
hints.serialization().registerType(org.apache.http.impl.auth.BasicScheme.class);
hints.serialization().registerType(org.apache.http.impl.auth.RFC2617Scheme.class);
hints.serialization().registerType(java.util.HashMap.class);
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2018-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
/**
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
* @see ElasticsearchConfigurationSupport
* @deprecated since 5.0
*/
@Deprecated
public abstract class AbstractElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
/**
* Return the {@link RestHighLevelClient} instance used to connect to the cluster. <br />
*
* @return never {@literal null}.
*/
@Bean
public abstract RestHighLevelClient elasticsearchClient();
/**
* Creates {@link ElasticsearchOperations}.
*
* @return never {@literal null}.
*/
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
RestHighLevelClient elasticsearchClient) {
ElasticsearchRestTemplate template = new ElasticsearchRestTemplate(elasticsearchClient, elasticsearchConverter);
template.setRefreshPolicy(refreshPolicy());
return template;
}
}
@@ -0,0 +1,81 @@
/*
* Copyright 2018-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
import org.elasticsearch.action.support.IndicesOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
* @see ElasticsearchConfigurationSupport
* @deprecated since 5.0
*/
@Deprecated
public abstract class AbstractReactiveElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
/**
* Return the {@link ReactiveElasticsearchClient} instance used to connect to the cluster. <br />
*
* @return never {@literal null}.
*/
@Bean
public abstract ReactiveElasticsearchClient reactiveElasticsearchClient();
/**
* Creates {@link ReactiveElasticsearchOperations}.
*
* @return never {@literal null}.
*/
@Bean
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter,
ReactiveElasticsearchClient reactiveElasticsearchClient) {
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient,
elasticsearchConverter);
template.setIndicesOptions(indicesOptions());
template.setRefreshPolicy(refreshPolicy());
return template;
}
/**
* Set up the write {@link RefreshPolicy}. Default is set to null to use the cluster defaults..
*
* @return {@literal null} to use the server defaults.
*/
@Nullable
protected RefreshPolicy refreshPolicy() {
return null;
}
/**
* Set up the read {@link IndicesOptions}. Default is set to {@link IndicesOptions#strictExpandOpenAndForbidClosed()}.
*
* @return {@literal null} to use the server defaults.
*/
@Nullable
protected IndicesOptions indicesOptions() {
return IndicesOptions.strictExpandOpenAndForbidClosed();
}
}

Some files were not shown because too many files have changed in this diff Show More