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

Compare commits

..

112 Commits

Author SHA1 Message Date
Mark Paluch 1f120621fd Release version 4.4.14 (2021.2.14).
See #2593
2023-07-14 10:31:36 +02:00
Mark Paluch 27ade04f8c Prepare 4.4.14 (2021.2.14).
See #2593
2023-07-14 10:31:12 +02:00
Mark Paluch 0695a67ca9 Update CI properties.
See #2593
2023-07-03 09:43:46 +02:00
Mark Paluch 72a344918f Upgrade to Maven Wrapper 3.9.3.
See #2612
2023-07-03 09:42:35 +02:00
Mark Paluch 5ec5e52842 After release cleanups.
See #2546
2023-06-16 16:24:06 +02:00
Mark Paluch c99aa8e6b9 Prepare next development iteration.
See #2546
2023-06-16 14:59:10 +02:00
Mark Paluch 7c13580a92 Release version 4.4.13 (2021.2.13).
See #2546
2023-06-16 14:56:15 +02:00
Mark Paluch 620a8064f2 Prepare 4.4.13 (2021.2.13).
See #2546
2023-06-16 14:56:00 +02:00
Mark Paluch ecc5c54139 Remove lombok dependency override.
Lombok issues are resolved. With the Lombok customization, we no longer can resolve the artifact that breaks the build, so removing it.

See #2585
2023-06-15 17:06:08 +02:00
Mark Paluch 354c5938b6 Upgrade to Maven Wrapper 3.9.2.
See #2586
2023-06-13 08:51:21 +02:00
Mark Paluch 8753d85cf8 Use snapshot and milestone repositories instead of libs-snapshot and libs-milestone.
Closes #2585
2023-06-06 10:58:40 +02:00
Christoph Strobl e2140d05d6 After release cleanups.
See #2527
2023-05-12 10:21:26 +02:00
Christoph Strobl 0ebb6b93e5 Prepare next development iteration.
See #2527
2023-05-12 10:21:24 +02:00
Christoph Strobl 577184c29d Release version 4.4.12 (2021.2.12).
See #2527
2023-05-12 10:18:02 +02:00
Christoph Strobl 7a9aad128f Prepare 4.4.12 (2021.2.12).
See #2527
2023-05-12 10:17:45 +02:00
Greg L. Turnquist 829e2c6794 After release cleanups.
See #2498
2023-04-14 09:26:07 -05:00
Greg L. Turnquist 56df906534 Prepare next development iteration.
See #2498
2023-04-14 09:26:00 -05:00
Greg L. Turnquist 1723fd24c3 Release version 4.4.11 (2021.2.11).
See #2498
2023-04-14 09:19:58 -05:00
Greg L. Turnquist 7b01fa3582 Prepare 4.4.11 (2021.2.11).
See #2498
2023-04-14 09:19:19 -05:00
Peter-Josef Meisch 27954e83c2 Polishing
(cherry picked from commit dfc1be286c)
(cherry picked from commit fdc03cf29e)
2023-04-10 11:55:52 +02:00
JKatzwinkel 6c4d101b46 Fix inner hits metadata mapping.
Original Pull Request #2522
Closes #2521

(cherry picked from commit 1f7fa77c15)
(cherry picked from commit e1da43ca9f)
2023-04-10 11:55:51 +02:00
Mark Paluch 48d77796ab Upgrade to Maven Wrapper 3.9.1.
See #2520
2023-04-06 16:18:44 +02:00
Christoph Strobl 96a4605361 After release cleanups.
See #2484
2023-03-20 13:48:10 +01:00
Christoph Strobl 144ae6d7d1 Prepare next development iteration.
See #2484
2023-03-20 13:48:09 +01:00
Christoph Strobl 7c429c4a94 Release version 4.4.10 (2021.2.10).
See #2484
2023-03-20 13:43:05 +01:00
Christoph Strobl 08ec702138 Prepare 4.4.10 (2021.2.10).
See #2484
2023-03-20 13:42:32 +01:00
Mark Paluch 91af6fefde After release cleanups.
See #2461
2023-03-03 10:22:38 +01:00
Mark Paluch 823c164ae7 Prepare next development iteration.
See #2461
2023-03-03 10:22:36 +01:00
Mark Paluch 08823409c5 Release version 4.4.9 (2021.2.9).
See #2461
2023-03-03 10:19:08 +01:00
Mark Paluch 834f48ff67 Prepare 4.4.9 (2021.2.9).
See #2461
2023-03-03 10:18:48 +01:00
Mark Paluch 7158e04859 Upgrade to Maven Wrapper 3.9.0.
See #2473
2023-02-20 12:02:21 +01:00
Mark Paluch 5074f14f34 After release cleanups.
See #2425
2023-02-17 10:11:46 +01:00
Mark Paluch d048df2c0d Prepare next development iteration.
See #2425
2023-02-17 10:11:44 +01:00
Mark Paluch be7d2c47c4 Release version 4.4.8 (2021.2.8).
See #2425
2023-02-17 10:08:26 +01:00
Mark Paluch 12030cde7c Prepare 4.4.8 (2021.2.8).
See #2425
2023-02-17 10:08:09 +01:00
Peter-Josef Meisch 5cb4ebdd30 Upgrade to Elasticsearch 7.17.9.
Original Pull Request #2457
Closes #2453
2023-02-14 19:58:47 +01:00
Peter-Josef Meisch f8d4e1ccf9 Polishing 2023-02-10 23:53:17 +01:00
Peter-Josef Meisch c6278556b6 @Query annotated repository method does not use Sort parameter.
Original Pull Request #2450
Closes #2449

(cherry picked from commit 4f30a492b9)
(cherry picked from commit 7d5b9d5b7c)
2023-02-10 23:06:57 +01:00
Mark Paluch ea48bf5dfe Update CI properties.
See #2425
2023-01-30 10:48:59 +01:00
Mark Paluch ae4934563b After release cleanups.
See #2365
2023-01-13 10:35:35 +01:00
Mark Paluch f608c80606 Prepare next development iteration.
See #2365
2023-01-13 10:35:33 +01:00
Mark Paluch 401cf99d89 Release version 4.4.7 (2021.2.7).
See #2365
2023-01-13 10:32:46 +01:00
Mark Paluch 691a264050 Prepare 4.4.7 (2021.2.7).
See #2365
2023-01-13 10:32:32 +01:00
Peter-Josef Meisch 87540bcabc findAllById returns all requested documents.
Original Pull Request #2421
Closes #2417

(cherry picked from commit 28489ffee8)
(cherry picked from commit 6551a80ccc)
2023-01-04 20:03:40 +01:00
Mark Paluch b896694d3a Extend license header copyright years to 2023.
See #2415
2023-01-02 09:52:11 +01:00
Peter-Josef Meisch 87be6dbd82 Upgrade to Elasticsearch 7.17.8.
Original Pull Request #2413
Closes #2401
2022-12-31 00:30:58 +01:00
Mark Paluch bb05d0ba76 Update CI properties.
See #2365
2022-11-18 15:33:55 +01:00
Mark Paluch 7a7e51ff6c After release cleanups.
See #2335
2022-11-18 10:55:38 +01:00
Mark Paluch 2c2e493ce4 Prepare next development iteration.
See #2335
2022-11-18 10:55:36 +01:00
Mark Paluch 3a876901c0 Release version 4.4.6 (2021.2.6).
See #2335
2022-11-18 10:45:33 +01:00
Mark Paluch f59d9c6eae Prepare 4.4.6 (2021.2.6).
See #2335
2022-11-18 10:44:45 +01:00
Tiny a1094527a9 Update Dynamic.java
Original Pull Request #2357
Closes #2359

(cherry picked from commit f8ddf16c0c)
2022-11-10 20:18:24 +01:00
Peter-Josef Meisch cd8fabe499 Upgrade to Elasticsearch 7.17.7.
Original Pull Request #2347
Closes #2346
2022-11-01 20:29:29 +01:00
Mark Paluch b61f44d872 Update CI properties.
See #2335
2022-10-31 13:18:18 +01:00
Peter-Josef Meisch 933ebab0e0 Fix repository methods value converting.
Original Pull Request #2339
Closes #2338

(cherry picked from commit e67150a55b)
2022-10-19 22:42:06 +02:00
Spring Builds edac49c470 After release cleanups.
See #2334
2022-10-13 13:46:55 +00:00
Spring Builds cb65bc5ead Prepare next development iteration.
See #2334
2022-10-13 13:46:42 +00:00
Spring Builds c99d633ac6 Release version 4.4.5 (2021.2.5).
See #2334
2022-10-13 13:24:06 +00:00
Spring Builds 7372f9120d Prepare 4.4.5 (2021.2.5).
See #2334
2022-10-13 13:21:37 +00:00
Spring Builds c09138f985 After release cleanups.
See #2296
2022-10-13 09:23:59 +00:00
Spring Builds 5ac167290c Prepare next development iteration.
See #2296
2022-10-13 09:23:46 +00:00
Spring Builds 06db4fde05 Release version 4.4.4 (2021.2.4).
See #2296
2022-10-13 08:59:56 +00:00
Spring Builds c823ce1946 Prepare 4.4.4 (2021.2.4).
See #2296
2022-10-13 08:57:08 +00:00
Peter-Josef Meisch 2fa15f772a Escape backslash in StringQuery.
Original Pull Request
Closes #2326

(cherry picked from commit 03ecc48b09)
2022-10-11 22:38:22 +02:00
Peter-Josef Meisch 02b6d54cc9 Prefer config supplied content-type and accept header.
Original Pull Request #2328
Closes #2327
2022-10-08 15:10:26 +02:00
Peter-Josef Meisch 8bb3474c05 Revert "Support partially update document by entity."
This reverts commit 7e904cdbe7.
2022-09-24 22:00:49 +02:00
puppylpg 7e904cdbe7 Support partially update document by entity.
Original Pull Request #2305
Closes #2304
2022-09-24 21:57:41 +02:00
Spring Builds e46b4977a4 After release cleanups.
See #2221
2022-09-19 12:00:24 +00:00
Spring Builds 5de2e0b96e Prepare next development iteration.
See #2221
2022-09-19 12:00:12 +00:00
Spring Builds c36b878cee Release version 4.4.3 (2021.2.3).
See #2221
2022-09-19 11:38:46 +00:00
Spring Builds 2efa79d469 Prepare 4.4.3 (2021.2.3).
See #2221
2022-09-19 11:36:24 +00:00
Peter-Josef Meisch 20fde914df Upgrade to Elasticsearch 7.17.6.
Original Pull Request #2289
Closes #2285
2022-09-04 12:42:14 +02:00
Peter-Josef Meisch 988736dd41 Fix NPE in RequestFactory when language in UpdateQuery is not set (4.4.x).
Original Pull Request #2288
Closes #2287
2022-09-03 07:50:01 +02:00
Peter-Josef Meisch 346c5cce58 Fix mapping of property values into a collection.
When reading from Elasticsearch into a property of type Collection<T> (List<T> or Set<T>) the MappingElasticsearchConverter now can read both from the returned JSON:

    an array of T objects - will put the objects in a corresponding collection
    a single T object will put the single object into a corrsponding colletcion

This is implemented and tested for both: entities where the properties have setters and immutable classes that only provide an all-args constructor.

Original Pull Request #2282
Closes #2280

(cherry picked from commit 86634ceb38)
2022-08-29 21:06:38 +02:00
Peter-Josef Meisch a3ebd8be78 Fix update call in reactive client (Elasticsearch 7 client)
Original Pull Request #2281
Closes #2276

(cherry picked from commit 8377f64a8a)
2022-08-26 08:12:22 +02:00
Peter-Josef Meisch 3c6d96e49f Don't try to write non-writeable properties.
Original Pull Request #2249
Closes #2230

(cherry picked from commit acf02a1dc9)

# Conflicts:
#	src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java
2022-08-04 12:11:22 +02:00
Peter-Josef Meisch be70a398be Fix exists query for imperative repository implementation.
Original Pull Request #2236
Closes #2162

(cherry picked from commit 373be49f97)
2022-07-22 22:01:26 +02:00
Peter-Josef Meisch e3e666fd2e Upgrade to Elasticsearch 7.17.5.
Original Pull Request #2224
Closes #2215
2022-07-16 21:30:41 +02:00
Christoph Strobl cb3d1e11d3 After release cleanups.
See #2186
2022-07-15 11:24:08 +02:00
Christoph Strobl 9e17bf3df8 Prepare next development iteration.
See #2186
2022-07-15 11:24:05 +02:00
Christoph Strobl f70a7d6414 Release version 4.4.2 (2021.2.2).
See #2186
2022-07-15 11:08:28 +02:00
Christoph Strobl 13a4aa31f6 Prepare 4.4.2 (2021.2.2).
See #2186
2022-07-15 11:07:51 +02:00
diamondT aa4aecacdf Fix handling of array-of-strings parameters for @Query-annotated queries.
Original Pull Request  #2182
Closes #2135

(cherry picked from commit 259c43af19)
2022-07-12 20:22:57 +02:00
Peter-Josef Meisch 85ed8a4891 Upgrade to Elasticsearch 7.17.4.
Original Pull Request #2201
Closes #2199
2022-06-25 21:15:59 +02:00
Peter-Josef Meisch ae66cbd619 Fix updatebyquery request.
Original Pull Request #2197
Closes #2191

(cherry picked from commit f901380766)
2022-06-25 19:58:40 +02:00
Peter-Josef Meisch 98d1f5bf63 Update version documentation.
Original Pull Request #2195
Closes #2194

(cherry picked from commit f917fb7a65)
2022-06-25 17:43:39 +02:00
Mark Paluch d9f71027b6 After release cleanups.
See #2166
2022-06-20 11:40:07 +02:00
Mark Paluch 2729fa95a3 Prepare next development iteration.
See #2166
2022-06-20 11:40:05 +02:00
Mark Paluch 1126c65766 Release version 4.4.1 (2021.2.1).
See #2166
2022-06-20 11:29:30 +02:00
Mark Paluch db21ab06f9 Prepare 4.4.1 (2021.2.1).
See #2166
2022-06-20 11:29:07 +02:00
Mark Paluch 1af298d95a Upgrade to Maven Wrapper 3.8.5.
See #2178
2022-06-03 09:39:36 +02:00
Mark Paluch 980c6b350d Update CI properties.
See #2166
2022-06-03 09:34:36 +02:00
panzhenchao 7efd4b3be7 Fix incorrect argument check asserts.
Original Pull Request #2169
Closes #2170

(cherry picked from commit c826adb152)
2022-05-27 20:29:40 +02:00
Christoph Strobl d2cc58ccad After release cleanups.
See #2140
2022-05-13 10:15:13 +02:00
Christoph Strobl 109dc05d9b Prepare next development iteration.
See #2140
2022-05-13 10:15:11 +02:00
Christoph Strobl edde0214a0 Release version 4.4 GA (2021.2.0).
See #2140
2022-05-13 10:05:08 +02:00
Christoph Strobl c8699d93d0 Prepare 4.4 GA (2021.2.0).
See #2140
2022-05-13 10:04:21 +02:00
Peter-Josef Meisch c0b26a51f1 Add new Elasticsearch client as an alternative to the existing REST client.
Original Pull Request #2160
Closes #1973
2022-05-12 07:32:39 +02:00
Peter-Josef Meisch 3dbb1e73d6 Update to Elasticsearch 7.17.3.
Original Pull Request #2145
Closes #2144

(cherry picked from commit 0950dd6c7a)
2022-04-24 11:58:05 +02:00
Christoph Strobl e5efd31973 After release cleanups.
See #2120
2022-04-19 11:21:18 +02:00
Christoph Strobl 0637927ed4 Prepare next development iteration.
See #2120
2022-04-19 11:21:15 +02:00
Christoph Strobl 16dacbb63c Release version 4.4 RC1 (2021.2.0).
See #2120
2022-04-19 11:10:55 +02:00
Christoph Strobl 12acddb86d Prepare 4.4 RC1 (2021.2.0).
See #2120
2022-04-19 11:10:15 +02:00
Peter-Josef Meisch 8cef50347e Add more implementations using the new client.
Original Pull Request #2136
See #1973
2022-04-13 22:12:02 +02:00
Peter-Josef Meisch ea4d3f9f30 Upgrade to Elasticsearch 7.17.2.
Original Pull Request #2131
Closes #2130

(cherry picked from commit a60f3059e1)
2022-04-02 21:15:17 +02:00
Peter-Josef Meisch b9e2b13f21 Add info to readme about Elasticsearch versions.
Original Pull Request #2128
Closes #2127

(cherry picked from commit 3154c74f94)
2022-04-02 18:22:54 +02:00
Peter-Josef Meisch 5549216db0 Default Refresh policy for ReactiveElasticsearchTemplate.
Original Pull Request #2124
Closes #2110

(cherry picked from commit acd7990fac)
2022-03-24 21:11:25 +01:00
Mark Paluch a2cee9defd Update Jenkinsfile according to Java 8 build pipeline configuration.
See #2120
2022-03-24 09:30:20 +01:00
Greg L. Turnquist ea0ac3f7bc After release cleanups.
See #2092
2022-03-21 10:20:33 -05:00
Greg L. Turnquist 49cb56ed0c Prepare next development iteration.
See #2092
2022-03-21 10:20:31 -05:00
Greg L. Turnquist e8f9f9f1e3 Release version 4.4 M4 (2021.2.0).
See #2092
2022-03-21 10:09:34 -05:00
Greg L. Turnquist f91c9c443b Prepare 4.4 M4 (2021.2.0).
See #2092
2022-03-21 10:07:09 -05:00
341 changed files with 5505 additions and 9804 deletions
+2 -2
View File
@@ -1,3 +1,3 @@
#Thu Apr 06 16:17:39 CEST 2023
#Mon Jul 03 09:42:35 CEST 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.1/apache-maven-3.9.1-bin.zip
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip
+1 -1
View File
@@ -8,4 +8,4 @@ In order to run the tests locally with `./mvnw test` you need to have docker run
== 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.
Tset 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
+54 -18
View File
@@ -1,7 +1,7 @@
def p = [:]
node {
checkout scm
p = readProperties interpolate: true, file: 'ci/pipeline.properties'
checkout scm
p = readProperties interpolate: true, file: 'ci/pipeline.properties'
}
pipeline {
@@ -9,7 +9,7 @@ pipeline {
triggers {
pollSCM 'H/10 * * * *'
upstream(upstreamProjects: "spring-data-commons/3.0.x", threshold: hudson.model.Result.SUCCESS)
upstream(upstreamProjects: "spring-data-commons/2.7.x", threshold: hudson.model.Result.SUCCESS)
}
options {
@@ -32,14 +32,18 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
script {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
}
}
}
}
@@ -59,14 +63,44 @@ pipeline {
label 'data'
}
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
script {
docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) {
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
}
}
}
}
}
stage("test: baseline (LTS)") {
agent {
label 'data'
}
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
script {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.lts.image']).inside(p['docker.java.inside.docker']) {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
}
}
}
}
@@ -93,15 +127,17 @@ pipeline {
steps {
script {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
"-Dartifactory.staging-repository=libs-snapshot-local " +
"-Dartifactory.build-name=spring-data-elasticsearch " +
"-Dartifactory.build-number=${BUILD_NUMBER} " +
'-Dmaven.test.skip=true clean deploy -U -B'
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
"-Dartifactory.staging-repository=libs-snapshot-local " +
"-Dartifactory.build-name=spring-data-elasticsearch " +
"-Dartifactory.build-number=${BUILD_NUMBER} " +
'-Dmaven.test.skip=true clean deploy -U -B'
}
}
}
}
+9 -8
View File
@@ -1,3 +1,4 @@
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]]
@@ -39,7 +40,7 @@ This means that a switch to this client version can only be done with the next m
=== 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.
It should be possible to use the Elasticsearch 7 client to access a cluster running version 8 by setting the appropriate aompatibility 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
@@ -137,9 +138,9 @@ To use the Release candidate versions of the upcoming major version, use our Mav
</dependency>
<repository>
<id>spring-libs-snapshot</id>
<id>spring-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>https://repo.spring.io/libs-milestone</url>
<url>https://repo.spring.io/milestone</url>
</repository>
----
@@ -154,9 +155,9 @@ If you'd rather like the latest snapshots of the upcoming major version, use our
</dependency>
<repository>
<id>spring-libs-snapshot</id>
<id>spring-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>https://repo.spring.io/libs-snapshot</url>
<url>https://repo.spring.io/snapshot</url>
</repository>
----
@@ -169,6 +170,7 @@ 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].
* If you are upgrading, check out the https://docs.spring.io/spring-data/elasticsearch/docs/current/changelog.txt[changelog] for "`new and noteworthy`" features.
* 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].
@@ -189,9 +191,7 @@ Attach a link to your code or a compressed file containing your code.
== Building from Source
You dont need to build from source to use Spring Data (binaries in https://repo.spring.io[repo.spring.io]), but if you want to try out the latest and greatest, Spring Data can be easily built with the https://github.com/takari/maven-wrapper[maven wrapper].
You need JDK 17 or above to build the _main_ branch.
For the branches up to and including release 4.4, JDK 8 is required.
You also need JDK 1.8.
[source,bash]
----
@@ -205,6 +205,7 @@ _Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull r
IMPORTANT: When contributing, please make sure an issue exists in https://github.com/spring-projects/spring-data-elasticsearch/issues[issue tracker] and comment on this issue with how you want to address it.
By this we not only know that someone is working on an issue, we can also align architectural questions and possible solutions before work is invested . We so can prevent that much work is put into Pull Requests that have little or no chances of being merged.
=== Building reference documentation
Building the documentation builds also the project without running tests.
+8 -6
View File
@@ -1,21 +1,23 @@
# Java versions
java.main.tag=17.0.6_10-jdk-focal
java.main.tag=8u362-b09-jdk-focal
java.next.tag=20-jdk-jammy
java.lts.tag=17.0.7_7-jdk-focal
# Docker container images - standard
docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.main.tag}
docker.java.next.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.next.tag}
docker.java.lts.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.lts.tag}
# Supported versions of MongoDB
docker.mongodb.4.4.version=4.4.18
docker.mongodb.5.0.version=5.0.14
docker.mongodb.6.0.version=6.0.4
docker.mongodb.4.0.version=4.0.28
docker.mongodb.4.4.version=4.4.22
docker.mongodb.5.0.version=5.0.18
# Supported versions of Redis
docker.redis.6.version=6.2.10
docker.redis.6.version=6.2.12
# Supported versions of Cassandra
docker.cassandra.3.version=3.11.14
docker.cassandra.3.version=3.11.15
# Docker environment settings
docker.java.inside.basic=-v $HOME:/tmp/jenkins-home
+28 -82
View File
@@ -5,12 +5,12 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>5.0.5</version>
<version>4.4.14</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>3.0.5</version>
<version>2.7.14</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -18,22 +18,15 @@
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
<properties>
<springdata.commons>3.0.5</springdata.commons>
<!-- version of the RestHighLevelClient -->
<elasticsearch-rhlc>7.17.9</elasticsearch-rhlc>
<!-- version of the new ElasticsearchClient -->
<elasticsearch-java>8.5.3</elasticsearch-java>
<log4j>2.18.0</log4j>
<elasticsearch-java>7.17.9</elasticsearch-java>
<log4j>2.17.1</log4j>
<netty>4.1.65.Final</netty>
<blockhound-junit>1.0.7.RELEASE</blockhound-junit>
<hoverfly>0.14.3</hoverfly>
<jsonassert>1.5.1</jsonassert>
<testcontainers>1.17.3</testcontainers>
<wiremock>2.33.2</wiremock>
<springdata.commons>2.7.14</springdata.commons>
<testcontainers>1.16.2</testcontainers>
<blockhound-junit>1.0.6.RELEASE</blockhound-junit>
<java-module-name>spring.data.elasticsearch</java-module-name>
<!--
@@ -144,12 +137,11 @@
<scope>test</scope>
</dependency>
<!-- optional Elasticsearch RestHighLevelClient, deprecated in SDE 5.0 -->
<!-- Elasticsearch RestHighLevelClient, will be removed probably in SDE 5 -->
<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>
@@ -193,6 +185,13 @@
</dependency>
<!-- CDI -->
<!-- Dependency order required to build against CDI 1.0 and test with CDI 2.0 -->
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jcdi_2.0_spec</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
@@ -202,40 +201,23 @@
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<version>3.0.1</version>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>${cdi}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>${jakarta-annotation-api}</version>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>${javax-annotation-api}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-se</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-spi</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-impl</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
@@ -279,35 +261,17 @@
<scope>test</scope>
</dependency>
<!--
we don't use lombok in Spring Data Elasticsearch anymore. But the dependency is set in the parent project, and so the
lombok compiler stuff is executed regardless of the fact that we don't need it.
On AdoptOpenJdk 16.0.0 this leads to an error, so the project does not build.
Therefore we replace lombok with a jar - that just contains an empty file - that lives in a local maven repository in
src/test/resources/local-maven-repo/
It was installed with
mvn deploy:deploy-file -DgroupId=org.projectlombok -DartifactId=lombok -Dversion=999999 -Durl=file:./src/test/resources/local-maven-repo/ -DrepositoryId=local-maven-repo -DupdateReleaseInfo=true -Dfile=path/to/empty.jar
-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--suppress MavenPackageUpdate -->
<version>999999</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>${jsonassert}</version>
<version>1.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>${wiremock}</version>
<version>2.32.0</version>
<scope>test</scope>
<exclusions>
<!-- these exclusions are needed because of Elasticsearch JarHell-->
@@ -325,7 +289,7 @@
<dependency>
<groupId>io.specto</groupId>
<artifactId>hoverfly-java-junit5</artifactId>
<version>${hoverfly}</version>
<version>0.14.1</version>
<scope>test</scope>
</dependency>
@@ -438,7 +402,7 @@
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.7.5</version>
<version>1.5.2</version>
<dependencies>
<dependency>
<groupId>org.pitest</groupId>
@@ -447,9 +411,6 @@
</dependency>
</dependencies>
<configuration>
<jvmArgs>
<jvmArg>-XX:+AllowRedefinitionToAddDeleteMethods</jvmArg>
</jvmArgs>
<excludedGroups>integration-test</excludedGroups>
<targetClasses>
<param>org.springframework.data.elasticsearch.core.geo.*</param>
@@ -466,7 +427,7 @@
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
</plugin>
</plugins>
</plugins>
</build>
<profiles>
@@ -519,23 +480,8 @@
</profiles>
<repositories>
<repository>
<id>spring-libs-release</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
<repository>
<id>local-maven-repo</id>
<url>file:///${project.basedir}/src/test/resources/local-maven-repo</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-plugins-release</id>
<url>https://repo.spring.io/plugins-release</url>
</pluginRepository>
</pluginRepositories>
</project>
+1 -3
View File
@@ -5,15 +5,13 @@ BioMed Central Development Team; Oliver Drotbohm; Greg Turnquist; Christoph Stro
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-2022 The original author(s).
(C) 2013-2021 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[]
+5 -7
View File
@@ -17,9 +17,9 @@ include::reference/elasticsearch-new.adoc[leveloffset=+1]
* 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://repo.spring.io/libs-release
* Milestone repository - https://repo.spring.io/libs-milestone
* Snapshot repository - https://repo.spring.io/libs-snapshot
* Release repository - https://repo1.maven.org/maven2/
* Milestone repository - https://repo.spring.io/milestone/
* Snapshot repository - https://repo.spring.io/snapshot/
[[preface.requirements]]
== Requirements
@@ -37,8 +37,7 @@ built and tested.
[cols="^,^,^,^,^",options="header"]
|===
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot
| 2022.0 (Turing) | 5.0.x | 8.5.3 | 6.0.x | 3.0.x
| 2021.2 (Raj) | 4.4.x | 7.17.3 | 5.3.x | 2.7.x
| 2021.2 (Raj) | 4.4.x | 7.17.9 | 5.3.x | 2.7.x
| 2021.1 (Q) | 4.3.x | 7.15.2 | 5.3.x | 2.6.x
| 2021.0 (Pascal) | 4.2.xfootnote:oom[Out of maintenance] | 7.12.0 | 5.3.x | 2.5.x
| 2020.0 (Ockham)footnote:oom[] | 4.1.xfootnote:oom[] | 7.9.3 | 5.3.2 | 2.4.x
@@ -49,5 +48,4 @@ built and tested.
| Ingallsfootnote:oom[] | 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>>.
Support for upcoming versions of Elasticsearch is being tracked and general compatibility should be given assuming the usage of the <<elasticsearch.clients.rest,high-level REST client>>.
@@ -1,7 +1,6 @@
[[elasticsearch.auditing]]
== Elasticsearch Auditing
[[elasticsearch.auditing.preparing]]
=== 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:
@@ -55,7 +54,6 @@ public class Person implements Persistable<Long> {
<.> the getter is the required implementation from the interface
<.> an object is new if it either has no `id` or none of fields containing creation attributes are set.
[[elasticsearch.auditing.activating]]
=== 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,126 +3,18 @@
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 <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster.
Although the Elasticsearch Client can be used 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
[[elasticsearch.clients.rest]]
== High Level REST Client
To use the imperative (non-reactive) client, a configuration bean must be configured like this:
The Java High Level REST Client is the default client of Elasticsearch, it is configured like shown:
.High Level REST Client
====
[source,java]
----
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
@Configuration
public class MyClientConfig extends ElasticsearchConfiguration {
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() <.>
.connectedTo("localhost:9200")
.build();
}
}
----
<.> for a detailed description of the builder methods see <<elasticsearch.clients.configuration>>
====
The following beans can then be injected in other Spring components:
====
[source,java]
----
@Autowired
ElasticsearchOperations operations; <.>
@Autowired
ElasticsearchClient elasticsearchClient; <.>
@Autowired
RestClient restClient; <.>
----
<.> an implementation of `ElasticsearchOperations`
<.> the `co.elastic.clients.elasticsearch.ElasticsearchClient` that is used.
<.> the low level `RestClient` from the Elasticsearch libraries
====
Basically one should just use the `ElasticsearchOperations` to interact with the Elasticsearch cluster.
When using repositories, this instance is used under the hood as well.
[[elasticsearch.clients.reactiverestclient]]
== Reactive Rest Client
When working with the reactive stack, the configuration must be derived from a different class:
====
[source,java]
----
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchConfiguration;
@Configuration
public class MyClientConfig extends ReactiveElasticsearchConfiguration {
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() <.>
.connectedTo("localhost:9200")
.build();
}
}
----
<.> for a detailed description of the builder methods see <<elasticsearch.clients.configuration>>
====
The following beans can then be injected in other Spring components:
====
[source,java]
----
@Autowired
ReactiveElasticsearchOperations operations; <.>
@Autowired
ReactiveElasticsearchClient elasticsearchClient; <.>
@Autowired
RestClient restClient; <.>
----
the following can be injected:
<.> an implementation of `ReactiveElasticsearchOperations`
<.> 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
====
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 {
@@ -144,6 +36,15 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
RestHighLevelClient highLevelClient;
RestClient lowLevelClient = highLevelClient.lowLevelClient(); <3>
// ...
IndexRequest request = new IndexRequest("spring-data")
.id(randomID())
.source(singletonMap("feature", "high-level-rest-client"))
.setRefreshPolicy(IMMEDIATE);
IndexResponse response = highLevelClient.index(request,RequestOptions.DEFAULT);
----
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
@@ -152,25 +53,16 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
====
[[elasticsearch.clients.reactive]]
== Reactive Client (deprecated)
== Reactive Client
The `org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient` is a non official driver based on `WebClient`.
The `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)
.Reactive REST Client
====
[source,java]
----
import org.springframework.data.elasticsearch.client.erhlc.AbstractReactiveElasticsearchConfiguration;
@Configuration
public class ReactiveRestClientConfig extends AbstractReactiveElasticsearchConfiguration {
@@ -197,6 +89,8 @@ Mono<IndexResponse> response = client.index(request ->
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
====
NOTE: The ReactiveClient response, especially for search operations, is bound to the `from` (offset) & `size` (limit) options of the request.
[[elasticsearch.clients.configuration]]
== Client Configuration
@@ -206,11 +100,6 @@ Client behaviour can be changed via the `ClientConfiguration` that allows to set
====
[source,java]
----
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import static org.springframework.data.elasticsearch.client.elc.ElasticsearchClients.*;
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("some-header", "on every request") <.>
@@ -229,7 +118,12 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
return headers;
})
.withClientConfigurer( <.>
ElasticsearchClientConfigurationCallback.from(clientBuilder -> {
ReactiveRestClients.WebClientConfigurationCallback.from(webClient -> {
// ...
return webClient;
}))
.withClientConfigurer( <.>
RestClients.RestClientConfigurationCallback.from(clientBuilder -> {
// ...
return clientBuilder;
}))
@@ -244,72 +138,27 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
<.> Optionally set a proxy.
<.> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
<.> Set the connection timeout.
Default is 10 sec.
<.> Set the socket timeout.
Default is 5 sec.
<.> 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 <<elasticsearch.clients.configuration.callbacks>>), can be added
multiple times.
<.> A `Supplier<Header>` 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.
<.> for reactive setup a function configuring the `WebClient`
<.> for non-reactive setup a function configuring the REST client
====
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens.
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 following callbacks are provided:
[[elasticsearch.clients.configuration.callbacks.rest]]
==== Configuration of the low level Elasticsearch `RestClient`:
This callback provides a `org.elasticsearch.client.RestClientBuilder` that can be used to configure the Elasticsearch
`RestClient`:
====
[source,java]
----
ClientConfiguration.builder()
.withClientConfigurer(ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
// configure the Elasticsearch RestClient
return restClientBuilder;
}))
.build();
----
====
[[elasticsearch.clients.configurationcallbacks.httpasync]]
==== Configuration of the HttpAsyncClient used by the low level Elasticsearch `RestClient`:
This callback provides a `org.apache.http.impl.nio.client.HttpAsyncClientBuilder` to configure the HttpCLient that is
used by the `RestClient`.
====
[source,java]
----
ClientConfiguration.builder()
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
// configure the HttpAsyncClient
return httpAsyncClientBuilder;
}))
.build();
----
====
[[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
When using Spring Data Elasticsearch 4 - which uses the Elasticsearch 7 client libraries - 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]
----
@@ -333,13 +182,12 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
[[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
https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-rest-low-usage-logging.html)
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.
.Enable transport layer logging
[source,xml]
----
<logger name="tracer" level="trace"/>
<logger name="org.springframework.data.elasticsearch.client.WIRE" level="trace"/>
----
NOTE: The above applies to both the `RestHighLevelClient` and `ReactiveElasticsearchClient` when obtained via `RestClients` respectively `ReactiveRestClients`.
@@ -3,7 +3,6 @@
Spring Data Elasticsearch supports the https://www.elastic.co/guide/en/elasticsearch/reference/current/parent-join.html[Join data type] for creating the corresponding index mappings and for storing the relevant information.
[[elasticsearch.jointype.setting-up]]
== Setting up the data
For an entity to be used in a parent child join relationship, it must have a property of type `JoinField` which must be annotated.
@@ -161,7 +160,6 @@ Spring Data Elasticsearch will build the following mapping for this class:
----
====
[[elasticsearch.jointype.storing]]
== Storing data
Given a repository for this class the following code inserts a question, two answers, a comment and a vote:
@@ -211,10 +209,9 @@ void init() {
<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. <<repositories.custom-implementations>> can be used instead.
Currently native search 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:
@@ -222,17 +219,11 @@ The following code shows as an example how to retrieve all entries that have a _
[source,java]
----
SearchHits<Statement> hasVotes() {
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(hasChildQuery("vote", matchAllQuery(), ScoreMode.None))
.build();
Query query = NativeQuery.builder()
.withQuery(co.elastic.clients.elasticsearch._types.query_dsl.Query.of(qb -> qb //
.hasChild(hc -> hc
.queryName("vote") //
.query(matchAllQueryAsQuery()) //
.scoreMode(ChildScoreMode.None)//
)))
.build();
return operations.search(query, Statement.class);
return operations.search(query, Statement.class);
}
----
====
@@ -95,7 +95,6 @@ default boolean createIndex(String indexName) {
[[elasticsearch-migration-guide-3.2-4.0.deprecations]]
== Deprecations
[[elasticsearch-migration-guide-3.2-4.0.deprecations.methods-classes]]
=== Methods and classes
Many functions and classes have been deprecated. These functions still work, but the Javadocs show with what they should be replaced.
@@ -116,7 +115,6 @@ Many functions and classes have been deprecated. These functions still work, but
<T> T queryForObject(GetQuery query, Class<T> clazz);
----
[[elasticsearch-migration-guide-3.2-4.0.deprecations.elasticsearch]]
=== Elasticsearch deprecations
Since version 7 the Elasticsearch `TransportClient` is deprecated, it will be removed with Elasticsearch version 8. Spring Data Elasticsearch deprecates the `ElasticsearchTemplate` class which uses the `TransportClient` in version 4.0.
@@ -9,7 +9,7 @@ This section describes breaking changes from version 4.0.x to 4.1.x and how remo
.Definition of the id property
It is possible to define a property of en entity as the id property by naming it either `id` or `document`.
This behaviour is now deprecated and will produce a warning.
Please use the `@Id` annotation to mark a property as being the id property.
PLease us the `@Id` annotation to mark a property as being the id property.
.Index mappings
In the `ReactiveElasticsearchClient.Indices` interface the `updateMapping` methods are deprecated in favour of the `putMapping` methods.
@@ -32,7 +32,6 @@ They had been deprecated in Spring Data Elasticsearch 4.0 and their values weren
[[elasticsearch-migration-guide-4.0-4.1.breaking-changes]]
== Breaking Changes
[[elasticsearch-migration-guide-4.0-4.1.breaking-changes.returntypes-1]]
=== Return types of ReactiveElasticsearchClient.Indices methods
The methods in the `ReactiveElasticsearchClient.Indices` were not used up to now.
@@ -41,8 +40,7 @@ With the introduction of the `ReactiveIndexOperations` it became necessary to ch
* the `createIndex` variants now return a `Mono<Boolean>` instead of a `Mono<Void>` to signal successful index creation.
* the `updateMapping` variants now return a `Mono<Boolean>` instead of a `Mono<Void>` to signal successful mappings storage.
[[elasticsearch-migration-guide-4.0-4.1.breaking-changes.returntypes-2]]
=== Return types of DocumentOperations.bulkIndex methods
=== Return types of DocumentOperartions.bulkIndex methods
These methods were returning a `List<String>` containing the ids of the new indexed records.
These methods were returing a `List<String>` containing the ids of the new indexed records.
Now they return a `List<IndexedObjectInformation>`; these objects contain the id and information about optimistic locking (seq_no and primary_term)
@@ -6,7 +6,6 @@ This section describes breaking changes from version 4.1.x to 4.2.x and how remo
[[elasticsearch-migration-guide-4.1-4.2.deprecations]]
== Deprecations
[[elasticsearch-migration-guide-4.1-4.2.deprecations.document]]
=== @Document parameters
The parameters of the `@Document` annotation that are relevant for the index settings (`useServerConfiguration`, `shards`. `replicas`, `refreshIntervall` and `indexStoretype`) have been moved to the `@Setting` annotation. Use in `@Document` is still possible but deprecated.
@@ -15,7 +14,7 @@ The parameters of the `@Document` annotation that are relevant for the index se
== Removals
The `@Score` annotation that was used to set the score return value in an entity was deprecated in version 4.0 and has been removed.
Score values are returned in the `SearchHit` instances that encapsulate the returned entities.
Scroe values are returned in the `SearchHit` instances that encapsulate the returned entities.
The `org.springframework.data.elasticsearch.ElasticsearchException` class has been removed.
The remaining usages have been replaced with `org.springframework.data.mapping.MappingException` and `org.springframework.dao.InvalidDataAccessApiUsageException`.
@@ -29,10 +28,8 @@ The deprecated `find` methods from `ReactiveSearchOperations` and `ReactiveDocum
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes]]
== Breaking Changes
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.refresh-policy]]
=== RefreshPolicy
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.refresh-policy.enum]]
==== Enum package changed
It was possible in 4.1 to configure the refresh policy for the `ReactiveElasticsearchTemplate` by overriding the method `AbstractReactiveElasticsearchConfiguration.refreshPolicy()` in a custom configuration class.
@@ -41,7 +38,6 @@ The return value of this method was an instance of the class `org.elasticsearch.
Now the configuration must return `org.springframework.data.elasticsearch.core.RefreshPolicy`.
This enum has the same values and triggers the same behaviour as before, so only the `import` statement has to be adjusted.
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.refresh-policy.behaviour]]
==== Refresh behaviour
`ElasticsearchOperations` and `ReactiveElasticsearchOperations` now explicitly use the `RefreshPolicy` set on the template for write requests if not null.
@@ -51,21 +47,17 @@ The provided implementations for `ElasticsearchRepository` and `ReactiveElastics
This is the same behaviour as in previous versions.
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 <<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]]
=== Method return types
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.method-return-types.delete]]
==== delete methods that take a Query
The reactive methods previously returned a `Mono<Long>` with the number of deleted documents, the non reactive versions were void. They now return a `Mono<ByQueryResponse>` which contains much more detailed information about the deleted documents and errors that might have occurred.
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.method-return-types.multiget]]
==== multiget methods
The implementations of _multiget_ previousl only returned the found entities in a `List<T>` for non-reactive implementations and in a `Flux<T>` for reactive implementations. If the request contained ids that were not found, the information that these are missing was not available. The user needed to compare the returned ids to the requested ones to find
@@ -21,7 +21,6 @@ Check the sections on <<elasticsearch-migration-guide-4.2-4.3.deprecations>> and
[[elasticsearch-migration-guide-4.2-4.3.deprecations]]
== Deprecations
[[elasticsearch-migration-guide-4.2-4.3.deprecations.suggest]]
=== suggest methods
In `SearchOperations`, and so in `ElasticsearchOperations` as well, the `suggest` methods taking a `org.elasticsearch.search.suggest.SuggestBuilder` as argument and returning a `org.elasticsearch.action.search.SearchResponse` have been deprecated.
@@ -33,7 +32,6 @@ Here as well the old methods are deprecated.
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes]]
== Breaking Changes
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.1]]
=== Removal of `org.elasticsearch` classes from the API.
* In the `org.springframework.data.elasticsearch.annotations.CompletionContext` annotation the property `type()` has changed from `org.elasticsearch.search.suggest.completion.context.ContextMapping.Type` to `org.springframework.data.elasticsearch.annotations.CompletionContext.ContextMappingType`, the available enum values are the same.
@@ -48,7 +46,6 @@ The same change has been done to the `ReactiveSearchOperations.aggregate()` func
Programs using the aggregations need to be changed to cast the returned value to the appropriate class to further proces it.
* methods that might have thrown a `org.elasticsearch.ElasticsearchStatusException` now will throw `org.springframework.data.elasticsearch.RestStatusException` instead.
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.2]]
=== Handling of field and sourceFilter properties of Query
Up to version 4.2 the `fields` property of a `Query` was interpreted and added to the include list of the `sourceFilter`.
@@ -56,13 +53,11 @@ This was not correct, as these are different things for Elasticsearch.
This has been corrected.
As a consequence code might not work anymore that relies on using `fields` to specify which fields should be returned from the document's `_source' and should be changed to use the `sourceFilter`.
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.3]]
=== search_type default value
The default value for the `search_type` in Elasticsearch is `query_then_fetch`.
This now is also set as default value in the `Query` implementations, it was previously set to `dfs_query_then_fetch`.
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.4]]
=== BulkOptions changes
Some properties of the `org.springframework.data.elasticsearch.core.query.BulkOptions` class have changed their type:
@@ -70,17 +65,14 @@ Some properties of the `org.springframework.data.elasticsearch.core.query.BulkOp
* the type of the `timeout` property has been changed to `java.time.Duration`.
* the type of the`refreshPolicy` property has been changed to `org.springframework.data.elasticsearch.core.RefreshPolicy`.
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.5]]
=== IndicesOptions change
Spring Data Elasticsearch now uses `org.springframework.data.elasticsearch.core.query.IndicesOptions` instead of `org.elasticsearch.action.support.IndicesOptions`.
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.6]]
=== Completion classes
The classes from the package `org.springframework.data.elasticsearch.core.completion` have been moved to `org.springframework.data.elasticsearch.core.suggest`.
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.7]]
=== Other renamings
The `org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter` interface has been renamed to `org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter`.
@@ -6,7 +6,6 @@ This section describes breaking changes from version 4.3.x to 4.4.x and how remo
[[elasticsearch-migration-guide-4.3-4.4.deprecations]]
== Deprecations
[[elasticsearch-migration-guide-4.3-4.4.deprecations.reactive-operations]]
=== org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations
The method `<T> Publisher<T> execute(ClientCallback<Publisher<T>> callback)` has been deprecated.
@@ -15,7 +14,6 @@ As there now are multiple implementations using different client libraries the `
[[elasticsearch-migration-guide-4.3-4.4.breaking-changes]]
== Breaking Changes
[[elasticsearch-migration-guide-4.3-4.4.breaking-changes.1]]
=== Removal of deprecated classes
==== `org.springframework.data.elasticsearch.core.ElasticsearchTemplate` has been removed
@@ -25,13 +23,11 @@ This means that the `org.springframework.data.elasticsearch.core.ElasticsearchTe
This was the implementation of the `ElasticsearchOperations` interface that was using the `TransportClient`.
Connections to Elasticsearch must be made using either the imperative `ElasticsearchRestTemplate` or the reactive `ReactiveElasticsearchTemplate`.
[[elasticsearch-migration-guide-4.3-4.4.breaking-changes.2]]
=== Package changes
In 4.3 two classes (`ElasticsearchAggregations` and `ElasticsearchAggregation`) had been moved to the `org.springframework.data.elasticsearch.core.clients.elasticsearch7` package in preparation for the integration of the new Elasticsearch client.
The were moved back to the `org.springframework.data.elasticsearch.core` package as we keep the classes use the old Elasticsearch client where they were.
[[elasticsearch-migration-guide-4.3-4.4.breaking-changes.3]]
=== Behaviour change
The `ReactiveElasticsearchTemplate`, when created directly or by Spring Boot configuration had a default refresh policy of IMMEDIATE.
@@ -50,7 +46,6 @@ Spring Data Elasticsearch 4.4 still uses the old client as the default client fo
* There are still some bugs in the Elasticsearch client which need to be resolved
* The implementation using the new client in Spring Data Elasticsearch is not yet complete, due to limited resources working on that - remember Spring Data Elasticsearch is a community driven project that lives from public contributions.
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to]]
=== How to use the new client
CAUTION: The implementation using the new client is not complete, some operations will throw a `java.lang.UnsupportedOperationException` or might throw NPE (for example when the Elasticsearch cannot parse a response from the server, this still happens sometimes) +
@@ -58,7 +53,6 @@ Use the new client to test the implementations but do not use it in productive c
In order to try and use the new client the following steps are necessary:
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to.not]]
==== Make sure not to configure the existing default client
If using Spring Boot, exclude Spring Data Elasticsearch from the autoconfiguration
@@ -75,9 +69,8 @@ public class SpringdataElasticTestApplication {
====
Remove Spring Data Elasticsearch related properties from your application configuration.
If Spring Data Elasticsearch was configured using a programmatic configuration (see <<elasticsearch.clients>>), remove these beans from the Spring application context.
If Spring Data Elasticsearch was configured using a programmatic configuration (see <<elastisearch.clients>>), remove these beans from the Spring application context.
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to.dependencies]]
==== Add dependencies
The dependencies for the new Elasticsearch client are still optional in Spring Data Elasticsearch so they need to be added explicitly:
@@ -89,7 +82,7 @@ The dependencies for the new Elasticsearch client are still optional in Spring D
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>7.17.3</version>
<version>7.17.9</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
@@ -100,7 +93,7 @@ The dependencies for the new Elasticsearch client are still optional in Spring D
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId> <!-- is Apache 2-->
<version>7.17.3</version>
<version>7.17.9</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
@@ -123,10 +116,8 @@ When using Spring Boot, it is necessary to set the following property in the _po
----
====
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to.configuration]]
==== New configuration classes
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to.configuration.imperative]]
===== Imperative style
In order configure Spring Data Elasticsearch to use the new client, it is necessary to create a configuration bean that derives from `org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration`:
@@ -154,7 +145,6 @@ With this configuration, the following beans will be available in the Spring app
* an `ElasticsearchClient` bean, this is the new client that uses the `RestClient`
* an `ElasticsearchOperations` bean, available with the bean names _elasticsearchOperations_ and _elasticsearchTemplate_, this uses the `ElasticsearchClient`
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to.configuration.reactive]]
===== Reactive style
To use the new client in a reactive environment the only difference is the class from which to derive the configuration:
@@ -1,168 +0,0 @@
[[elasticsearch-migration-guide-4.4-5.0]]
= Upgrading from 4.4.x to 5.0.x
This section describes breaking changes from version 4.4.x to 5.0.x and how removed features can be replaced by new introduced features.
[[elasticsearch-migration-guide-4.4-4.5.deprecations]]
== Deprecations
=== 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".
[[elasticsearch-migration-guide-4.4-4.5.deprecations.package]]
=== `org.springframework.data.elasticsearch.client.erhlc` package
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
`DateFormat.none` and `DateFormat.custom` had been deprecated since version 4.2 and have been removed.
The properties of `@Document` that were deprecated since 4.2 have been removed.
Use the `@Settings` annotation for these.
`@DynamicMapping` and `@DynamicMappingValue` have been removed.
Use `@Document.dynamic` or `@Field.dynamic` instead.
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes]]
== Breaking Changes
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes.deprecated-calls]]
=== Removal of deprecated calls
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes.deprecated-calls.1]]
==== suggest calls in operations interfaces have been removed
Both `SearchOperations` and `ReactiveSearchOperations` had deprecated calls that were using Elasticsearch classes as parameters.
These now have been removed and so the dependency on Elasticsearch classes in these APIs has been cleaned.
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes-packages]]
=== Package changes
All the classes that are using or depend on the deprecated Elasticsearch `RestHighLevelClient` have been moved to the package `org.springframework.data.elasticsearch.client.erhlc`.
By this change we now have a clear separation of code using the old deprecated Elasticsearch libraries, code using the new Elasticsearch client and code that is independent of the client implementation.
Also the reactive implementation that was provided up to now has been moved here, as this implementation contains code that was copied and adapted from Elasticsearch libraries.
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 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]]
=== Conversion to Java 17 records
The following classes have been converted to `Record`, you might need to adjust the use of getter methods from
`getProp()` to `prop()`:
* `org.springframework.data.elasticsearch.core.AbstractReactiveElasticsearchTemplate.IndexResponseMetaData`
* `org.springframework.data.elasticsearch.core.ActiveShardCount`
* `org.springframework.data.elasticsearch.support.Version`
* `org.springframework.data.elasticsearch.support.ScoreDoc`
* `org.springframework.data.elasticsearch.core.query.ScriptData`
* `org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm`
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes-http-headers]]
=== New HttpHeaders class
Until version 4.4 the client configuration used the `HttpHeaders` class from the `org.springframework:spring-web`
project.
This introduces a dependency on that artifact.
Users that do not use spring-web then face an error as this class cannot be found.
In version 5.0 we introduce our own `HttpHeaders` to configure the clients.
So if you are using headers in the client configuration, you need to replace `org.springframework.http.HttpHeaders`
with `org.springframework.data.elasticsearch.support.HttpHeaders`.
Hint: You can pass a `org.springframework.http
.HttpHeaders` to the `addAll()` method of `org.springframework.data.elasticsearch.support.HttpHeaders`.
[[elasticsearch-migration-guide-4.4-5.0.new-clients]]
== New Elasticsearch client
Spring Data Elasticsearch now uses the new `ElasticsearchClient` and has deprecated the use of the previous `RestHighLevelClient`.
[[elasticsearch-migration-guide-4.4-5.0.new-clients.imperative]]
=== Imperative style configuration
To configure Spring Data Elasticsearch to use the new client, it is necessary to create a configuration bean that derives from `org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration`:
====
[source,java]
----
@Configuration
public class NewRestClientConfig extends ElasticsearchConfiguration {
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() //
.connectedTo("localhost:9200") //
.build();
}
}
----
====
The configuration is done in the same way as with the old client, but it is not necessary anymore to create more than the configuration bean.
With this configuration, the following beans will be available in the Spring application context:
* a `RestClient` bean, that is the configured low level `RestClient` that is used by the Elasticsearch client
* an `ElasticsearchClient` bean, this is the new client that uses the `RestClient`
* an `ElasticsearchOperations` bean, available with the bean names _elasticsearchOperations_ and _elasticsearchTemplate_, this uses the `ElasticsearchClient`
[[elasticsearch-migration-guide-4.4-5.0.new-clients.reactive]]
=== Reactive style configuration
To use the new client in a reactive environment the only difference is the class from which to derive the configuration:
====
[source,java]
----
@Configuration
public class NewRestClientConfig extends ReactiveElasticsearchConfiguration {
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() //
.connectedTo("localhost:9200") //
.build();
}
}
----
====
With this configuration, the following beans will be available in the Spring application context:
* a `RestClient` bean, that is the configured low level `RestClient` that is used by the Elasticsearch client
* an `ReactiveElasticsearchClient` bean, this is the new reactive client that uses the `RestClient`
* an `ReactiveElasticsearchOperations` bean, available with the bean names _reactiveElasticsearchOperations_ and _reactiveElasticsearchTemplate_, this uses the `ReactiveElasticsearchClient`
[[elasticsearch-migration-guide-4.4-5.0.old-client]]
=== Still want to use the old client?
The old deprecated `RestHighLevelClient` can still be used, but you will need to add the dependency explicitly to your application as Spring Data Elasticsearch does not pull it in automatically anymore:
====
[source,xml]
----
<!-- include the RHLC, specify version explicitly -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.9</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
----
====
Make sure to specify the version 7.17.9 explicitly, otherwise maven will resolve to 8.5.3, and this does not exist.
@@ -87,19 +87,12 @@ private ElasticsearchOperations operations;
IndexCoordinates index = IndexCoordinates.of("sample-index");
Query query = NativeQuery.builder()
.withQuery(q -> q
.matchAll(ma -> ma))
.withFilter( q -> q
.bool(b -> b
.must(m -> m
.term(t -> t
.field("id")
.value(documentId))
)))
.build();
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withFilter(boolFilter().must(termFilter("id", documentId)))
.build();
SearchHits<SampleEntity> sampleEntities = operations.search(query, SampleEntity.class, index);
Page<SampleEntity> sampleEntities = operations.searchForPage(searchQuery, SampleEntity.class, index);
----
====
@@ -114,15 +107,13 @@ This is internally used by Spring Data Elasticsearch to provide the implementati
----
IndexCoordinates index = IndexCoordinates.of("sample-index");
Query searchQuery = NativeQuery.builder()
.withQuery(q -> q
.matchAll(ma -> ma))
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
SearchHitsIterator<SampleEntity> stream = elasticsearchOperations.searchForStream(searchQuery, SampleEntity.class,
index);
SearchHitsIterator<SampleEntity> stream = elasticsearchTemplate.searchForStream(searchQuery, SampleEntity.class, index);
List<SampleEntity> sampleEntities = new ArrayList<>();
while (stream.hasNext()) {
@@ -133,28 +124,23 @@ stream.close();
----
====
There are no methods in the `SearchOperations` API to access the scroll id, if it should be necessary to access this,
the following methods of the `AbstractElasticsearchTemplate` can be used (this is the base implementation for the
different `ElasticsearchOperations` implementations:
There are no methods in the `SearchOperations` API to access the scroll id, if it should be necessary to access this, the following methods of the `ElasticsearchRestTemplate` can be used:
====
[source,java]
----
@Autowired ElasticsearchOperations operations;
AbstractElasticsearchTemplate template = (AbstractElasticsearchTemplate)operations;
@Autowired ElasticsearchRestTemplate template;
IndexCoordinates index = IndexCoordinates.of("sample-index");
Query query = NativeQuery.builder()
.withQuery(q -> q
.matchAll(ma -> ma))
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
SearchScrollHits<SampleEntity> scroll = template.searchScrollStart(1000, query, SampleEntity.class, index);
SearchScrollHits<SampleEntity> scroll = template.searchScrollStart(1000, searchQuery, SampleEntity.class, index);
String scrollId = scroll.getScrollId();
List<SampleEntity> sampleEntities = new ArrayList<>();
@@ -204,7 +190,6 @@ Sort.by(new GeoDistanceOrder("location", new GeoPoint(48.137154, 11.5761247)))
From version 7.12 on Elasticsearch has added the feature of runtime fields (https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime.html).
Spring Data Elasticsearch supports this in two ways:
[[elasticsearch.misc.runtime-fields.index-mappings]]
=== Runtime field definitions in the index mappings
The first way to define runtime fields is by adding the definitions to the index mappings (see https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-mapping-fields.html).
@@ -239,7 +224,6 @@ public class RuntimeFieldEntity {
----
====
[[elasticsearch.misc.runtime-fields.query]]
=== Runtime fields definitions set on a Query
The second way to define runtime fields is by adding the definitions to a search query (see https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-search-request.html).
@@ -277,42 +261,3 @@ SearchHits<SomethingToBuy> searchHits = operations.search(query, SomethingToBuy.
====
This works with every implementation of the `Query` interface.
[[elasticsearch.misc.point-in-time]]
== Point In Time (PIT) API
`ElasticsearchOperations` supports the point in time API of Elasticsearch (see https://www.elastic
.co/guide/en/elasticsearch/reference/8.3/point-in-time-api.html). The following code snippet shows how to use this
feature with a fictional `Person` class:
====
[source,java]
----
ElasticsearchOperations operations; // autowired
Duration tenSeconds = Duration.ofSeconds(10);
String pit = operations.openPointInTime(IndexCoordinates.of("person"), tenSeconds); <.>
// create query for the pit
Query query1 = new CriteriaQueryBuilder(Criteria.where("lastName").is("Smith"))
.withPointInTime(new Query.PointInTime(pit, tenSeconds)) <.>
.build();
SearchHits<Person> searchHits1 = operations.search(query1, Person.class);
// do something with the data
// create 2nd query for the pit, use the id returned in the previous result
Query query2 = new CriteriaQueryBuilder(Criteria.where("lastName").is("Miller"))
.withPointInTime(
new Query.PointInTime(searchHits1.getPointInTimeId(), tenSeconds)) <.>
.build();
SearchHits<Person> searchHits2 = operations.search(query2, Person.class);
// do something with the data
operations.closePointInTime(searchHits2.getPointInTimeId()); <.>
----
<.> create a point in time for an index (can be multiple names) and a keep-alive duration and retrieve its id
<.> pass that id into the query to search together with the next keep-alive value
<.> for the next query, use the id returned from the previous search
<.> when done, close the point in time using the last returned id
====
@@ -1,24 +1,11 @@
[[new-features]]
= What's new
[[new-features.5-0-1]]
== New in Spring Data Elasticsearch 5.0.1
* Upgrade to Elasticsearch 8.5.3
[[new-features.5-0-0]]
== New in Spring Data Elasticsearch 5.0
* Upgrade to Java 17 baseline
* Upgrade to Spring Framework 6
* Upgrade to Elasticsearch 8.5.0
* Use the new Elasticsearch client library
[[new-features.4-4-0]]
== New in Spring Data Elasticsearch 4.4
* Introduction of new imperative and reactive clients using the classes from the new Elasticsearch Java client
* Upgrade to Elasticsearch 7.17.3.
* Upgrade to Elasticsearch 7.17.6.
[[new-features.4-3-0]]
== New in Spring Data Elasticsearch 4.3
@@ -1,9 +1,20 @@
[[elasticsearch.mapping]]
= Elasticsearch Object Mapping
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON
representation that is stored in Elasticsearch and back. The class that is internally used for this mapping is the
`MappingElasticsearcvhConverter`.
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON representation that is stored in Elasticsearch and back.
Earlier versions of Spring Data Elasticsearch used a Jackson based conversion, Spring Data Elasticsearch 3.2.x introduced the <<elasticsearch.mapping.meta-model>>.
As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore and the `MappingElasticsearchConverter` is used.
The main reasons for the removal of the Jackson based mapper are:
* Custom mappings of fields needed to be done with annotations like `@JsonFormat` or `@JsonInclude`.
This often caused problems when the same object was used in different JSON based datastores or sent over a JSON based API.
* Custom field types and formats also need to be stored into the Elasticsearch index mappings.
The Jackson based annotations did not fully provide all the information that is necessary to represent the types of Elasticsearch.
* Fields must be mapped not only when converting from and to entities, but also in query argument, returned data and on other places.
Using the `MappingElasticsearchConverter` now covers all these cases.
[[elasticsearch.mapping.meta-model]]
== Meta Model Object Mapping
@@ -20,16 +31,17 @@ The metadata is taken from the entity's properties which can be annotated.
The following annotations are available:
* `@Document`: Applied at the class level to indicate this class is a candidate for mapping to the database.
The most important attributes are (check the API documentation for the complete list of attributes):
The most important attributes are:
** `indexName`: the name of the index to store this entity in.
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 <<elasticsearch.repositories.autocreation>>
** `versionType`: Configuration of version management.
Default value is _EXTERNAL_.
* `@Id`: Applied at the field level to mark the field used for identity purpose.
* `@Transient`, `@ReadOnlyProperty`, `@WriteOnlyProperty`: see the following section <<elasticsearch.mapping.meta-model.annotations.read-write>> for detailed information.
* `@Transient`: By default all fields are mapped to the document when it is stored or retrieved, this annotation excludes the field.
* `@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 +50,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 <<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>>.
** `format`: One or more built-in date formats, see the next section <<elasticsearch.mapping.meta-model.date-formats>>.
** `pattern`: One or more custom date formats, see the next section <<elasticsearch.mapping.meta-model.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.
@@ -49,26 +61,7 @@ In difference to a registered Spring `Converter` this only converts the annotate
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
[[elasticsearch.mapping.meta-model.annotations.read-write]]
==== Controlling which properties are written to and read from Elasticsearch
This section details the annotations that define if the value of a property is written to or
read from Elasticsearch.
`@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 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 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]]
[[elasticsearch.mapping.meta-model.date-formats]]
==== Date format mapping
Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation of type `FieldType.Date` or a custom converter must be registered for this type.
@@ -110,10 +103,6 @@ The following table shows the different attributes and the mapping created from
NOTE: If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_.
This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7].
Check the code of the `org.springframework.data.elasticsearch.annotations.DateFormat` enum for a complete list of
predefined values and their patterns.
[[elasticsearch.mapping.meta-model.annotations.range]]
==== Range types
When a field is annotated with a type of one of _Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range,_ or _Ip_Range_ the field must be an instance of a class that will be mapped to an Elasticsearch range, for example:
@@ -159,7 +148,6 @@ class SomePersonData {
Supported classes for the type `<T>` are `Integer`, `Long`, `Float`, `Double`, `Date` and classes that implement the
`TemporalAccessor` interface.
[[elasticsearch.mapping.meta-model.annotations.mapped-names]]
==== Mapped field names
Without further configuration, Spring Data Elasticsearch will use the property name of an object as field name in Elasticsearch.
@@ -169,31 +157,9 @@ It is also possible to define a `FieldNamingStrategy` in the configuration of th
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.
[[elasticsearch.mapping.meta-model.annotations.non-field-backed-properties]]
==== Non-field-backed properties
Normally the properties used in an entity are fields of the entity class. There might be cases, when a property value
is calculated in the entity and should be stored in Elasticsearch. In this case, the getter method (`getProperty()`) can be
annotated
with the `@Field` annotation, in addition to that the method must be annotated with `@AccessType(AccessType.Type
.PROPERTY)`. The third annotation that is needed in such a case is `@WriteOnlyProperty`, as such a value is only
written to Elasticsearch. A full example:
====
[source,java]
----
@Field(type = Keyword)
@WriteOnlyProperty
@AccessType(AccessType.Type.PROPERTY)
public String getProperty() {
return "some value that is calculated here";
}
----
====
[[elasticsearch.mapping.meta-model.rules]]
=== Mapping Rules
[[elasticsearch.mapping.meta-model.rules.typehints]]
==== Type Hints
Mapping uses _type hints_ embedded in the document sent to the server to allow generic type mapping.
@@ -204,6 +170,7 @@ Those type hints are represented as `_class` attributes within the document and
[source,java]
----
public class Person { <1>
@Id String id;
String firstname;
String lastname;
@@ -260,7 +227,7 @@ In this case, writing the type hint will produce an error, as the field cannot b
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:
As an alternativ they can be disabled for a single index with the `@Document` annotation:
====
[source,java]
@@ -273,7 +240,6 @@ WARNING: We strongly advise against disabling Type Hints.
Only do this if you are forced to.
Disabling type hints can lead to documents not being retrieved correctly from Elasticsearch in case of polymorphic data or document retrieval may fail completely.
[[elasticsearch.mapping.meta-model.rules.geospatial]]
==== Geospatial Types
Geospatial types like `Point` & `GeoPoint` are converted into _lat/lon_ pairs.
@@ -283,6 +249,7 @@ Geospatial types like `Point` & `GeoPoint` are converted into _lat/lon_ pairs.
[source,java]
----
public class Address {
String city, street;
Point location;
}
@@ -298,7 +265,6 @@ public class Address {
----
====
[[elasticsearch.mapping.meta-model.rules.geojson]]
==== GeoJson Types
Spring Data Elasticsearch supports the GeoJson types by providing an interface `GeoJson` and implementations for the different geometries.
@@ -339,7 +305,6 @@ The following GeoJson types are implemented:
* `GeoJsonMultiPolygon`
* `GeoJsonGeometryCollection`
[[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 <<elasticsearch.mapping.meta-model.conversions>>.
@@ -367,7 +332,6 @@ 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 <<elasticsearch.mapping.meta-model.conversions>>.
@@ -30,11 +30,37 @@ There is support for automatic creation of indices and writing the mappings when
====
[[elasticsearch.operations.resttemplate]]
== ElasticsearchRestTemplate
The `ElasticsearchRestTemplate` is an implementation of the `ElasticsearchOperations` interface using the <<elasticsearch.clients.rest>>.
.ElasticsearchRestTemplate configuration
====
[source,java]
----
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
public RestHighLevelClient elasticsearchClient() { <1>
return RestClients.create(ClientConfiguration.localhost()).rest();
}
// no special bean creation needed <2>
}
----
<1> Setting up the <<elasticsearch.clients.rest>>.
<2> The base class `AbstractElasticsearchConfiguration` already provides the `elasticsearchTemplate` bean.
====
[[elasticsearch.operations.usage]]
== Usage examples
As both `ElasticsearchTemplate` and `ElasticsearchRestTemplate` implement the `ElasticsearchOperations` interface, the code to use them is not different.
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 <<elasticsearch.mapping.meta-model.annotations>>).
The decision, if this is using the `TransportClient` or the `RestClient` is made by providing the corresponding Bean with one of the configurations shown above.
.ElasticsearchOperations usage
====
[source,java]
@@ -45,29 +71,34 @@ public class TestController {
private ElasticsearchOperations elasticsearchOperations;
public TestController(ElasticsearchOperations elasticsearchOperations) { <.>
public TestController(ElasticsearchOperations elasticsearchOperations) { <1>
this.elasticsearchOperations = elasticsearchOperations;
}
@PostMapping("/person")
public String save(@RequestBody Person person) { <.>
Person savedEntity = elasticsearchOperations.save(person);
return savedEntity.getId();
public String save(@RequestBody Person person) { <2>
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(person.getId().toString())
.withObject(person)
.build();
String documentId = elasticsearchOperations.index(indexQuery);
return documentId;
}
@GetMapping("/person/{id}")
public Person findById(@PathVariable("id") Long id) { <.>
Person person = elasticsearchOperations.get(id.toString(), Person.class);
public Person findById(@PathVariable("id") Long id) { <3>
Person person = elasticsearchOperations
.queryForObject(GetQuery.getById(id.toString()), Person.class);
return person;
}
}
----
<.> Let Spring inject the provided `ElasticsearchOperations` bean in the constructor.
<.> Store some entity in the Elasticsearch cluster. The id is read from the returned entity, as it might have been
null in the `person` object and been created by Elasticsearch.
<.> Retrieve the entity with a get by id.
<1> Let Spring inject the provided `ElasticsearchOperations` bean in the constructor.
<2> Store some entity in the Elasticsearch cluster.
<3> Retrieve the entity with a query by id.
====
To see the full possibilities of `ElasticsearchOperations` please refer to the API documentation.
@@ -119,7 +150,7 @@ An Iterator returned by the streaming functions of the `SearchOperations` interf
[[elasticsearch.operations.queries]]
== Queries
Almost all of the methods defined in the `SearchOperations` and `ReactiveSearchOperations` interface take a `Query` parameter that defines the query to execute for searching. `Query` is an interface and Spring Data Elasticsearch provides three implementations: `CriteriaQuery`, `StringQuery` and `NativeQuery`.
Almost all of the methods defined in the `SearchOperations` and `ReactiveSearchOperations` interface take a `Query` parameter that defines the query to execute for searching. `Query` is an interface and Spring Data Elasticsearch provides three implementations: `CriteriaQuery`, `StringQuery` and `NativeSearchQuery`.
[[elasticsearch.operations.criteriaquery]]
=== CriteriaQuery
@@ -199,7 +230,7 @@ The following code shows a query that searches for persons having the first name
[source,java]
----
Query query = new StringQuery("{ \"match\": { \"firstname\": { \"query\": \"Jack\" } } } ");
Query query = new SearchQuery("{ \"match\": { \"firstname\": { \"query\": \"Jack\" } } } ");
SearchHits<Person> searchHits = operations.search(query, Person.class);
----
@@ -207,28 +238,21 @@ SearchHits<Person> searchHits = operations.search(query, Person.class);
Using `StringQuery` may be appropriate if you already have an Elasticsearch query to use.
[[elasticsearch.operations.nativequery]]
=== NativeQuery
[[elasticsearch.operations.nativesearchquery]]
=== NativeSearchQuery
`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".
`NativeSearchQuery` 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 `QueryBuilder` 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 occurences of the lastnames for these persons:
====
[source,java]
----
Query query = NativeQuery.builder()
.withAggregation("lastNames", Aggregation.of(a -> a
.terms(ta -> ta.field("last-name").size(10))))
.withQuery(q -> q
.match(m -> m
.field("firstName")
.query(firstName)
)
)
.withPageable(pageable)
.build();
Query query = new NativeSearchQueryBuilder()
.addAggregation(terms("lastnames").field("lastname").size(10)) //
.withQuery(QueryBuilders.matchQuery("firstname", firstName))
.build();
SearchHits<Person> searchHits = operations.search(query, Person.class);
----
@@ -40,7 +40,6 @@ include::reactive-elasticsearch-repositories.adoc[leveloffset=+1]
[[elasticsearch.repositories.annotations]]
== Annotations for repository methods
[[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:
@@ -54,7 +53,7 @@ interface BookRepository extends Repository<Book, String> {
@HighlightField(name = "name"),
@HighlightField(name = "summary")
})
SearchHits<Book> findByNameOrSummary(String text, String summary);
List<SearchHit<Book>> findByNameOrSummary(String text, String summary);
}
----
====
@@ -63,31 +62,6 @@ It is possible to define multiple fields to be highlighted like above, and both
In the search results the highlight data can be retrieved from the `SearchHit` class.
[[elasticsearch.repositories.annotations.sourcefilters]]
=== @SourceFilters
Sometimes the user does not need to have all the properties of an entity returned from a search but only a subset.
Elasticsearch provides source filtering to reduce the amount of data that is transferred across the network to the
application.
When working with `Query` implementations and the `ElasticsearchOperations` this is easily possible by setting a
source filter on the query.
When using repository methods there is the `@SourceFilters` annotation:
====
[source,java]
----
interface BookRepository extends Repository<Book, String> {
@SourceFilters(includes = "name")
SearchHits<Book> findByName(String text);
}
----
====
In this example, all the properties of the returned `Book` objects would be `null` except the name.
[[elasticsearch.annotation]]
== Annotation based configuration
@@ -6,7 +6,6 @@
The Elasticsearch module supports all basic query building feature as string queries, native search queries, criteria based queries or have it being derived from the method name.
[[elasticsearch.query-methods.finders.declared]]
=== Declared queries
Deriving the query from the method name is not always sufficient and/or may result in unreadable method names.
@@ -299,7 +298,6 @@ A list of supported keywords for Elasticsearch is shown below.
NOTE: Methods names to build Geo-shape queries taking `GeoJson` parameters are not supported.
Use `ElasticsearchOperations` with `CriteriaQuery` in a custom repository implementation if you need to have such a function in a repository.
[[elasticsearch.query-methods.return-types]]
== Method return types
Repository methods can be defined to have the following return types for returning multiple Elements:
@@ -361,3 +359,4 @@ would make an https://www.elastic.co/guide/en/elasticsearch/reference/current/qu
}
----
====
@@ -1,3 +1,4 @@
[[elasticsearch.routing]]
= Routing values
@@ -7,7 +8,6 @@ For this Elasticsearch offers the possibility to define a routing, which is the
Spring Data Elasticsearch supports routing definitions on storing and retrieving data in the following ways:
[[elasticsearch.routing.join-types]]
== Routing on join-types
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.
@@ -15,7 +15,6 @@ When using join-types (see <<elasticsearch.jointype>>), Spring Data Elasticsearc
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).
[[elasticsearch.routing.custom]]
== Custom routing values
To define a custom routing for an entity, Spring Data Elasticsearch provides a `@Routing` annotation (reusing the `Statement` class from above):
@@ -104,3 +103,4 @@ operations.withRouting(RoutingResolver.just(routing)).delete(id);
----
<.> `RoutingResolver.just(s)` returns a resolver that will just return the given String.
====
@@ -12,7 +12,4 @@ 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[]
:leveloffset: -1
@@ -5,22 +5,80 @@
The `ReactiveElasticsearchTemplate` is the default implementation of `ReactiveElasticsearchOperations`.
[[elasticsearch.reactive.operations]]
== Reactive Elasticsearch Operations
[[elasticsearch.reactive.template]]
== Reactive Elasticsearch Template
To get started the `ReactiveElasticsearchOperations` needs to know about the actual client to work with.
Please see <<elasticsearch.clients.reactiverestclient>> for details on the client and how to configure it.
To get started the `ReactiveElasticsearchTemplate` needs to know about the actual client to work with.
Please see <<elasticsearch.clients.reactive>> for details on the client.
[[elasticsearch.reactive.operations.usage]]
=== Reactive Operations Usage
[[elasticsearch.reactive.template.configuration]]
=== Reactive Template Configuration
`ReactiveElasticsearchOperations` lets you save, find and delete your domain objects and map those objects to documents
stored
in Elasticsearch.
The easiest way of setting up the `ReactiveElasticsearchTemplate` is via `AbstractReactiveElasticsearchConfiguration` providing
dedicated configuration method hooks for `base package`, the `initial entity set` etc.
.The AbstractReactiveElasticsearchConfiguration
====
[source,java]
----
@Configuration
public class Config extends AbstractReactiveElasticsearchConfiguration {
@Bean <1>
@Override
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
// ...
}
}
----
<1> Configure the client to use. This can be done by `ReactiveRestClients` or directly via `DefaultReactiveElasticsearchClient`.
====
NOTE: If applicable set default `HttpHeaders` via the `ClientConfiguration` of the `ReactiveElasticsearchClient`. See <<elasticsearch.clients.configuration>>.
TIP: If needed the `ReactiveElasticsearchTemplate` can be configured with default `RefreshPolicy` and `IndicesOptions` that get applied to the related requests by overriding the defaults of `refreshPolicy()` and `indicesOptions()`.
However one might want to be more in control over the actual components and use a more verbose approach.
.Configure the ReactiveElasticsearchTemplate
====
[source,java]
----
@Configuration
public class Config {
@Bean <1>
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
// ...
}
@Bean <2>
public ElasticsearchConverter elasticsearchConverter() {
return new MappingElasticsearchConverter(elasticsearchMappingContext());
}
@Bean <3>
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
return new SimpleElasticsearchMappingContext();
}
@Bean <4>
public ReactiveElasticsearchOperations reactiveElasticsearchOperations() {
return new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(), elasticsearchConverter());
}
}
----
<1> Configure the client to use. This can be done by `ReactiveRestClients` or directly via `DefaultReactiveElasticsearchClient`.
<2> Set up the `ElasticsearchConverter` used for domain type mapping utilizing metadata provided by the mapping context.
<3> The Elasticsearch specific mapping context for domain type metadata.
<4> The actual template based on the client and conversion infrastructure.
====
[[elasticsearch.reactive.template.usage]]
=== Reactive Template Usage
`ReactiveElasticsearchTemplate` lets you save, find and delete your domain objects and map those objects to documents stored in Elasticsearch.
Consider the following:
.Use the ReactiveElasticsearchOperations
.Use the ReactiveElasticsearchTemplate
====
[source,java]
----
@@ -36,20 +94,15 @@ public class Person {
[source,java]
----
ReactiveELasticsearchOperations operations; <.>
// ...
operations.save(new Person("Bruce Banner", 42)) <.>
template.save(new Person("Bruce Banner", 42)) <1>
.doOnNext(System.out::println)
.flatMap(person -> operations.get(person.id, Person.class)) <.>
.flatMap(person -> template.findById(person.id, Person.class)) <2>
.doOnNext(System.out::println)
.flatMap(person -> operations.delete(person)) <.>
.flatMap(person -> template.delete(person)) <3>
.doOnNext(System.out::println)
.flatMap(id -> operations.count(Person.class)) <.>
.flatMap(id -> template.count(Person.class)) <4>
.doOnNext(System.out::println)
.subscribe(); <.>
.subscribe(); <5>
----
The above outputs the following sequence on the console.
@@ -61,10 +114,11 @@ 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.
<.> 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.
<.> Don't forget to _subscribe()_.
<1> Insert a new `Person` document into the _marvel_ index under type _characters_. The `id` is generated on server side and set into the instance returned.
<2> Lookup the `Person` with matching `id` in the _marvel_ index under type _characters_.
<3> Delete the `Person` with matching `id`, extracted from the given instance, in the _marvel_ index under type _characters_.
<4> Count the total number of documents in the _marvel_ index under type _characters_.
<5> Don't forget to _subscribe()_.
====
@@ -79,7 +79,7 @@ interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Str
parameters.
<9> Count all entities with matching `firstname`.
<10> Check if at least one entity with matching `firstname` exists.
<11> Delete all entities with matching `firstname`.
<11> Delete all entites with matching `firstname`.
====
[[elasticsearch.reactive.repositories.configuration]]
@@ -15,10 +15,10 @@
*/
package org.springframework.data.elasticsearch;
import org.springframework.lang.Nullable;
import java.util.List;
import javax.annotation.Nullable;
/**
* Object describing an Elasticsearch error
*
@@ -26,8 +26,7 @@ import java.util.List;
* @since 4.4
*/
public class ElasticsearchErrorCause {
@Nullable
private final String type;
@Nullable private final String type;
private final String reason;
@@ -16,11 +16,9 @@
package org.springframework.data.elasticsearch.annotations;
/**
* Values based on <a href="https://www.elastic.co/guide/reference/mapping/date-format/">Elasticsearch reference
* documentation</a>. The patterns are taken from this documentation and slightly adapted so that a Java
* {@link java.time.format.DateTimeFormatter} produces the same values as the Elasticsearch formatter. Use
* <code>format = {}</code> to disable built-in date formats in the {@link Field} annotation. If you want to use only a
* custom date format pattern, you must set the <code>format</code> property to empty <code>{}</code>.
* Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/. The patterns are taken
* from this documentation and slightly adapted so that a Java {@link java.time.format.DateTimeFormatter} produces the
* same values as the Elasticsearch formatter.
*
* @author Jakub Vavrik
* @author Tim te Beek
@@ -28,6 +26,19 @@ package org.springframework.data.elasticsearch.annotations;
* @author Sascha Woo
*/
public enum DateFormat {
/**
* @deprecated since 4.2, will be removed in a future version. Use <code>format = {}</code> to disable built-in date
* formats in the @Field annotation.
*/
@Deprecated
none(""), //
/**
* @deprecated since 4.2, will be removed in a future version.It is no longer required for using a custom date format
* pattern. If you want to use only a custom date format pattern, you must set the <code>format</code>
* property to empty <code>{}</code>.
*/
@Deprecated
custom(""), //
basic_date("uuuuMMdd"), //
basic_date_time("uuuuMMdd'T'HHmmss.SSSXXX"), //
basic_date_time_no_millis("uuuuMMdd'T'HHmmssXXX"), //
@@ -49,7 +60,7 @@ public enum DateFormat {
date_hour_minute_second_millis("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), //
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
date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in it's implementation
epoch_millis("epoch_millis"), //
epoch_second("epoch_second"), //
hour("HH"), //
@@ -53,6 +53,49 @@ public @interface Document {
*/
String indexName();
/**
* Use server-side settings when creating the index.
*
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
*/
@Deprecated
boolean useServerConfiguration() default false;
/**
* Number of shards for the index {@link #indexName()}. Used for index creation. <br/>
* With version 4.0, the default value is changed from 5 to 1 to reflect the change in the default settings of
* Elasticsearch which changed to 1 as well in Elasticsearch 7.0.
* ComposableAnnotationsUnitTest.documentAnnotationShouldBeComposable:60
*
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
*/
@Deprecated
short shards() default 1;
/**
* Number of replicas for the index {@link #indexName()}. Used for index creation.
*
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
*/
@Deprecated
short replicas() default 1;
/**
* Refresh interval for the index {@link #indexName()}. Used for index creation.
*
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
*/
@Deprecated
String refreshInterval() default "1s";
/**
* Index storage type for the index {@link #indexName()}. Used for index creation.
*
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
*/
@Deprecated
String indexStoreType() default "fs";
/**
* Configuration whether to create an index on repository bootstrapping.
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2022-2023 the original author or authors.
* Copyright 2019-2023 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.
@@ -22,14 +22,19 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to mark a property that will be written to Elasticsearch, but not set when reading from Elasticsearch.
* This is needed for synthesized fields that may be used for search but that are not available in the entity.
* Annotation to set the dynamic mapping mode
* {@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html">elasticsearch doc</a>}
*
* @author Peter-Josef Meisch
* @since 5.0
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Target({ ElementType.TYPE, ElementType.FIELD })
@Documented
public @interface WriteOnlyProperty {
@Deprecated
public @interface DynamicMapping {
DynamicMappingValue value() default DynamicMappingValue.True;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2023 the original author or authors.
* Copyright 2019-2023 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.
@@ -13,23 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.aot;
import java.util.function.Predicate;
import org.springframework.data.util.ReactiveWrappers;
package org.springframework.data.elasticsearch.annotations;
/**
* values for the {@link DynamicMapping annotation}
*
* @author Peter-Josef Meisch
* @since 5.0.1
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
public class ElasticsearchAotPredicates {
@Deprecated
public enum DynamicMappingValue {
True("true"), False("false"), Strict("strict");
public static final Predicate<ReactiveWrappers.ReactiveLibrary> IS_REACTIVE_LIBARARY_AVAILABLE = (
lib) -> ReactiveWrappers.isAvailable(lib);
private final String mappedName;
public static boolean isReactorPresent() {
return IS_REACTIVE_LIBARARY_AVAILABLE.test(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
DynamicMappingValue(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
@@ -39,7 +39,7 @@ import org.springframework.core.annotation.AliasFor;
* @author Sascha Woo
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Documented
@Inherited
public @interface Field {
@@ -53,8 +53,9 @@ public @interface Field {
String value() default "";
/**
* The <em>name</em> to be used to store the field inside the document. If not set, the name of the annotated property
* is used.
* The <em>name</em> to be used to store the field inside the document.
* <p>
* √5 If not set, the name of the annotated property is used.
*
* @since 3.2
*/
@@ -15,14 +15,8 @@
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.annotation.QueryAnnotation;
import java.lang.annotation.*;
/**
* Query
@@ -40,17 +34,16 @@ import org.springframework.data.annotation.QueryAnnotation;
public @interface Query {
/**
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0. Alias for query.
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0
*/
@AliasFor("query")
String value() default "";
/**
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0. Alias for value
* @since 5.0
* Named Query Named looked up by repository.
*
* @deprecated since 4.2, not implemented and used anywhere
*/
@AliasFor("value")
String query() default "";
String name() default "";
/**
* Returns whether the query defined should be executed as count projection.
@@ -1,73 +0,0 @@
/*
* Copyright 2022-2023 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.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation can be placed on repository methods to define the properties that should be requested from
* Elasticsearch when the method is run.
*
* @author Alexander Torres
* @author Peter-Josef Meisch
* @since 5.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
public @interface SourceFilters {
/**
* Properties to be requested from Elasticsearch to be included in the response. These can be passed in as literals
* like
*
* <pre>
* {@code @SourceFilters(includes = {"property1", "property2"})}
* </pre>
*
* or as a parameterized value
*
* <pre>
* {@code @SourceFilters(includes = "?0")}
* </pre>
*
* when the list of properties is passed as a function parameter.
*/
String[] includes() default "";
/**
* Properties to be requested from Elasticsearch to be excluded in the response. These can be passed in as literals
* like
*
* <pre>
* {@code @SourceFilters(excludes = {"property1", "property2"})}
* </pre>
*
* or as a parameterized value
*
* <pre>
* {@code @SourceFilters(excludes = "?0")}
* </pre>
*
* when the list of properties is passed as a function parameter.
*/
String[] excludes() default "";
}
@@ -1,75 +0,0 @@
/*
* Copyright 2023 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.aot;
import static org.springframework.data.elasticsearch.aot.ElasticsearchAotPredicates.*;
import java.util.Arrays;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
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.0.1
*/
public class ElasticsearchRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.reflection().registerTypes( //
Arrays.asList( //
TypeReference.of(AfterConvertCallback.class), //
TypeReference.of(AfterLoadCallback.class), //
TypeReference.of(AfterSaveCallback.class), //
TypeReference.of(BeforeConvertCallback.class), //
TypeReference.of(EntityAsMap.class) //
), //
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS));
if (isReactorPresent()) {
hints.reflection().registerTypes( //
Arrays.asList( //
TypeReference.of(ReactiveAfterConvertCallback.class), //
TypeReference.of(ReactiveAfterLoadCallback.class), //
TypeReference.of(ReactiveAfterSaveCallback.class), //
TypeReference.of(ReactiveBeforeConvertCallback.class) //
), //
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS));
}
// properties needed to log the different versions
hints.resources().registerPattern("versions.properties");
hints.resources().registerPattern("co/elastic/clients/version.properties");
}
}
@@ -26,7 +26,9 @@ import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
@@ -52,6 +54,7 @@ public interface ClientConfiguration {
/**
* Creates a new {@link ClientConfiguration} instance configured to localhost.
* <p/>
*
* <pre class="code">
* // "localhost:9200"
@@ -66,8 +69,9 @@ public interface ClientConfiguration {
}
/**
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@code hostAndPort}. For
* example given the endpoint http://localhost:9200
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@code hostAndPort}.
* <p/>
* For example given the endpoint http://localhost:9200
*
* <pre class="code">
* ClientConfiguration configuration = ClientConfiguration.create("localhost:9200");
@@ -80,8 +84,9 @@ public interface ClientConfiguration {
}
/**
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@link InetSocketAddress}. For
* example given the endpoint http://localhost:9200
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@link InetSocketAddress}.
* <p/>
* For example given the endpoint http://localhost:9200
*
* <pre class="code">
* ClientConfiguration configuration = ClientConfiguration
@@ -168,6 +173,14 @@ public interface ClientConfiguration {
*/
Function<WebClient, WebClient> getWebClientConfigurer();
/**
* @return the Rest Client configuration callback.
* @since 4.2
* @deprecated since 4.3 use {@link #getClientConfigurers()}
*/
@Deprecated
HttpClientConfigCallback getHttpClientConfigurer();
/**
* @return the client configuration callbacks
* @since 4.3
@@ -336,6 +349,29 @@ public interface ClientConfiguration {
*/
TerminalClientConfigurationBuilder withProxy(String proxy);
/**
* set customization hook in case of a reactive configuration
*
* @param webClientConfigurer function to configure the WebClient
* @return the {@link TerminalClientConfigurationBuilder}.
* @deprecated since 4.3, use {@link #withClientConfigurer(ClientConfigurationCallback)} with
* {@link ReactiveRestClients.WebClientConfigurationCallback}
*/
@Deprecated
TerminalClientConfigurationBuilder withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
/**
* Register a {HttpClientConfigCallback} to configure the non-reactive REST client.
*
* @param httpClientConfigurer configuration callback, must not be null.
* @return the {@link TerminalClientConfigurationBuilder}.
* @since 4.2
* @deprecated since 4.3, use {@link #withClientConfigurer(ClientConfigurationCallback)} with
* {@link RestClients.RestClientConfigurationCallback}
*/
@Deprecated
TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientConfigCallback httpClientConfigurer);
/**
* Register a {@link ClientConfigurationCallback} to configure the client.
*
@@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
@@ -30,7 +31,8 @@ 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;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
@@ -49,7 +51,7 @@ class ClientConfigurationBuilder
implements ClientConfigurationBuilderWithRequiredEndpoint, MaybeSecureClientConfigurationBuilder {
private final List<InetSocketAddress> hosts = new ArrayList<>();
private HttpHeaders headers = new HttpHeaders();
private HttpHeaders headers = HttpHeaders.EMPTY;
private boolean useSsl;
private @Nullable SSLContext sslContext;
private @Nullable HostnameVerifier hostnameVerifier;
@@ -59,10 +61,10 @@ class ClientConfigurationBuilder
private @Nullable String password;
private @Nullable String pathPrefix;
private @Nullable String proxy;
private final Function<WebClient, WebClient> webClientConfigurer = Function.identity();
private Supplier<HttpHeaders> headersSupplier = HttpHeaders::new;
@Deprecated private final HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
private final List<ClientConfiguration.ClientConfigurationCallback<?>> clientConfigurers = new ArrayList<>();
private Function<WebClient, WebClient> webClientConfigurer = Function.identity();
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
@Deprecated private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
private List<ClientConfiguration.ClientConfigurationCallback<?>> clientConfigurers = new ArrayList<>();
/*
* (non-Javadoc)
@@ -73,7 +75,7 @@ class ClientConfigurationBuilder
Assert.notEmpty(hostAndPorts, "At least one host is required");
this.hosts.addAll(Arrays.stream(hostAndPorts).map(ClientConfigurationBuilder::parse).toList());
this.hosts.addAll(Arrays.stream(hostAndPorts).map(ClientConfigurationBuilder::parse).collect(Collectors.toList()));
return this;
}
@@ -199,6 +201,28 @@ class ClientConfigurationBuilder
return this;
}
@Override
public TerminalClientConfigurationBuilder withWebClientConfigurer(
Function<WebClient, WebClient> webClientConfigurer) {
Assert.notNull(webClientConfigurer, "webClientConfigurer must not be null");
this.webClientConfigurer = webClientConfigurer;
this.clientConfigurers.add(ReactiveRestClients.WebClientConfigurationCallback.from(webClientConfigurer));
return this;
}
@Override
public TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientConfigCallback httpClientConfigurer) {
Assert.notNull(httpClientConfigurer, "httpClientConfigurer must not be null");
this.httpClientConfigurer = httpClientConfigurer;
this.clientConfigurers
.add(RestClients.RestClientConfigurationCallback.from(httpClientConfigurer::customizeHttpClient));
return this;
}
@Override
public TerminalClientConfigurationBuilder withClientConfigurer(
ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer) {
@@ -226,6 +250,9 @@ class ClientConfigurationBuilder
public ClientConfiguration build() {
if (username != null && password != null) {
if (HttpHeaders.EMPTY.equals(headers)) {
headers = new HttpHeaders();
}
headers.setBasicAuth(username, password);
}
@@ -19,6 +19,7 @@ import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
@@ -29,11 +30,8 @@ import org.springframework.util.ObjectUtils;
* @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");
@@ -127,10 +125,10 @@ public abstract class ClientLogger {
* @param logId the correlation id, see {@link #newLogId()}.
* @param statusCode the HTTP status code.
*/
public static void logRawResponse(String logId, @Nullable Integer statusCode) {
public static void logRawResponse(String logId, @Nullable HttpStatus statusCode) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Received raw response: %d", logId, statusCode));
WIRE_LOGGER.trace(String.format("[%s] Received raw response: %s", logId, statusCode));
}
}
@@ -141,10 +139,10 @@ public abstract class ClientLogger {
* @param statusCode the HTTP status code.
* @param headers a String containing the headers
*/
public static void logRawResponse(String logId, @Nullable Integer statusCode, String headers) {
public static void logRawResponse(String logId, @Nullable HttpStatus statusCode, String headers) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Received response: %d%n%s", logId, statusCode, headers));
WIRE_LOGGER.trace(String.format("[%s] Received response: %s%n%s", logId, statusCode, headers));
}
}
@@ -155,10 +153,10 @@ public abstract class ClientLogger {
* @param statusCode the HTTP status code.
* @param body body content.
*/
public static void logResponse(String logId, Integer statusCode, String body) {
public static void logResponse(String logId, HttpStatus statusCode, String body) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Received response: %d%nResponse body: %s", logId, statusCode, body));
WIRE_LOGGER.trace(String.format("[%s] Received response: %s%nResponse body: %s", logId, statusCode, body));
}
}
@@ -171,10 +169,10 @@ public abstract class ClientLogger {
* @param body body content.
* @since 4.4
*/
public static void logResponse(String logId, @Nullable Integer statusCode, String headers, String body) {
public static void logResponse(String logId, @Nullable HttpStatus statusCode, String headers, String body) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Received response: %d%nHeaders: %s%nResponse body: %s", logId, statusCode,
WIRE_LOGGER.trace(String.format("[%s] Received response: %s%nHeaders: %s%nResponse body: %s", logId, statusCode,
headers, body));
}
}
@@ -17,6 +17,8 @@ package org.springframework.data.elasticsearch.client;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
@@ -26,7 +28,7 @@ import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
@@ -61,8 +63,8 @@ class DefaultClientConfiguration implements ClientConfiguration {
Function<WebClient, WebClient> webClientConfigurer, HttpClientConfigCallback httpClientConfigurer,
List<ClientConfigurationCallback<?>> clientConfigurers, Supplier<HttpHeaders> headersSupplier) {
this.hosts = List.copyOf(hosts);
this.headers = headers;
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
this.headers = new HttpHeaders(headers);
this.useSsl = useSsl;
this.sslContext = sslContext;
this.soTimeout = soTimeout;
@@ -127,6 +129,13 @@ class DefaultClientConfiguration implements ClientConfiguration {
return webClientConfigurer;
}
@Deprecated
@Override
public HttpClientConfigCallback getHttpClientConfigurer() {
return httpClientConfigurer;
}
@SuppressWarnings("unchecked")
@Override
public <T> List<ClientConfigurationCallback<?>> getClientConfigurers() {
return clientConfigurers;
@@ -26,7 +26,7 @@ import org.springframework.util.StringUtils;
* @author Mark Paluch
* @since 3.2
*/
public class InetSocketAddressParser {
class InetSocketAddressParser {
/**
* Parse a host and port string into a {@link InetSocketAddress}.
@@ -36,7 +36,7 @@ public class InetSocketAddressParser {
* @return a {@link InetSocketAddress} that is unresolved to avoid DNS lookups.
* @see InetSocketAddress#createUnresolved(String, int)
*/
public static InetSocketAddress parse(String hostPortString, int defaultPort) {
static InetSocketAddress parse(String hostPortString, int defaultPort) {
Assert.notNull(hostPortString, "HostPortString must not be null");
String host;
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client;
import java.net.URL;
import java.util.ArrayList;
@@ -31,17 +31,14 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* RestHighLevelClientFactoryBean
* RestClientFactoryBean
*
* @author Don Wellington
* @author Peter-Josef Meisch
* @deprecated since 5.0
*/
@Deprecated
public class RestHighLevelClientFactoryBean
implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
public class RestClientFactoryBean implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
private static final Log LOGGER = LogFactory.getLog(RestHighLevelClientFactoryBean.class);
private static final Log LOGGER = LogFactory.getLog(RestClientFactoryBean.class);
private @Nullable RestHighLevelClient client;
private String hosts = "http://localhost:9200";
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
@@ -43,10 +43,8 @@ import org.apache.http.protocol.HttpContext;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.RestHighLevelClientBuilder;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.ClientLogger;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
/**
@@ -60,9 +58,7 @@ import org.springframework.util.Assert;
* @author Peter-Josef Meisch
* @author Nic Hines
* @since 3.2
* @deprecated since 5.0
*/
@Deprecated
public final class RestClients {
/**
@@ -127,7 +123,8 @@ public final class RestClients {
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurer instanceof RestClientConfigurationCallback restClientConfigurationCallback) {
if (clientConfigurer instanceof RestClientConfigurationCallback) {
RestClientConfigurationCallback restClientConfigurationCallback = (RestClientConfigurationCallback) clientConfigurer;
clientBuilder = restClientConfigurationCallback.configure(clientBuilder);
}
}
@@ -135,7 +132,7 @@ public final class RestClients {
return clientBuilder;
});
RestHighLevelClient client = new RestHighLevelClientBuilder(builder.build()).setApiCompatibilityMode(true).build();
RestHighLevelClient client = new RestHighLevelClient(builder);
return () -> client;
}
@@ -184,9 +181,7 @@ public final class RestClients {
*
* @see ClientLogger
* @since 3.2
* @deprecated since 5.0
*/
@Deprecated
private static class HttpLoggingInterceptor implements HttpResponseInterceptor, HttpRequestInterceptor {
@Override
@@ -199,9 +194,9 @@ public final class RestClients {
context.setAttribute(RestClients.LOG_ID_ATTRIBUTE, logId);
}
if (request instanceof HttpEntityEnclosingRequest entityRequest
&& ((HttpEntityEnclosingRequest) request).getEntity() != null) {
if (request instanceof HttpEntityEnclosingRequest && ((HttpEntityEnclosingRequest) request).getEntity() != null) {
HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request;
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
entity.writeTo(buffer);
@@ -220,7 +215,7 @@ public final class RestClients {
@Override
public void process(HttpResponse response, HttpContext context) {
String logId = (String) context.getAttribute(RestClients.LOG_ID_ATTRIBUTE);
ClientLogger.logRawResponse(logId, response.getStatusLine().getStatusCode());
ClientLogger.logRawResponse(logId, HttpStatus.resolve(response.getStatusLine().getStatusCode()));
}
}
@@ -241,7 +236,7 @@ public final class RestClients {
public void process(HttpRequest request, HttpContext context) {
HttpHeaders httpHeaders = headersSupplier.get();
if (httpHeaders != null && !httpHeaders.isEmpty()) {
if (httpHeaders != null && httpHeaders != HttpHeaders.EMPTY) {
Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader);
}
}
@@ -252,9 +247,7 @@ public final class RestClients {
* the RestClient with a {@link HttpAsyncClientBuilder}
*
* @since 4.3
* @deprecated since 5.0
*/
@Deprecated
public interface RestClientConfigurationCallback
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
@@ -1,25 +0,0 @@
/*
* Copyright 2022-2023 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;
/**
* @author Peter-Josef Meisch
*/
public class UnsupportedClientOperationException extends RuntimeException {
public UnsupportedClientOperationException(Class<?> clientClass, String operation) {
super("Client %1$s does not support the operation %2$s".formatted(clientClass, operation));
}
}
@@ -19,7 +19,7 @@ import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
/**
* Class to combine an Elasticsearch {@link co.elastic.clients.elasticsearch._types.aggregations.Aggregate} with its
* name. Necessary as the Elasticsearch Aggregate does not know its name.
* name. Necessary as the Elasticsearch Aggregate does not know i"s name.
*
* @author Peter-Josef Meisch
* @since 4.4
@@ -18,7 +18,6 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.ApiClient;
import co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.transport.Transport;
import java.io.IOException;
@@ -33,7 +32,7 @@ import org.springframework.util.Assert;
* @author Peter-Josef Meisch
* @since 4.4
*/
public abstract class ChildTemplate<T extends Transport, CLIENT extends ApiClient<T, CLIENT>> {
public abstract class ChildTemplate<CLIENT extends ApiClient> {
protected final CLIENT client;
protected final RequestConverter requestConverter;
@@ -18,7 +18,6 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient;
import co.elastic.clients.elasticsearch.cluster.HealthRequest;
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
import co.elastic.clients.transport.ElasticsearchTransport;
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
import org.springframework.data.elasticsearch.core.cluster.ClusterOperations;
@@ -30,8 +29,7 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte
* @author Peter-Josef Meisch
* @since 4.4
*/
public class ClusterTemplate extends ChildTemplate<ElasticsearchTransport, ElasticsearchClusterClient>
implements ClusterOperations {
public class ClusterTemplate extends ChildTemplate<ElasticsearchClusterClient> implements ClusterOperations {
public ClusterTemplate(ElasticsearchClusterClient client, ElasticsearchConverter elasticsearchConverter) {
super(client, elasticsearchConverter);
@@ -130,37 +130,37 @@ class CriteriaFilterProcessor {
ObjectBuilder<? extends QueryVariant> queryBuilder = null;
switch (key) {
case WITHIN -> {
case WITHIN:
Assert.isTrue(value instanceof Object[], "Value of a geo distance filter should be an array of two values.");
queryBuilder = withinQuery(fieldName, (Object[]) value);
}
case BBOX -> {
break;
case BBOX:
Assert.isTrue(value instanceof Object[],
"Value of a boundedBy filter should be an array of one or two values.");
queryBuilder = boundingBoxQuery(fieldName, (Object[]) value);
}
case GEO_INTERSECTS -> {
break;
case GEO_INTERSECTS:
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_INTERSECTS filter must be a GeoJson object");
queryBuilder = geoJsonQuery(fieldName, (GeoJson<?>) value, "intersects");
}
case GEO_IS_DISJOINT -> {
break;
case GEO_IS_DISJOINT:
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_IS_DISJOINT filter must be a GeoJson object");
queryBuilder = geoJsonQuery(fieldName, (GeoJson<?>) value, "disjoint");
}
case GEO_WITHIN -> {
break;
case GEO_WITHIN:
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_WITHIN filter must be a GeoJson object");
queryBuilder = geoJsonQuery(fieldName, (GeoJson<?>) value, "within");
}
case GEO_CONTAINS -> {
break;
case GEO_CONTAINS:
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_CONTAINS filter must be a GeoJson object");
queryBuilder = geoJsonQuery(fieldName, (GeoJson<?>) value, "contains");
}
break;
}
return Optional.ofNullable(queryBuilder != null ? queryBuilder.build()._toQuery() : null);
}
private static ObjectBuilder<GeoDistanceQuery> withinQuery(String fieldName, Object... values) {
private static ObjectBuilder<GeoDistanceQuery> withinQuery(String fieldName, Object[] values) {
Assert.noNullElements(values, "Geo distance filter takes 2 not null elements array as parameter.");
Assert.isTrue(values.length == 2, "Geo distance filter takes a 2-elements array as parameter.");
@@ -176,7 +176,8 @@ class CriteriaFilterProcessor {
.distance(dist) //
.distanceType(GeoDistanceType.Plane) //
.location(location -> {
if (values[0]instanceof GeoPoint loc) {
if (values[0] instanceof GeoPoint) {
GeoPoint loc = (GeoPoint) values[0];
location.latlon(latlon -> latlon.lat(loc.getLat()).lon(loc.getLon()));
} else if (values[0] instanceof Point) {
GeoPoint loc = GeoPoint.fromPoint((Point) values[0]);
@@ -194,7 +195,7 @@ class CriteriaFilterProcessor {
});
}
private static ObjectBuilder<GeoBoundingBoxQuery> boundingBoxQuery(String fieldName, Object... values) {
private static ObjectBuilder<GeoBoundingBoxQuery> boundingBoxQuery(String fieldName, Object[] values) {
Assert.noNullElements(values, "Geo boundedBy filter takes a not null element array as parameter.");
@@ -239,12 +240,13 @@ class CriteriaFilterProcessor {
)))));
}
private static void twoParameterBBox(GeoBoundingBoxQuery.Builder queryBuilder, Object... values) {
private static void twoParameterBBox(GeoBoundingBoxQuery.Builder queryBuilder, Object[] values) {
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) {
GeoPoint topLeft = (GeoPoint) values[0];
GeoPoint bottomRight = (GeoPoint) values[1];
queryBuilder.boundingBox(bb -> bb //
.tlbr(tlbr -> tlbr //
@@ -327,8 +329,12 @@ class CriteriaFilterProcessor {
StringBuilder sb = new StringBuilder();
sb.append((int) distance.getValue());
switch ((Metrics) distance.getMetric()) {
case KILOMETERS -> sb.append("km");
case MILES -> sb.append("mi");
case KILOMETERS:
sb.append("km");
break;
case MILES:
sb.append("mi");
break;
}
return sb.toString();
@@ -28,6 +28,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.Field;
@@ -183,7 +184,7 @@ class CriteriaQueryProcessor {
Criteria.OperationKey key = entry.getKey();
Object value = key.hasValue() ? entry.getValue() : null;
String searchText = value != null ? escape(value.toString()) : "UNKNOWN_VALUE";
String searchText = value != null ? QueryParserUtil.escape(value.toString()) : "UNKNOWN_VALUE";
Query.Builder queryBuilder = new Query.Builder();
switch (key) {
@@ -288,7 +289,8 @@ class CriteriaQueryProcessor {
break;
case IN:
if (value instanceof Iterable<?> iterable) {
if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) value;
if (isKeywordField) {
queryBuilder.bool(bb -> bb //
.must(mb -> mb //
@@ -309,7 +311,8 @@ class CriteriaQueryProcessor {
}
break;
case NOT_IN:
if (value instanceof Iterable<?> iterable) {
if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) value;
if (isKeywordField) {
queryBuilder.bool(bb -> bb //
.mustNot(mnb -> mnb //
@@ -355,32 +358,11 @@ class CriteriaQueryProcessor {
sb.append(' ');
}
sb.append('"');
sb.append(escape(item.toString()));
sb.append(QueryParserUtil.escape(item.toString()));
sb.append('"');
}
}
return sb.toString();
}
/**
* Returns a String where those characters that TextParser expects to be escaped are escaped by a preceding
* <code>\</code>. Copied from Apachae 2 licensed org.apache.lucene.queryparser.flexible.standard.QueryParserUtil
* class
*/
public static String escape(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// These characters are part of the query syntax and must be escaped
if (c == '\\' || c == '+' || c == '-' || c == '!' || c == '(' || c == ')' || c == ':' || c == '^' || c == '['
|| c == ']' || c == '\"' || c == '{' || c == '}' || c == '~' || c == '*' || c == '?' || c == '|' || c == '&'
|| c == '/') {
sb.append('\\');
}
sb.append(c);
}
return sb.toString();
}
}
@@ -19,7 +19,6 @@ import co.elastic.clients.elasticsearch.core.GetResponse;
import co.elastic.clients.elasticsearch.core.MgetResponse;
import co.elastic.clients.elasticsearch.core.explain.ExplanationDetail;
import co.elastic.clients.elasticsearch.core.get.GetResult;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.NestedIdentity;
import co.elastic.clients.json.JsonData;
@@ -73,7 +72,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,
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null,
searchDocument -> null, jsonpMapper));
});
@@ -101,12 +100,12 @@ final class DocumentAdapters {
EntityAsMap hitFieldsAsMap = fromFields.apply(hit.fields());
Map<String, List<Object>> documentFields = new LinkedHashMap<>();
hitFieldsAsMap.forEach((key, value) -> {
if (value instanceof List) {
hitFieldsAsMap.entrySet().forEach(entry -> {
if (entry.getValue() instanceof List) {
// noinspection unchecked
documentFields.put(key, (List<Object>) value);
documentFields.put(entry.getKey(), (List<Object>) entry.getValue());
} else {
documentFields.put(key, Collections.singletonList(value));
documentFields.put(entry.getKey(), Collections.singletonList(entry.getValue()));
}
});
@@ -117,7 +116,8 @@ final class DocumentAdapters {
} else {
if (source instanceof EntityAsMap) {
document = Document.from((EntityAsMap) source);
} else if (source instanceof JsonData jsonData) {
} else if (source instanceof JsonData) {
JsonData jsonData = (JsonData) source;
document = Document.from(jsonData.to(EntityAsMap.class));
} else {
@@ -139,23 +139,7 @@ final class DocumentAdapters {
float score = hit.score() != null ? hit.score().floatValue() : Float.NaN;
return new SearchDocumentAdapter(document, score, hit.sort().stream().map(TypeUtils::toString).toArray(),
documentFields, highlightFields, innerHits, nestedMetaData, explanation, matchedQueries, hit.routing());
}
public static SearchDocument from(CompletionSuggestOption<EntityAsMap> completionSuggestOption) {
Document document = completionSuggestOption.source() != null ? Document.from(completionSuggestOption.source())
: Document.create();
document.setIndex(completionSuggestOption.index());
if (completionSuggestOption.id() != null) {
document.setId(completionSuggestOption.id());
}
float score = completionSuggestOption.score() != null ? completionSuggestOption.score().floatValue() : Float.NaN;
return new SearchDocumentAdapter(document, score, new Object[] {}, Collections.emptyMap(), Collections.emptyMap(),
Collections.emptyMap(), null, null, null, completionSuggestOption.routing());
}
documentFields, highlightFields, innerHits, nestedMetaData, explanation, matchedQueries, hit.routing()); }
@Nullable
private static Explanation from(@Nullable co.elastic.clients.elasticsearch.core.explain.Explanation explanation) {
@@ -18,61 +18,45 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.data.elasticsearch.core.AggregationsContainer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* AggregationsContainer implementation for the Elasticsearch aggregations.
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.4
*/
public class ElasticsearchAggregations implements AggregationsContainer<List<ElasticsearchAggregation>> {
private final List<ElasticsearchAggregation> aggregations;
private final Map<String, ElasticsearchAggregation> aggregationsAsMap;
public ElasticsearchAggregations(Map<String, Aggregate> aggregations) {
public ElasticsearchAggregations(List<ElasticsearchAggregation> aggregations) {
Assert.notNull(aggregations, "aggregations must not be null");
aggregationsAsMap = new HashMap<>();
aggregations.forEach((name, aggregate) -> aggregationsAsMap //
.put(name, new ElasticsearchAggregation(new Aggregation(name, aggregate))));
this.aggregations = aggregations;
}
this.aggregations = new ArrayList<>(aggregationsAsMap.values());
/**
* convenience constructor taking a map as it is returned from the new Elasticsearch client.
*
* @param aggregationsMap aggregate map
*/
public ElasticsearchAggregations(Map<String, Aggregate> aggregationsMap) {
Assert.notNull(aggregationsMap, "aggregationsMap must not be null");
aggregations = new ArrayList<>(aggregationsMap.size());
aggregationsMap
.forEach((name, aggregate) -> aggregations.add(new ElasticsearchAggregation(new Aggregation(name, aggregate))));
}
@Override
public List<ElasticsearchAggregation> aggregations() {
return aggregations;
}
/**
* @return the {@link ElasticsearchAggregation}s keyed by aggregation name.
*/
public Map<String, ElasticsearchAggregation> aggregationsAsMap() {
return aggregationsAsMap;
}
/**
* Returns the aggregation that is associated with the specified name.
*
* @param name the name of the aggregation
* @return the aggregation or {@literal null} if not found
*/
@Nullable
public ElasticsearchAggregation get(String name) {
Assert.notNull(name, "name must not be null");
return aggregationsAsMap.get(name);
}
}
@@ -1,47 +0,0 @@
/*
* Copyright 2022-2023 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 org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* @author Peter-Josef Meisch
* @since 5.0
*/
public class ElasticsearchClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ElasticsearchClientFactoryBean.class);
setConfigurations(element, builder);
return getSourcedBeanDefinition(builder, element, parserContext);
}
private void setConfigurations(Element element, BeanDefinitionBuilder builder) {
builder.addPropertyValue("hosts", element.getAttribute("hosts"));
}
private AbstractBeanDefinition getSourcedBeanDefinition(BeanDefinitionBuilder builder, Element source,
ParserContext context) {
AbstractBeanDefinition definition = builder.getBeanDefinition();
definition.setSource(context.extractSource(source));
return definition;
}
}
@@ -1,97 +0,0 @@
/*
* Copyright 2022-2023 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.ElasticsearchClient;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* ElasticsearchClientFactoryBean
*
* @author Peter-Josef Meisch
* @since 5.0
*/
public class ElasticsearchClientFactoryBean
implements FactoryBean<ElasticsearchClient>, InitializingBean, DisposableBean {
private static final Log LOGGER = LogFactory.getLog(ElasticsearchClientFactoryBean.class);
private @Nullable AutoCloseableElasticsearchClient client;
private String hosts = "http://localhost:9200";
static final String COMMA = ",";
@Override
public void destroy() {
try {
LOGGER.info("Closing elasticSearch client");
if (client != null) {
client.close();
}
} catch (final Exception e) {
LOGGER.error("Error closing ElasticSearch client: ", e);
}
}
@Override
public void afterPropertiesSet() throws Exception {
buildClient();
}
@Override
public ElasticsearchClient getObject() {
if (client == null) {
throw new FactoryBeanNotInitializedException();
}
return client;
}
@Override
public Class<?> getObjectType() {
return ElasticsearchClient.class;
}
@Override
public boolean isSingleton() {
return false;
}
protected void buildClient() throws Exception {
Assert.hasText(hosts, "[Assertion Failed] At least one host must be set.");
var clientConfiguration = ClientConfiguration.builder().connectedTo(hosts).build();
client = (AutoCloseableElasticsearchClient) ElasticsearchClients.createImperative(clientConfiguration);
}
public void setHosts(String hosts) {
this.hosts = hosts;
}
public String getHosts() {
return this.hosts;
}
}
@@ -19,7 +19,6 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.TransportOptions;
import co.elastic.clients.transport.Version;
import co.elastic.clients.transport.rest_client.RestClientOptions;
import co.elastic.clients.transport.rest_client.RestClientTransport;
@@ -29,7 +28,6 @@ import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -43,19 +41,19 @@ import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
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;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
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.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Utility class to create the different Elasticsearch clients
@@ -228,20 +226,14 @@ public final class ElasticsearchClients {
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurer instanceof ElasticsearchHttpClientConfigurationCallback restClientConfigurationCallback) {
if (clientConfigurer instanceof ElasticsearchClientConfigurationCallback) {
ElasticsearchClientConfigurationCallback restClientConfigurationCallback = (ElasticsearchClientConfigurationCallback) clientConfigurer;
clientBuilder = restClientConfigurationCallback.configure(clientBuilder);
}
}
return clientBuilder;
});
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurationCallback : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurationCallback instanceof ElasticsearchRestClientConfigurationCallback configurationCallback) {
builder = configurationCallback.configure(builder);
}
}
return builder;
}
@@ -250,27 +242,12 @@ public final class ElasticsearchClients {
TransportOptions.Builder transportOptionsBuilder = transportOptions != null ? transportOptions.toBuilder()
: new RestClientOptions(RequestOptions.DEFAULT).toBuilder();
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 (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
transportOptionsBuilder.addHeader(header, jsonContentType.toString());
}
};
setHeaderIfNotPresent.accept("Content-Type");
setHeaderIfNotPresent.accept("Accept");
TransportOptions transportOptionsWithHeader = transportOptionsBuilder
.addHeader(X_SPRING_DATA_ELASTICSEARCH_CLIENT, clientType).build();
return new RestClientTransport(restClient, new JacksonJsonpMapper(), transportOptionsWithHeader);
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper(),
transportOptionsWithHeader);
return transport;
}
private static List<String> formattedHosts(List<InetSocketAddress> hosts, boolean useSsl) {
@@ -290,9 +267,7 @@ public final class ElasticsearchClients {
*
* @see ClientLogger
* @since 4.4
* @deprecated since 5.0
*/
@Deprecated
private static class HttpLoggingInterceptor implements HttpResponseInterceptor, HttpRequestInterceptor {
@Override
@@ -310,9 +285,9 @@ public final class ElasticsearchClients {
+ ((header.getName().equals("Authorization")) ? ": *****" : ": " + header.getValue()))
.collect(Collectors.joining(", ", "[", "]"));
if (request instanceof HttpEntityEnclosingRequest entityRequest
&& ((HttpEntityEnclosingRequest) request).getEntity() != null) {
if (request instanceof HttpEntityEnclosingRequest && ((HttpEntityEnclosingRequest) request).getEntity() != null) {
HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request;
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
entity.writeTo(buffer);
@@ -339,9 +314,9 @@ public final class ElasticsearchClients {
+ ((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
// no way of logging the body, in this callback, it is not read yset, later there is no callback possibility in
// RestClient or RestClientTransport
ClientLogger.logRawResponse(logId, response.getStatusLine().getStatusCode(), headers);
ClientLogger.logRawResponse(logId, HttpStatus.resolve(response.getStatusLine().getStatusCode()), headers);
}
}
@@ -362,7 +337,7 @@ public final class ElasticsearchClients {
public void process(HttpRequest request, HttpContext context) {
HttpHeaders httpHeaders = headersSupplier.get();
if (httpHeaders != null && !httpHeaders.isEmpty()) {
if (httpHeaders != null && httpHeaders != HttpHeaders.EMPTY) {
Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader);
}
}
@@ -370,37 +345,37 @@ public final class ElasticsearchClients {
/**
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
* the Elasticsearch RestClient's Http client with a {@link HttpAsyncClientBuilder}
* the RestClient with a {@link HttpAsyncClientBuilder}
*
* @since 4.4
*/
public interface ElasticsearchHttpClientConfigurationCallback
public interface ElasticsearchClientConfigurationCallback
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
static ElasticsearchHttpClientConfigurationCallback from(
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> httpClientBuilderCallback) {
static ElasticsearchClientConfigurationCallback from(
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> clientBuilderCallback) {
Assert.notNull(httpClientBuilderCallback, "httpClientBuilderCallback must not be null");
Assert.notNull(clientBuilderCallback, "clientBuilderCallback must not be null");
return httpClientBuilderCallback::apply;
// noinspection NullableProblems
return clientBuilderCallback::apply;
}
}
/**
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
* the RestClient client with a {@link RestClientBuilder}
* the ReactiveElasticsearchClient with a {@link WebClient}
*
* @since 5.0
* @since 4.4
*/
public interface ElasticsearchRestClientConfigurationCallback
extends ClientConfiguration.ClientConfigurationCallback<RestClientBuilder> {
public interface WebClientConfigurationCallback extends ClientConfiguration.ClientConfigurationCallback<WebClient> {
static ElasticsearchRestClientConfigurationCallback from(
Function<RestClientBuilder, RestClientBuilder> restClientBuilderCallback) {
static WebClientConfigurationCallback from(Function<WebClient, WebClient> webClientCallback) {
Assert.notNull(restClientBuilderCallback, "restClientBuilderCallback must not be null");
Assert.notNull(webClientCallback, "webClientCallback must not be null");
return restClientBuilderCallback::apply;
// noinspection NullableProblems
return webClientCallback::apply;
}
}
}
@@ -42,7 +42,7 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
*
* @return configuration, must not be {@literal null}
*/
@Bean(name="elasticsearchClientConfiguration")
@Bean
public abstract ClientConfiguration clientConfiguration();
/**
@@ -52,7 +52,7 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
* @return RestClient
*/
@Bean
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
public RestClient restClient(ClientConfiguration clientConfiguration) {
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
@@ -31,6 +31,7 @@ import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.http.HttpStatus;
/**
* Simple {@link PersistenceExceptionTranslator} for Elasticsearch. Convert the given runtime exception to an
@@ -71,18 +72,17 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
return new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict", ex);
}
if (ex instanceof ElasticsearchException elasticsearchException) {
if (ex instanceof ElasticsearchException) {
ElasticsearchException elasticsearchException = (ElasticsearchException) ex;
ErrorResponse response = elasticsearchException.response();
var errorType = response.error().type();
var errorReason = response.error().reason() != null ? response.error().reason() : "undefined reason";
if (response.status() == 404 && "index_not_found_exception".equals(errorType)) {
if (response.status() == HttpStatus.NOT_FOUND.value()
&& "index_not_found_exception".equals(response.error().type())) {
// noinspection RegExpRedundantEscape
Pattern pattern = Pattern.compile(".*no such index \\[(.*)\\]");
String index = "";
Matcher matcher = pattern.matcher(errorReason);
Matcher matcher = pattern.matcher(response.error().reason());
if (matcher.matches()) {
index = matcher.group(1);
}
@@ -90,8 +90,8 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
}
String body = JsonUtils.toJson(response, jsonpMapper);
if (errorType != null && errorType.contains("validation_exception")) {
return new DataIntegrityViolationException(errorReason);
if (response.error().type().contains("validation_exception")) {
return new DataIntegrityViolationException(response.error().reason());
}
return new UncategorizedElasticsearchException(ex.getMessage(), response.status(), body, ex);
@@ -109,7 +109,9 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
Integer status = null;
String message = null;
if (exception instanceof ResponseException responseException) {
if (exception instanceof ResponseException) {
ResponseException responseException = (ResponseException) exception;
status = responseException.getResponse().getStatusLine().getStatusCode();
message = responseException.getMessage();
} else if (exception.getCause() != null) {
@@ -27,7 +27,6 @@ import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.transport.Version;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -80,17 +79,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
private final ElasticsearchExceptionTranslator exceptionTranslator;
// region _initialization
public ElasticsearchTemplate(ElasticsearchClient client) {
Assert.notNull(client, "client must not be null");
this.client = client;
this.jsonpMapper = client._transport().jsonpMapper();
requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper);
responseConverter = new ResponseConverter(jsonpMapper);
exceptionTranslator = new ElasticsearchExceptionTranslator(jsonpMapper);
}
public ElasticsearchTemplate(ElasticsearchClient client, ElasticsearchConverter elasticsearchConverter) {
super(elasticsearchConverter);
@@ -285,18 +273,19 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
// endregion
@Override
public String getClusterVersion() {
protected String getClusterVersion() {
return execute(client -> client.info().version().number());
}
@Override
public String getVendor() {
protected String getVendor() {
return "Elasticsearch";
}
@Override
public String getRuntimeLibraryVersion() {
return Version.VERSION != null ? Version.VERSION.toString() : "0.0.0.?";
protected String getRuntimeLibraryVersion() {
return Version.VERSION.toString();
}
// region search operations
@@ -343,7 +332,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
}
@Override
public <T> SearchScrollHits<T> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
protected <T> SearchScrollHits<T> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
@@ -356,7 +345,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
}
@Override
public <T> SearchScrollHits<T> searchScrollContinue(String scrollId, long scrollTimeInMillis, Class<T> clazz,
protected <T> SearchScrollHits<T> searchScrollContinue(String scrollId, long scrollTimeInMillis, Class<T> clazz,
IndexCoordinates index) {
Assert.notNull(scrollId, "scrollId must not be null");
@@ -379,7 +368,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
}
@Override
public void searchScrollClear(List<String> scrollIds) {
protected void searchScrollClear(List<String> scrollIds) {
Assert.notNull(scrollIds, "scrollIds must not be null");
@@ -487,30 +476,17 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
/**
* value class combining the information needed for a single query in a multisearch request.
*/
record MultiSearchQueryParameter(Query query, Class<?> clazz, IndexCoordinates index) {
static class MultiSearchQueryParameter {
final Query query;
final Class<?> clazz;
final IndexCoordinates index;
public MultiSearchQueryParameter(Query query, Class<?> clazz, IndexCoordinates index) {
this.query = query;
this.clazz = clazz;
this.index = index;
}
}
@Override
public String openPointInTime(IndexCoordinates index, Duration keepAlive, Boolean ignoreUnavailable) {
Assert.notNull(index, "index must not be null");
Assert.notNull(keepAlive, "keepAlive must not be null");
Assert.notNull(ignoreUnavailable, "ignoreUnavailable must not be null");
var request = requestConverter.searchOpenPointInTimeRequest(index, keepAlive, ignoreUnavailable);
return execute(client -> client.openPointInTime(request)).id();
}
@Override
public Boolean closePointInTime(String pit) {
Assert.notNull(pit, "pit must not be null");
ClosePointInTimeRequest request = requestConverter.searchClosePointInTime(pit);
var response = execute(client -> client.closePointInTime(request));
return response.succeeded();
}
// endregion
// region client callback
@@ -18,7 +18,6 @@ package org.springframework.data.elasticsearch.client.elc;
import static org.springframework.util.StringUtils.*;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import java.util.List;
@@ -57,8 +56,7 @@ import org.springframework.util.Assert;
* @author Peter-Josef Meisch
* @since 4.4
*/
public class IndicesTemplate extends ChildTemplate<ElasticsearchTransport, ElasticsearchIndicesClient>
implements IndexOperations {
public class IndicesTemplate extends ChildTemplate<ElasticsearchIndicesClient> implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(IndicesTemplate.class);
@@ -189,6 +187,7 @@ public class IndicesTemplate extends ChildTemplate<ElasticsearchTransport, Elast
Assert.notNull(clazz, "clazz must not be null");
// load mapping specified in Mapping annotation if present
// noinspection DuplicatedCode
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
if (mappingAnnotation != null) {
@@ -278,22 +277,12 @@ public class IndicesTemplate extends ChildTemplate<ElasticsearchTransport, Elast
@Override
public Map<String, Set<AliasData>> getAliases(String... aliasNames) {
Assert.notNull(aliasNames, "aliasNames must not be null");
GetAliasRequest getAliasRequest = requestConverter.indicesGetAliasRequest(aliasNames, null);
var getAliasResponse = execute(client -> client.getAlias(getAliasRequest));
return responseConverter.indicesGetAliasData(getAliasResponse);
throw new UnsupportedOperationException("not implemented");
}
@Override
public Map<String, Set<AliasData>> getAliasesForIndex(String... indexNames) {
Assert.notNull(indexNames, "indexNames must not be null");
GetAliasRequest getAliasRequest = requestConverter.indicesGetAliasRequest(null, indexNames);
var getAliasResponse = execute(client -> client.getAlias(getAliasRequest));
return responseConverter.indicesGetAliasData(getAliasResponse);
throw new UnsupportedOperationException("not implemented");
}
@Override
@@ -20,11 +20,9 @@ import jakarta.json.stream.JsonGenerator;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
/**
* @author Peter-Josef Meisch
@@ -43,28 +41,13 @@ final class JsonUtils {
JsonGenerator generator = mapper.jsonProvider().createGenerator(baos);
mapper.serialize(object, generator);
generator.close();
String json = "{}";
String jsonMapping = "{}";
try {
json = baos.toString("UTF-8");
jsonMapping = baos.toString("UTF-8");
} catch (UnsupportedEncodingException e) {
LOGGER.warn("could not read json", e);
}
return json;
return jsonMapping;
}
@Nullable
public static String queryToJson(@Nullable co.elastic.clients.elasticsearch._types.query_dsl.Query query, JsonpMapper mapper) {
if (query == null) {
return null;
}
var baos = new ByteArrayOutputStream();
var generator = mapper.jsonProvider().createGenerator(baos);
query.serialize(generator, mapper);
generator.close();
return baos.toString(StandardCharsets.UTF_8);
}
}
@@ -15,12 +15,10 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.search.FieldCollapse;
import co.elastic.clients.elasticsearch.core.search.Suggester;
import co.elastic.clients.json.JsonData;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -28,6 +26,7 @@ import java.util.List;
import java.util.Map;
import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
import org.springframework.data.elasticsearch.core.query.ScriptedField;
import org.springframework.lang.Nullable;
@@ -36,7 +35,6 @@ import org.springframework.lang.Nullable;
* Elasticsearch Client library.
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.4
*/
public class NativeQuery extends BaseQuery {
@@ -48,9 +46,7 @@ public class NativeQuery extends BaseQuery {
@Nullable private Suggester suggester;
@Nullable private FieldCollapse fieldCollapse;
private List<ScriptedField> scriptedFields = Collections.emptyList();
private List<SortOptions> sortOptions = Collections.emptyList();
private Map<String, JsonData> searchExtensions = Collections.emptyMap();
private List<RescorerQuery> rescorerQueries = Collections.emptyList();
public NativeQuery(NativeQueryBuilder builder) {
super(builder);
@@ -60,8 +56,7 @@ public class NativeQuery extends BaseQuery {
this.suggester = builder.getSuggester();
this.fieldCollapse = builder.getFieldCollapse();
this.scriptedFields = builder.getScriptedFields();
this.sortOptions = builder.getSortOptions();
this.searchExtensions = builder.getSearchExtensions();
this.rescorerQueries = builder.getRescorerQueries();
}
public NativeQuery(@Nullable Query query) {
@@ -100,11 +95,8 @@ public class NativeQuery extends BaseQuery {
return scriptedFields;
}
public List<SortOptions> getSortOptions() {
return sortOptions;
}
public Map<String, JsonData> getSearchExtensions() {
return searchExtensions;
@Override
public List<RescorerQuery> getRescorerQueries() {
return rescorerQueries;
}
}
@@ -15,29 +15,26 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.search.FieldCollapse;
import co.elastic.clients.elasticsearch.core.search.Suggester;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.util.ObjectBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
import org.springframework.data.elasticsearch.core.query.ScriptedField;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.4
*/
public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQueryBuilder> {
@@ -48,8 +45,7 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
@Nullable private Suggester suggester;
@Nullable private FieldCollapse fieldCollapse;
private final List<ScriptedField> scriptedFields = new ArrayList<>();
private List<SortOptions> sortOptions = new ArrayList<>();
private Map<String, JsonData> searchExtensions = new LinkedHashMap<>();
private List<RescorerQuery> rescorerQueries = new ArrayList<>();
public NativeQueryBuilder() {}
@@ -81,12 +77,8 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
return scriptedFields;
}
public List<SortOptions> getSortOptions() {
return sortOptions;
}
public Map<String, JsonData> getSearchExtensions() {
return this.searchExtensions;
public List<RescorerQuery> getRescorerQueries() {
return rescorerQueries;
}
public NativeQueryBuilder withQuery(Query query) {
@@ -97,23 +89,16 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
return this;
}
public NativeQueryBuilder withQuery(Function<Query.Builder, ObjectBuilder<Query>> fn) {
Assert.notNull(fn, "fn must not be null");
return withQuery(fn.apply(new Query.Builder()).build());
}
public NativeQueryBuilder withFilter(@Nullable Query filter) {
this.filter = filter;
return this;
}
public NativeQueryBuilder withFilter(Function<Query.Builder, ObjectBuilder<Query>> fn) {
public NativeQueryBuilder withQuery(Function<Query.Builder, ObjectBuilder<Query>> fn) {
Assert.notNull(fn, "fn must not be null");
return withFilter(fn.apply(new Query.Builder()).build());
return withQuery(fn.apply(new Query.Builder()).build());
}
public NativeQueryBuilder withAggregation(String name, Aggregation aggregation) {
@@ -143,48 +128,11 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
return this;
}
public NativeQueryBuilder withSort(List<SortOptions> values) {
public NativeQueryBuilder withResorerQuery(RescorerQuery resorerQuery) {
Assert.notEmpty(values, "values must not be empty");
sortOptions.clear();
sortOptions.addAll(values);
Assert.notNull(resorerQuery, "resorerQuery must not be null");
return this;
}
public NativeQueryBuilder withSort(SortOptions value, SortOptions... values) {
Assert.notNull(value, "value must not be null");
sortOptions.add(value);
if (values.length > 0) {
sortOptions.addAll(Arrays.asList(values));
}
return this;
}
public NativeQueryBuilder withSort(Function<SortOptions.Builder, ObjectBuilder<SortOptions>> fn) {
Assert.notNull(fn, "fn must not be null");
withSort(fn.apply(new SortOptions.Builder()).build());
return this;
}
public NativeQueryBuilder withSearchExtension(String key, JsonData value) {
Assert.notNull(key, "key must not be null");
Assert.notNull(value, "value must not be null");
searchExtensions.put(key, value);
return this;
}
public NativeQueryBuilder withSearchExtensions(Map<String, JsonData> searchExtensions) {
Assert.notNull(searchExtensions, "searchExtensions must not be null");
searchExtensions.putAll(searchExtensions);
this.rescorerQueries.add(resorerQuery);
return this;
}
@@ -17,7 +17,6 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.ApiClient;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.transport.Transport;
import reactor.core.publisher.Flux;
import org.reactivestreams.Publisher;
@@ -30,17 +29,18 @@ import org.springframework.util.Assert;
* @author Peter-Josef Meisch
* @since 4.4
*/
public class ReactiveChildTemplate<T extends Transport, CLIENT extends ApiClient<T, CLIENT>> {
public class ReactiveChildTemplate<CLIENT extends ApiClient> {
protected final CLIENT client;
protected final ElasticsearchConverter elasticsearchConverter;
protected final RequestConverter requestConverter;
protected final ResponseConverter responseConverter;
private final JsonpMapper jsonpMapper;
protected final ElasticsearchExceptionTranslator exceptionTranslator;
public ReactiveChildTemplate(CLIENT client, ElasticsearchConverter elasticsearchConverter) {
this.client = client;
this.elasticsearchConverter = elasticsearchConverter;
JsonpMapper jsonpMapper = client._transport().jsonpMapper();
jsonpMapper = client._transport().jsonpMapper();
requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper);
responseConverter = new ResponseConverter(jsonpMapper);
exceptionTranslator = new ElasticsearchExceptionTranslator(jsonpMapper);
@@ -17,19 +17,17 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch.cluster.HealthRequest;
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
import co.elastic.clients.transport.ElasticsearchTransport;
import reactor.core.publisher.Mono;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveClusterOperations;
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
/**
* @author Peter-Josef Meisch
* @since 4.4
*/
public class ReactiveClusterTemplate
extends ReactiveChildTemplate<ElasticsearchTransport, ReactiveElasticsearchClusterClient>
public class ReactiveClusterTemplate extends ReactiveChildTemplate<ReactiveElasticsearchClusterClient>
implements ReactiveClusterOperations {
public ReactiveClusterTemplate(ReactiveElasticsearchClusterClient client,
@@ -276,48 +276,6 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
return clearScroll(fn.apply(new ClearScrollRequest.Builder()).build());
}
/**
* @since 5.0
*/
public Mono<OpenPointInTimeResponse> openPointInTime(OpenPointInTimeRequest request) {
Assert.notNull(request, "request must not be null");
return Mono.fromFuture(transport.performRequestAsync(request, OpenPointInTimeRequest._ENDPOINT, transportOptions));
}
/**
* @since 5.0
*/
public Mono<OpenPointInTimeResponse> openPointInTime(
Function<OpenPointInTimeRequest.Builder, ObjectBuilder<OpenPointInTimeRequest>> fn) {
Assert.notNull(fn, "fn must not be null");
return openPointInTime(fn.apply(new OpenPointInTimeRequest.Builder()).build());
}
/**
* @since 5.0
*/
public Mono<ClosePointInTimeResponse> closePointInTime(ClosePointInTimeRequest request) {
Assert.notNull(request, "request must not be null");
return Mono.fromFuture(transport.performRequestAsync(request, ClosePointInTimeRequest._ENDPOINT, transportOptions));
}
/**
* @since 5.0
*/
public Mono<ClosePointInTimeResponse> closePointInTime(
Function<ClosePointInTimeRequest.Builder, ObjectBuilder<ClosePointInTimeRequest>> fn) {
Assert.notNull(fn, "fn must not be null");
return closePointInTime(fn.apply(new ClosePointInTimeRequest.Builder()).build());
}
// endregion
}
@@ -41,7 +41,7 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
*
* @return configuration, must not be {@literal null}
*/
@Bean(name="elasticsearchClientConfiguration")
@Bean
public abstract ClientConfiguration clientConfiguration();
/**
@@ -51,7 +51,7 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
* @return RestClient
*/
@Bean
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
public RestClient restClient(ClientConfiguration clientConfiguration) {
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
@@ -15,7 +15,6 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import static co.elastic.clients.util.ApiTypeHelper.*;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
import co.elastic.clients.elasticsearch._types.Result;
@@ -30,18 +29,18 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import java.time.Duration;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.reactivestreams.Publisher;
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.client.erhlc.ReactiveClusterOperations;
import org.springframework.data.elasticsearch.client.util.ScrollState;
import org.springframework.data.elasticsearch.core.AbstractReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.AggregationContainer;
@@ -49,6 +48,7 @@ import org.springframework.data.elasticsearch.core.IndexedObjectInformation;
import org.springframework.data.elasticsearch.core.MultiGetItem;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ReactiveIndexOperations;
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;
@@ -80,7 +80,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
private final JsonpMapper jsonpMapper;
private final ElasticsearchExceptionTranslator exceptionTranslator;
public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, ElasticsearchConverter converter) {
protected ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, ElasticsearchConverter converter) {
super(converter);
Assert.notNull(client, "client must not be null");
@@ -115,7 +115,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return entitiesPublisher //
.flatMapMany(entities -> Flux.fromIterable(entities) //
.concatMap(entity -> maybeCallbackBeforeConvert(entity, index)) //
.concatMap(entity -> maybeCallBeforeConvert(entity, index)) //
).collectList() //
.map(Entities::new) //
.flatMapMany(entities -> {
@@ -131,7 +131,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
BulkResponseItem response = indexAndResponse.getT2();
updateIndexedObject(savedEntity, IndexedObjectInformation.of(response.id(), response.seqNo(),
response.primaryTerm(), response.version()));
return maybeCallbackAfterSave(savedEntity, index);
return maybeCallAfterSave(savedEntity, index);
});
});
}
@@ -177,9 +177,8 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return Mono.from(execute( //
(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"))
.flatMap(response -> (response.task() == null) ? Mono.error(
new UnsupportedBackendOperation("ElasticsearchClient did not return a task id on submit request"))
: Mono.just(response.task()));
}
@@ -328,24 +327,26 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
Time scrollTimeout = searchRequest.scroll() != null ? searchRequest.scroll() : Time.of(t -> t.time("1m"));
Flux<ResponseBody<EntityAsMap>> searchResponses = Flux.usingWhen(Mono.fromSupplier(ScrollState::new), //
state -> Mono
.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client -> client.search(searchRequest,
EntityAsMap.class))) //
.expand(entityAsMapSearchResponse -> {
state -> {
return Mono
.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client1 -> client1
.search(searchRequest, EntityAsMap.class))) //
.expand(entityAsMapSearchResponse -> {
state.updateScrollId(entityAsMapSearchResponse.scrollId());
state.updateScrollId(entityAsMapSearchResponse.scrollId());
if (entityAsMapSearchResponse.hits() == null
|| CollectionUtils.isEmpty(entityAsMapSearchResponse.hits().hits())) {
return Mono.empty();
}
if (entityAsMapSearchResponse.hits() == null
|| CollectionUtils.isEmpty(entityAsMapSearchResponse.hits().hits())) {
return Mono.empty();
}
return Mono.from(execute((ClientCallback<Publisher<ScrollResponse<EntityAsMap>>>) client1 -> {
ScrollRequest scrollRequest = ScrollRequest
.of(sr -> sr.scrollId(state.getScrollId()).scroll(scrollTimeout));
return client1.scroll(scrollRequest, EntityAsMap.class);
}));
}),
return Mono.from(execute((ClientCallback<Publisher<ScrollResponse<EntityAsMap>>>) client1 -> {
ScrollRequest scrollRequest = ScrollRequest
.of(sr -> sr.scrollId(state.getScrollId()).scroll(scrollTimeout));
return client1.scroll(scrollRequest, EntityAsMap.class);
}));
});
},
this::cleanupScroll, (state, ex) -> cleanupScroll(state), this::cleanupScroll);
return searchResponses.flatMapIterable(entityAsMapSearchResponse -> entityAsMapSearchResponse.hits().hits())
@@ -354,10 +355,6 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
private Publisher<?> cleanupScroll(ScrollState state) {
if (state.getScrollIds().isEmpty()) {
return Mono.empty();
}
return execute((ClientCallback<Publisher<ClearScrollResponse>>) client -> client
.clearScroll(ClearScrollRequest.of(csr -> csr.scrollId(state.getScrollIds()))));
}
@@ -413,49 +410,21 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
});
}
@Override
public Mono<String> openPointInTime(IndexCoordinates index, Duration keepAlive, Boolean ignoreUnavailable) {
Assert.notNull(index, "index must not be null");
Assert.notNull(keepAlive, "keepAlive must not be null");
Assert.notNull(ignoreUnavailable, "ignoreUnavailable must not be null");
var request = requestConverter.searchOpenPointInTimeRequest(index, keepAlive, ignoreUnavailable);
return Mono
.from(execute((ClientCallback<Publisher<OpenPointInTimeResponse>>) client -> client.openPointInTime(request)))
.map(OpenPointInTimeResponse::id);
}
@Override
public Mono<Boolean> closePointInTime(String pit) {
Assert.notNull(pit, "pit must not be null");
ClosePointInTimeRequest request = requestConverter.searchClosePointInTime(pit);
return Mono
.from(execute((ClientCallback<Publisher<ClosePointInTimeResponse>>) client -> client.closePointInTime(request)))
.map(ClosePointInTimeResponse::succeeded);
}
// endregion
@Override
public Mono<String> getVendor() {
protected Mono<String> getVendor() {
return Mono.just("Elasticsearch");
}
@Override
public Mono<String> getRuntimeLibraryVersion() {
protected Mono<String> getRuntimeLibraryVersion() {
return Mono.just(Version.VERSION != null ? Version.VERSION.toString() : "null");
}
@Override
public Mono<String> getClusterVersion() {
return Mono.from(execute((ReactiveElasticsearchClient reactiveElasticsearchClient) -> {
try (var ignored = DANGEROUS_disableRequiredPropertiesCheck(true)) {
return reactiveElasticsearchClient.info();
}
})).map(infoResponse -> infoResponse.version().number());
protected Mono<String> getClusterVersion() {
return Mono.from(execute(ReactiveElasticsearchClient::info)).map(infoResponse -> infoResponse.version().number());
}
@Override
@@ -512,6 +481,16 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return new ReactiveClusterTemplate(client.cluster(), converter);
}
@Override
public Flux<Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType) {
throw new UnsupportedOperationException("not implemented");
}
@Override
public Flux<Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index) {
throw new UnsupportedOperationException("not implemented");
}
@Override
public Query matchAllQuery() {
return NativeQuery.builder().withQuery(QueryBuilders.matchAllQueryAsQuery()).build();
@@ -19,7 +19,6 @@ import static org.springframework.util.StringUtils.*;
import co.elastic.clients.elasticsearch._types.AcknowledgedResponseBase;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -54,8 +53,7 @@ import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
*/
public class ReactiveIndicesTemplate
extends ReactiveChildTemplate<ElasticsearchTransport, ReactiveElasticsearchIndicesClient>
public class ReactiveIndicesTemplate extends ReactiveChildTemplate<ReactiveElasticsearchIndicesClient>
implements ReactiveIndexOperations {
@Nullable private final Class<?> boundClass;
@@ -154,8 +152,7 @@ public class ReactiveIndicesTemplate
@Override
public Mono<Void> refresh() {
RefreshRequest refreshRequest = requestConverter.indicesRefreshRequest(getIndexCoordinates());
return Mono.from(execute(client -> client.refresh(refreshRequest))).then();
return Mono.from(execute(client1 -> client1.refresh())).then();
}
@Override
@@ -15,12 +15,8 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.searchType;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.slices;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.time;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.timeStringMs;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.toFloat;
import static org.springframework.util.CollectionUtils.isEmpty;
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.FieldValue;
@@ -38,14 +34,12 @@ import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch._types.query_dsl.Like;
import co.elastic.clients.elasticsearch.cluster.HealthRequest;
import co.elastic.clients.elasticsearch.core.BulkRequest;
import co.elastic.clients.elasticsearch.core.ClosePointInTimeRequest;
import co.elastic.clients.elasticsearch.core.DeleteByQueryRequest;
import co.elastic.clients.elasticsearch.core.DeleteRequest;
import co.elastic.clients.elasticsearch.core.GetRequest;
import co.elastic.clients.elasticsearch.core.IndexRequest;
import co.elastic.clients.elasticsearch.core.MgetRequest;
import co.elastic.clients.elasticsearch.core.MsearchRequest;
import co.elastic.clients.elasticsearch.core.OpenPointInTimeRequest;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.UpdateByQueryRequest;
import co.elastic.clients.elasticsearch.core.UpdateRequest;
@@ -54,21 +48,10 @@ import co.elastic.clients.elasticsearch.core.bulk.CreateOperation;
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.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.Rescore;
import co.elastic.clients.elasticsearch.core.search.SourceConfig;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest;
import co.elastic.clients.elasticsearch.indices.ExistsRequest;
import co.elastic.clients.elasticsearch.indices.GetAliasRequest;
import co.elastic.clients.elasticsearch.indices.GetIndexRequest;
import co.elastic.clients.elasticsearch.indices.GetIndicesSettingsRequest;
import co.elastic.clients.elasticsearch.indices.GetMappingRequest;
import co.elastic.clients.elasticsearch.indices.IndexSettings;
import co.elastic.clients.elasticsearch.indices.PutMappingRequest;
import co.elastic.clients.elasticsearch.indices.RefreshRequest;
import co.elastic.clients.elasticsearch.indices.UpdateAliasesRequest;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.elasticsearch.indices.update_aliases.Action;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.JsonpDeserializer;
@@ -78,7 +61,6 @@ import jakarta.json.stream.JsonParser;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
@@ -107,19 +89,7 @@ import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
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.ScriptData;
import org.springframework.data.elasticsearch.core.query.SourceFilter;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.data.elasticsearch.core.reindex.Remote;
import org.springframework.data.elasticsearch.support.DefaultStringObjectMap;
@@ -131,9 +101,6 @@ import org.springframework.util.StringUtils;
* Class to create Elasticsearch request and request builders.
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @author cdalxndr
* @author scoobyzhang
* @since 4.4
*/
class RequestConverter {
@@ -177,12 +144,14 @@ class RequestConverter {
createRequestBuilder.index(indexCoordinates.getIndexName());
// note: the new client does not support the index.storeType anymore
createRequestBuilder.settings(IndexSettings.of(b -> b //
.withJson(new StringReader(Document.from(settings).toJson()))));
String settingsJson = Document.from(settings).toJson();
IndexSettings indexSettings = fromJson(settingsJson, IndexSettings._DESERIALIZER);
createRequestBuilder.settings(indexSettings);
if (mapping != null) {
createRequestBuilder.mappings(TypeMapping.of(b -> b //
.withJson(new StringReader(mapping.toJson()))));
String mappingJson = mapping.toJson();
TypeMapping typeMapping = fromJson(mappingJson, TypeMapping._DESERIALIZER);
createRequestBuilder.mappings(typeMapping);
}
return createRequestBuilder.build();
@@ -213,7 +182,8 @@ class RequestConverter {
Action.Builder actionBuilder = new Action.Builder();
if (aliasAction instanceof AliasAction.Add add) {
if (aliasAction instanceof AliasAction.Add) {
AliasAction.Add add = (AliasAction.Add) aliasAction;
AliasActionParameters parameters = add.getParameters();
actionBuilder.add(addActionBuilder -> {
addActionBuilder //
@@ -242,7 +212,8 @@ class RequestConverter {
});
}
if (aliasAction instanceof AliasAction.Remove remove) {
if (aliasAction instanceof AliasAction.Remove) {
AliasAction.Remove remove = (AliasAction.Remove) aliasAction;
AliasActionParameters parameters = remove.getParameters();
actionBuilder.remove(removeActionBuilder -> {
removeActionBuilder.indices(Arrays.asList(parameters.getIndices()));
@@ -255,7 +226,8 @@ class RequestConverter {
});
}
if (aliasAction instanceof AliasAction.RemoveIndex removeIndex) {
if (aliasAction instanceof AliasAction.RemoveIndex) {
AliasAction.RemoveIndex removeIndex = (AliasAction.RemoveIndex) aliasAction;
AliasActionParameters parameters = removeIndex.getParameters();
actionBuilder.removeIndex(
removeIndexActionBuilder -> removeIndexActionBuilder.indices(Arrays.asList(parameters.getIndices())));
@@ -274,12 +246,13 @@ class RequestConverter {
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
Assert.notNull(mapping, "mapping must not be null");
PutMappingRequest request = new PutMappingRequest.Builder() //
.withJson(new StringReader(mapping.toJson())) //
.index(Arrays.asList(indexCoordinates.getIndexNames())) //
.build();
PutMappingRequest.Builder builder = new PutMappingRequest.Builder();
builder.index(Arrays.asList(indexCoordinates.getIndexNames()));
addPropertiesToMapping(builder, mapping);
return request;
// TODO #2155 what else to add
return builder.build();
}
public GetMappingRequest indicesGetMappingRequest(IndexCoordinates indexCoordinates) {
@@ -289,6 +262,23 @@ class RequestConverter {
return new GetMappingRequest.Builder().index(Arrays.asList(indexCoordinates.getIndexNames())).build();
}
private void addPropertiesToMapping(PutMappingRequest.Builder builder, Document mapping) {
Object properties = mapping.get("properties");
if (properties != null) {
if (properties instanceof Map) {
Map<String, Property> propertiesMap = new HashMap<>();
// noinspection unchecked
((Map<String, Object>) properties).forEach((key, value) -> {
Property property = getProperty(value);
propertiesMap.put(key, property);
});
builder.properties(propertiesMap);
}
}
}
private Property getProperty(Object value) {
// noinspection SpellCheckingInspection
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -447,6 +437,7 @@ class RequestConverter {
* so the code needs to be duplicated.
*/
@SuppressWarnings("DuplicatedCode")
public IndexRequest<?> documentIndexRequest(IndexQuery query, IndexCoordinates indexCoordinates,
@Nullable RefreshPolicy refreshPolicy) {
@@ -455,12 +446,12 @@ class RequestConverter {
IndexRequest.Builder<Object> builder = new IndexRequest.Builder<>();
builder.index(query.getIndexName() != null ? query.getIndexName() : indexCoordinates.getIndexName());
builder.index(indexCoordinates.getIndexName());
Object queryObject = query.getObject();
if (queryObject != null) {
String id = StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject);
String id = !StringUtils.hasText(query.getId()) ? getPersistentEntityId(queryObject) : query.getId();
builder //
.id(id) //
.document(elasticsearchConverter.mapObject(queryObject));
@@ -486,8 +477,12 @@ class RequestConverter {
if (query.getOpType() != null) {
switch (query.getOpType()) {
case INDEX -> builder.opType(OpType.Index);
case CREATE -> builder.opType(OpType.Create);
case INDEX:
builder.opType(OpType.Index);
break;
case CREATE:
builder.opType(OpType.Create);
break;
}
}
@@ -507,12 +502,12 @@ class RequestConverter {
IndexOperation.Builder<Object> builder = new IndexOperation.Builder<>();
builder.index(query.getIndexName() != null ? query.getIndexName() : indexCoordinates.getIndexName());
builder.index(indexCoordinates.getIndexName());
Object queryObject = query.getObject();
if (queryObject != null) {
String id = StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject);
String id = StringUtils.hasText(query.getId()) ? getPersistentEntityId(queryObject) : query.getId();
builder //
.id(id) //
.document(elasticsearchConverter.mapObject(queryObject));
@@ -548,12 +543,12 @@ class RequestConverter {
CreateOperation.Builder<Object> builder = new CreateOperation.Builder<>();
builder.index(query.getIndexName() != null ? query.getIndexName() : indexCoordinates.getIndexName());
builder.index(indexCoordinates.getIndexName());
Object queryObject = query.getObject();
if (queryObject != null) {
String id = StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject);
String id = StringUtils.hasText(query.getId()) ? getPersistentEntityId(queryObject) : query.getId();
builder //
.id(id) //
.document(elasticsearchConverter.mapObject(queryObject));
@@ -630,18 +625,20 @@ class RequestConverter {
Map<String, JsonData> params = new HashMap<>();
if (scriptData.params() != null) {
scriptData.params().forEach((key, value) -> params.put(key, JsonData.of(value, jsonpMapper)));
if (scriptData.getParams() != null) {
scriptData.getParams().forEach((key, value) -> {
params.put(key, JsonData.of(value, jsonpMapper));
});
}
return co.elastic.clients.elasticsearch._types.Script.of(sb -> {
if (scriptData.type() == ScriptType.INLINE) {
if (scriptData.getType() == ScriptType.INLINE) {
sb.inline(is -> is //
.lang(scriptData.language()) //
.source(scriptData.script()) //
.lang(scriptData.getLanguage()) //
.source(scriptData.getScript()) //
.params(params)); //
} else if (scriptData.type() == ScriptType.STORED) {
} else if (scriptData.getType() == ScriptType.STORED) {
sb.stored(ss -> ss //
.id(scriptData.script()) //
.id(scriptData.getScript()) //
.params(params) //
);
}
@@ -664,7 +661,7 @@ class RequestConverter {
}
if (bulkOptions.getWaitForActiveShards() != null) {
builder.waitForActiveShards(wasb -> wasb.count(bulkOptions.getWaitForActiveShards().value()));
builder.waitForActiveShards(wasb -> wasb.count(bulkOptions.getWaitForActiveShards().getValue()));
}
if (bulkOptions.getPipeline() != null) {
@@ -677,14 +674,16 @@ class RequestConverter {
List<BulkOperation> operations = queries.stream().map(query -> {
BulkOperation.Builder ob = new BulkOperation.Builder();
if (query instanceof IndexQuery indexQuery) {
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
if (indexQuery.getOpType() == IndexQuery.OpType.CREATE) {
ob.create(bulkCreateOperation(indexQuery, indexCoordinates, refreshPolicy));
} else {
ob.index(bulkIndexOperation(indexQuery, indexCoordinates, refreshPolicy));
}
} else if (query instanceof UpdateQuery updateQuery) {
} else if (query instanceof UpdateQuery) {
UpdateQuery updateQuery = (UpdateQuery) query;
ob.update(bulkUpdateOperation(updateQuery, indexCoordinates, refreshPolicy));
}
return ob.build();
@@ -733,8 +732,8 @@ class RequestConverter {
List<MultiGetOperation> multiGetOperations = query.getIdsWithRouting().stream()
.map(idWithRouting -> MultiGetOperation.of(mgo -> mgo //
.index(index.getIndexName()) //
.id(idWithRouting.id()) //
.routing(idWithRouting.routing()) //
.id(idWithRouting.getId()) //
.routing(idWithRouting.getRouting()) //
.source(sourceConfig)))
.collect(Collectors.toList());
@@ -829,8 +828,12 @@ class RequestConverter {
.maxDocs(reindexRequest.getMaxDocs()).waitForCompletion(waitForCompletion) //
.refresh(reindexRequest.getRefresh()) //
.requireAlias(reindexRequest.getRequireAlias()) //
.requestsPerSecond(toFloat(reindexRequest.getRequestsPerSecond())) //
.slices(slices(reindexRequest.getSlices()));
.requestsPerSecond(reindexRequest.getRequestsPerSecond()) //
;
if (reindexRequest.getSlices() != null) {
builder.slices(sb -> sb.value(reindexRequest.getSlices().intValue()));
}
return builder.build();
}
@@ -889,7 +892,9 @@ class RequestConverter {
Map<String, JsonData> params = new HashMap<>();
if (query.getParams() != null) {
query.getParams().forEach((key, value) -> params.put(key, JsonData.of(value, jsonpMapper)));
query.getParams().forEach((key, value) -> {
params.put(key, JsonData.of(value, jsonpMapper));
});
}
uqb.script(sb -> {
@@ -969,25 +974,29 @@ class RequestConverter {
.script(getScript(updateQuery.getScriptData())) //
.maxDocs(updateQuery.getMaxDocs() != null ? Long.valueOf(updateQuery.getMaxDocs()) : null) //
.pipeline(updateQuery.getPipeline()) //
.requestsPerSecond(updateQuery.getRequestsPerSecond()) //
.slices(slices(updateQuery.getSlices() != null ? Long.valueOf(updateQuery.getSlices()) : null));
.requestsPerSecond(
updateQuery.getRequestsPerSecond() != null ? updateQuery.getRequestsPerSecond().longValue() : null) //
;
if (updateQuery.getSlices() != null) {
ub.slices(sb -> sb.value(updateQuery.getSlices() != null ? updateQuery.getSlices() : null));
}
if (updateQuery.getAbortOnVersionConflict() != null) {
ub.conflicts(updateQuery.getAbortOnVersionConflict() ? Conflicts.Abort : Conflicts.Proceed);
}
if (updateQuery.getBatchSize() != null) {
ub.size(Long.valueOf(updateQuery.getBatchSize()));
}
if (updateQuery.getQuery() != null) {
Query queryQuery = updateQuery.getQuery();
if (updateQuery.getBatchSize() != null) {
((BaseQuery) queryQuery).setMaxResults(updateQuery.getBatchSize());
}
ub.query(getQuery(queryQuery, null));
// no indicesOptions available like in old client
ub.scroll(time(queryQuery.getScrollTime()));
}
// no maxRetries available like in old client
@@ -1032,6 +1041,7 @@ class RequestConverter {
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
elasticsearchConverter.updateQuery(query, clazz);
SearchRequest.Builder builder = new SearchRequest.Builder();
prepareSearchRequest(query, clazz, indexCoordinates, builder, forCount, useScroll);
@@ -1049,120 +1059,22 @@ class RequestConverter {
public MsearchRequest searchMsearchRequest(
List<ElasticsearchTemplate.MultiSearchQueryParameter> multiSearchQueryParameters) {
// basically the same stuff as in prepareSearchRequest, but the new Elasticsearch has different builders for a
// normal search and msearch
return MsearchRequest.of(mrb -> {
multiSearchQueryParameters.forEach(param -> {
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntity(param.clazz());
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntity(param.clazz);
var query = param.query();
mrb.searches(sb -> sb //
.header(h -> {
h //
.index(Arrays.asList(param.index().getIndexNames())) //
.routing(query.getRoute()) //
.searchType(searchType(query.getSearchType())) //
.requestCache(query.getRequestCache()) //
;
if (query.getPreference() != null) {
h.preference(query.getPreference());
}
return h;
}) //
.body(bb -> {
bb //
.query(getQuery(query, param.clazz()))//
.seqNoPrimaryTerm(persistentEntity != null ? persistentEntity.hasSeqNoPrimaryTermProperty() : null) //
.version(true) //
.trackScores(query.getTrackScores()) //
.source(getSourceConfig(query)) //
.timeout(timeStringMs(query.getTimeout())) //
;
if (query.getPageable().isPaged()) {
bb //
.from((int) query.getPageable().getOffset()) //
.size(query.getPageable().getPageSize());
}
if (!isEmpty(query.getFields())) {
bb.fields(fb -> {
query.getFields().forEach(fb::field);
return fb;
});
}
if (!isEmpty(query.getStoredFields())) {
bb.storedFields(query.getStoredFields());
}
if (query.isLimiting()) {
bb.size(query.getMaxResults());
}
if (query.getMinScore() > 0) {
bb.minScore((double) query.getMinScore());
}
if (query.getSort() != null) {
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
if (!sortOptions.isEmpty()) {
bb.sort(sortOptions);
}
}
addHighlight(query, bb);
if (query.getExplain()) {
bb.explain(true);
}
if (!isEmpty(query.getSearchAfter())) {
bb.searchAfter(query.getSearchAfter().stream().map(it -> FieldValue.of(it.toString()))
.collect(Collectors.toList()));
}
query.getRescorerQueries().forEach(rescorerQuery -> bb.rescore(getRescore(rescorerQuery)));
if (!query.getRuntimeFields().isEmpty()) {
Map<String, RuntimeField> runtimeMappings = new HashMap<>();
query.getRuntimeFields().forEach(runtimeField -> {
RuntimeField esRuntimeField = RuntimeField.of(rt -> {
RuntimeField.Builder builder = rt
.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType()));
String script = runtimeField.getScript();
if (script != null) {
builder = builder.script(s -> s.inline(is -> is.source(script)));
}
return builder;
});
runtimeMappings.put(runtimeField.getName(), esRuntimeField);
});
bb.runtimeMappings(runtimeMappings);
}
if (!isEmpty(query.getIndicesBoost())) {
Map<String, Double> boosts = new LinkedHashMap<>();
query.getIndicesBoost()
.forEach(indexBoost -> boosts.put(indexBoost.getIndexName(), (double) indexBoost.getBoost()));
// noinspection unchecked
bb.indicesBoost(boosts);
}
if (query instanceof NativeQuery) {
prepareNativeSearch((NativeQuery) query, bb);
}
return bb;
} //
.header(h -> h //
.index(param.index.getIndexName()) //
// todo #2156 add remaining flags for header
) //
.body(bb -> bb //
.query(getQuery(param.query, param.clazz))//
// todo #2138 seq_no_primary_term and version not available in client ES issue 161
// todo #2156 add remaining flags for body
) //
);
});
return mrb;
});
}
@@ -1177,24 +1089,10 @@ class RequestConverter {
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntity(clazz);
builder //
.index(Arrays.asList(indexNames)) //
.version(true) //
.trackScores(query.getTrackScores());
var pointInTime = query.getPointInTime();
if (pointInTime != null) {
builder.pit(pb -> pb.id(pointInTime.id()).keepAlive(time(pointInTime.keepAlive())));
} else {
builder.index(Arrays.asList(indexNames));
if (query.getRoute() != null) {
builder.routing(query.getRoute());
}
if (query.getPreference() != null) {
builder.preference(query.getPreference());
}
}
if (persistentEntity != null && persistentEntity.hasSeqNoPrimaryTermProperty()) {
builder.seqNoPrimaryTerm(true);
}
@@ -1232,6 +1130,10 @@ class RequestConverter {
builder.minScore((double) query.getMinScore());
}
if (query.getPreference() != null) {
builder.preference(query.getPreference());
}
builder.searchType(searchType(query.getSearchType()));
if (query.getSort() != null) {
@@ -1256,6 +1158,10 @@ class RequestConverter {
builder.trackTotalHits(th -> th.count(query.getTrackTotalHitsUpTo()));
}
if (query.getRoute() != null) {
builder.routing(query.getRoute());
}
builder.timeout(timeStringMs(query.getTimeout()));
if (query.getExplain()) {
@@ -1267,23 +1173,20 @@ class RequestConverter {
query.getSearchAfter().stream().map(it -> FieldValue.of(it.toString())).collect(Collectors.toList()));
}
query.getRescorerQueries().forEach(rescorerQuery -> builder.rescore(getRescore(rescorerQuery)));
query.getRescorerQueries().forEach(rescorerQuery -> {
builder.rescore(getRescore(rescorerQuery));
});
builder.requestCache(query.getRequestCache());
if (!query.getRuntimeFields().isEmpty()) {
Map<String, RuntimeField> runtimeMappings = new HashMap<>();
query.getRuntimeFields()
.forEach(runtimeField -> runtimeMappings.put(runtimeField.getName(), RuntimeField.of(runtimeFieldBuilder -> {
runtimeFieldBuilder.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType()));
String script = runtimeField.getScript();
if (script != null) {
runtimeFieldBuilder.script(s -> s.inline(is -> is.source(script)));
}
return runtimeFieldBuilder;
})));
query.getRuntimeFields().forEach(runtimeField -> {
runtimeMappings.put(runtimeField.getName(), RuntimeField.of(rt -> rt //
.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType())) //
.script(s -> s.inline(is -> is.source(runtimeField.getScript())))));
});
builder.runtimeMappings(runtimeMappings);
}
@@ -1302,8 +1205,9 @@ class RequestConverter {
if (!isEmpty(query.getIndicesBoost())) {
Map<String, Double> boosts = new LinkedHashMap<>();
query.getIndicesBoost()
.forEach(indexBoost -> boosts.put(indexBoost.getIndexName(), (double) indexBoost.getBoost()));
query.getIndicesBoost().forEach(indexBoost -> {
boosts.put(indexBoost.getIndexName(), (double) indexBoost.getBoost());
});
// noinspection unchecked
builder.indicesBoost(boosts);
}
@@ -1334,16 +1238,6 @@ class RequestConverter {
builder.highlight(highlight);
}
private void addHighlight(Query query, MultisearchBody.Builder builder) {
Highlight highlight = query.getHighlightQuery()
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
.getHighlight(highlightQuery.getHighlight(), highlightQuery.getType()))
.orElse(null);
builder.highlight(highlight);
}
private List<SortOptions> getSortOptions(Sort sort, @Nullable ElasticsearchPersistentEntity<?> persistentEntity) {
return sort.stream().map(order -> getSortOptions(order, persistentEntity)).collect(Collectors.toList());
}
@@ -1354,7 +1248,8 @@ class RequestConverter {
Order.Mode mode = Order.DEFAULT_MODE;
String unmappedType = null;
if (order instanceof Order o) {
if (order instanceof Order) {
Order o = (Order) order;
mode = o.getMode();
unmappedType = o.getUnmappedType();
}
@@ -1368,7 +1263,8 @@ class RequestConverter {
String fieldName = property != null ? property.getFieldName() : order.getProperty();
Order.Mode finalMode = mode;
if (order instanceof GeoDistanceOrder geoDistanceOrder) {
if (order instanceof GeoDistanceOrder) {
GeoDistanceOrder geoDistanceOrder = (GeoDistanceOrder) order;
return SortOptions.of(so -> so //
.geoDistance(gd -> gd //
@@ -1406,44 +1302,22 @@ class RequestConverter {
}
}
@SuppressWarnings("DuplicatedCode")
private void prepareNativeSearch(NativeQuery query, SearchRequest.Builder builder) {
query.getScriptedFields().forEach(scriptedField -> builder.scriptFields(scriptedField.getFieldName(),
sf -> sf.script(getScript(scriptedField.getScriptData()))));
query.getScriptedFields().forEach(scriptedField -> {
builder.scriptFields(scriptedField.getFieldName(), sf -> sf.script(getScript(scriptedField.getScriptData())));
});
builder //
.suggest(query.getSuggester()) //
.collapse(query.getFieldCollapse()) //
.sort(query.getSortOptions());
;
if (!isEmpty(query.getAggregations())) {
builder.aggregations(query.getAggregations());
}
if (!isEmpty(query.getSearchExtensions())) {
builder.ext(query.getSearchExtensions());
}
}
@SuppressWarnings("DuplicatedCode")
private void prepareNativeSearch(NativeQuery query, MultisearchBody.Builder builder) {
query.getScriptedFields().forEach(scriptedField -> builder.scriptFields(scriptedField.getFieldName(),
sf -> sf.script(getScript(scriptedField.getScriptData()))));
builder //
.suggest(query.getSuggester()) //
.collapse(query.getFieldCollapse()) //
.sort(query.getSortOptions());
if (!isEmpty(query.getAggregations())) {
builder.aggregations(query.getAggregations());
}
if (!isEmpty(query.getSearchExtensions())) {
builder.ext(query.getSearchExtensions());
}
// todo #2150 searchExt, currently not supported by the new client
}
@Nullable
@@ -1462,7 +1336,8 @@ class RequestConverter {
esQuery = CriteriaQueryProcessor.createQuery(((CriteriaQuery) query).getCriteria());
} else if (query instanceof StringQuery) {
esQuery = QueryBuilders.wrapperQueryAsQuery(((StringQuery) query).getSource());
} else if (query instanceof NativeQuery nativeQuery) {
} else if (query instanceof NativeQuery) {
NativeQuery nativeQuery = (NativeQuery) query;
if (nativeQuery.getQuery() != null) {
esQuery = nativeQuery.getQuery();
@@ -1470,7 +1345,6 @@ class RequestConverter {
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
}
return esQuery;
}
@@ -1536,27 +1410,6 @@ class RequestConverter {
return moreLikeThisQuery;
}
public OpenPointInTimeRequest searchOpenPointInTimeRequest(IndexCoordinates index, Duration keepAlive,
Boolean ignoreUnavailable) {
Assert.notNull(index, "index must not be null");
Assert.notNull(keepAlive, "keepAlive must not be null");
Assert.notNull(ignoreUnavailable, "ignoreUnavailable must not be null");
return OpenPointInTimeRequest.of(opit -> opit //
.index(Arrays.asList(index.getIndexNames())) //
.ignoreUnavailable(ignoreUnavailable) //
.keepAlive(time(keepAlive)) //
);
}
public ClosePointInTimeRequest searchClosePointInTime(String pit) {
Assert.notNull(pit, "pit must not be null");
return ClosePointInTimeRequest.of(cpit -> cpit.id(pit));
}
// endregion
// region helper functions
@@ -1617,12 +1470,20 @@ class RequestConverter {
.getVersionType();
if (entityVersionType != null) {
versionType = switch (entityVersionType) {
case INTERNAL -> VersionType.Internal;
case EXTERNAL -> VersionType.External;
case EXTERNAL_GTE -> VersionType.ExternalGte;
case FORCE -> VersionType.Force;
};
switch (entityVersionType) {
case INTERNAL:
versionType = VersionType.Internal;
break;
case EXTERNAL:
versionType = VersionType.External;
break;
case EXTERNAL_GTE:
versionType = VersionType.ExternalGte;
break;
case FORCE:
versionType = VersionType.Force;
break;
}
}
}
@@ -1661,11 +1522,15 @@ class RequestConverter {
return null;
}
return switch (refreshPolicy) {
case IMMEDIATE -> true;
case WAIT_UNTIL -> null;
case NONE -> false;
};
switch (refreshPolicy) {
case IMMEDIATE:
return true;
case WAIT_UNTIL:
return null;
case NONE:
default:
return false;
}
}
// endregion
@@ -90,7 +90,7 @@ class ResponseConverter {
.withNumberOfPendingTasks(healthResponse.numberOfPendingTasks()) //
.withRelocatingShards(healthResponse.relocatingShards()) //
.withStatus(healthResponse.status().toString()) //
.withTaskMaxWaitingTimeMillis(healthResponse.taskMaxWaitingInQueueMillis()) //
.withTaskMaxWaitingTimeMillis(Long.parseLong(healthResponse.taskMaxWaitingInQueueMillis())) //
.withTimedOut(healthResponse.timedOut()) //
.withUnassignedShards(healthResponse.unassignedShards()) //
.build(); //
@@ -144,11 +144,11 @@ class ResponseConverter {
if (indexMappingRecord == null) {
if (mappings.size() != 1) {
LOGGER.warn("no mapping returned for index {}", indexCoordinates.getIndexName());
return Document.create();
}
String index = mappings.keySet().iterator().next();
indexMappingRecord = mappings.get(index);
LOGGER.warn("no mapping returned for index {}", indexCoordinates.getIndexName());
return Document.create();
}
String index = mappings.keySet().iterator().next();
indexMappingRecord = mappings.get(index);
}
return Document.parse(toJson(indexMappingRecord.mappings(), jsonpMapper));
@@ -266,7 +266,7 @@ class ResponseConverter {
// noinspection ConstantConditions
return ReindexResponse.builder() //
.withTook(reindexResponse.took()) //
.withTook(timeToLong(reindexResponse.took())) //
.withTimedOut(reindexResponse.timedOut()) //
.withTotal(reindexResponse.total()) //
.withCreated(reindexResponse.created()) //
@@ -277,10 +277,9 @@ class ResponseConverter {
.withNoops(reindexResponse.noops()) //
.withBulkRetries(reindexResponse.retries().bulk()) //
.withSearchRetries(reindexResponse.retries().search()) //
.withThrottledMillis(reindexResponse.throttledMillis()) //
.withThrottledMillis(Long.parseLong(reindexResponse.throttledMillis())) //
.withRequestsPerSecond(reindexResponse.requestsPerSecond()) //
.withThrottledUntilMillis(reindexResponse.throttledUntilMillis()) //
.withFailures(failures) //
.withThrottledUntilMillis(Long.parseLong(reindexResponse.throttledUntilMillis())).withFailures(failures) //
.build();
}
@@ -17,8 +17,6 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggest;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
@@ -27,25 +25,16 @@ import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.json.JsonpMapper;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.elasticsearch.search.SearchHits;
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
import org.springframework.data.elasticsearch.core.document.SearchDocument;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion;
import org.springframework.data.elasticsearch.core.suggest.response.PhraseSuggestion;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.elasticsearch.core.suggest.response.TermSuggestion;
import org.springframework.data.elasticsearch.support.ScoreDoc;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Factory class to create {@link SearchDocumentResponse} instances.
@@ -54,9 +43,6 @@ import org.springframework.util.CollectionUtils;
* @since 4.4
*/
class SearchDocumentResponseBuilder {
private static final Log LOGGER = LogFactory.getLog(SearchDocumentResponseBuilder.class);
/**
* creates a SearchDocumentResponse from the {@link SearchResponse}
*
@@ -65,6 +51,7 @@ class SearchDocumentResponseBuilder {
* @param jsonpMapper to map JsonData objects
* @return the SearchDocumentResponse
*/
@SuppressWarnings("DuplicatedCode")
public static <T> SearchDocumentResponse from(ResponseBody<EntityAsMap> responseBody,
SearchDocumentResponse.EntityCreator<T> entityCreator, JsonpMapper jsonpMapper) {
@@ -75,17 +62,16 @@ class SearchDocumentResponseBuilder {
String scrollId = responseBody.scrollId();
Map<String, Aggregate> aggregations = responseBody.aggregations();
Map<String, List<Suggestion<EntityAsMap>>> suggest = responseBody.suggest();
var pointInTimeId = responseBody.pitId();
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
return from(hitsMetadata, scrollId, aggregations, suggest, entityCreator, jsonpMapper);
}
/**
* creates a {@link SearchDocumentResponseBuilder} from {@link HitsMetadata} with the given scrollId aggregations and
* creates a {@link SearchDocumentResponseBuilder} from {@link SearchHits} with the given scrollId aggregations and
* suggestES
*
* @param <T> entity type
* @param hitsMetadata the {@link HitsMetadata} to process
* @param hitsMetadata the {@link SearchHits} to process
* @param scrollId scrollId
* @param aggregations aggregations
* @param suggestES the suggestion response from Elasticsearch
@@ -94,9 +80,8 @@ class SearchDocumentResponseBuilder {
* @return the {@link SearchDocumentResponse}
*/
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) {
Map<String, Aggregate> aggregations, Map<String, List<Suggestion<EntityAsMap>>> suggestES,
SearchDocumentResponse.EntityCreator<T> entityCreator, JsonpMapper jsonpMapper) {
Assert.notNull(hitsMetadata, "hitsMetadata must not be null");
@@ -106,11 +91,16 @@ class SearchDocumentResponseBuilder {
TotalHits responseTotalHits = hitsMetadata.total();
if (responseTotalHits != null) {
totalHits = responseTotalHits.value();
totalHitsRelation = switch (responseTotalHits.relation().jsonValue()) {
case "eq" -> TotalHitsRelation.EQUAL_TO.name();
case "gte" -> TotalHitsRelation.GREATER_THAN_OR_EQUAL_TO.name();
default -> TotalHitsRelation.OFF.name();
};
switch (responseTotalHits.relation().jsonValue()) {
case "eq":
totalHitsRelation = TotalHitsRelation.EQUAL_TO.name();
break;
case "gte":
totalHitsRelation = TotalHitsRelation.GREATER_THAN_OR_EQUAL_TO.name();
break;
default:
totalHitsRelation = TotalHitsRelation.OFF.name();
}
} else {
totalHits = hitsMetadata.hits().size();
totalHitsRelation = "OFF";
@@ -126,115 +116,10 @@ class SearchDocumentResponseBuilder {
ElasticsearchAggregations aggregationsContainer = aggregations != null ? new ElasticsearchAggregations(aggregations)
: null;
Suggest suggest = suggestFrom(suggestES, entityCreator);
// todo #2154
Suggest suggest = null;
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchDocuments,
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments,
aggregationsContainer, suggest);
}
@Nullable
private static <T> Suggest suggestFrom(Map<String, List<Suggestion<EntityAsMap>>> suggestES,
SearchDocumentResponse.EntityCreator<T> entityCreator) {
if (CollectionUtils.isEmpty(suggestES)) {
return null;
}
List<Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>>> suggestions = new ArrayList<>();
suggestES.forEach((name, suggestionsES) -> {
if (!suggestionsES.isEmpty()) {
// take the type from the first entry
switch (suggestionsES.get(0)._kind()) {
case Term -> {
suggestions.add(getTermSuggestion(name, suggestionsES));
break;
}
case Phrase -> {
suggestions.add(getPhraseSuggestion(name, suggestionsES));
break;
}
case Completion -> {
suggestions.add(getCompletionSuggestion(name, suggestionsES, entityCreator));
break;
}
default -> {}
}
}
});
// todo: hasScoreDocs checks if any one
boolean hasScoreDocs = false;
return new Suggest(suggestions, hasScoreDocs);
}
private static TermSuggestion getTermSuggestion(String name, List<Suggestion<EntityAsMap>> suggestionsES) {
List<TermSuggestion.Entry> entries = new ArrayList<>();
suggestionsES.forEach(suggestionES -> {
var termSuggest = suggestionES.term();
var termSuggestOptions = termSuggest.options();
List<TermSuggestion.Entry.Option> options = new ArrayList<>();
termSuggestOptions.forEach(optionES -> options.add(new TermSuggestion.Entry.Option(optionES.text(), null,
optionES.score(), null, Math.toIntExact(optionES.freq()))));
entries.add(new TermSuggestion.Entry(termSuggest.text(), termSuggest.offset(), termSuggest.length(), options));
});
return new TermSuggestion(name, suggestionsES.size(), entries, null);
}
private static PhraseSuggestion getPhraseSuggestion(String name, List<Suggestion<EntityAsMap>> suggestionsES) {
List<PhraseSuggestion.Entry> entries = new ArrayList<>();
suggestionsES.forEach(suggestionES -> {
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(), null, null)));
entries.add(new PhraseSuggestion.Entry(phraseSuggest.text(), phraseSuggest.offset(), phraseSuggest.length(),
options, null));
});
return new PhraseSuggestion(name, suggestionsES.size(), entries);
}
private static <T> CompletionSuggestion<T> getCompletionSuggestion(String name,
List<Suggestion<EntityAsMap>> suggestionsES, SearchDocumentResponse.EntityCreator<T> entityCreator) {
List<CompletionSuggestion.Entry<T>> entries = new ArrayList<>();
suggestionsES.forEach(suggestionES -> {
CompletionSuggest<EntityAsMap> completionSuggest = suggestionES.completion();
List<CompletionSuggestion.Entry.Option<T>> options = new ArrayList<>();
List<CompletionSuggestOption<EntityAsMap>> optionsES = completionSuggest.options();
optionsES.forEach(optionES -> {
SearchDocument searchDocument = (optionES.source() != null) ? DocumentAdapters.from(optionES) : null;
T hitEntity = null;
if (searchDocument != null) {
try {
hitEntity = entityCreator.apply(searchDocument).get();
} catch (Exception e) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Error creating entity from SearchDocument: " + e.getMessage());
}
}
}
Map<String, Set<String>> contexts = new HashMap<>();
optionES.contexts().forEach((key, contextList) -> contexts.put(key,
contextList.stream().map(context -> context._get().toString()).collect(Collectors.toSet())));
// response from the new client does not have a doc and shardindex as the ScoreDoc from the old client responses
options.add(new CompletionSuggestion.Entry.Option<>(optionES.text(), null, optionES.score(),
optionES.collateMatch() != null ? optionES.collateMatch() : false, contexts,
new ScoreDoc(optionES.score() != null ? optionES.score() : Double.NaN, null, null), searchDocument,
hitEntity));
});
entries.add(new CompletionSuggestion.Entry<>(completionSuggest.text(), completionSuggest.offset(),
completionSuggest.length(), options));
});
return new CompletionSuggestion<>(name, suggestionsES.size(), entries);
}
}
@@ -18,6 +18,7 @@ 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.core.search.BoundaryScanner;
import co.elastic.clients.elasticsearch.core.search.BuiltinHighlighterType;
import co.elastic.clients.elasticsearch.core.search.HighlighterEncoder;
import co.elastic.clients.elasticsearch.core.search.HighlighterFragmenter;
import co.elastic.clients.elasticsearch.core.search.HighlighterOrder;
@@ -49,38 +50,64 @@ final class TypeUtils {
static BoundaryScanner boundaryScanner(@Nullable String value) {
if (value != null) {
return switch (value.toLowerCase()) {
case "chars" -> BoundaryScanner.Chars;
case "sentence" -> BoundaryScanner.Sentence;
case "word" -> BoundaryScanner.Word;
default -> null;
};
switch (value.toLowerCase()) {
case "chars":
return BoundaryScanner.Chars;
case "sentence":
return BoundaryScanner.Sentence;
case "word":
return BoundaryScanner.Word;
default:
return null;
}
}
return null;
}
static Conflicts conflicts(ReindexRequest.Conflicts conflicts) {
return switch (conflicts) {
case ABORT -> Conflicts.Abort;
case PROCEED -> Conflicts.Proceed;
};
switch (conflicts) {
case ABORT:
return Conflicts.Abort;
case PROCEED:
return Conflicts.Proceed;
}
throw new IllegalArgumentException("Cannot map conflicts value " + conflicts.name());
}
@Nullable
static DistanceUnit distanceUnit(String unit) {
return switch (unit.toLowerCase()) {
case "in", "inch" -> DistanceUnit.Inches;
case "yd", "yards" -> DistanceUnit.Yards;
case "ft", "feet" -> DistanceUnit.Feet;
case "km", "kilometers" -> DistanceUnit.Kilometers;
case "nm", "nmi" -> DistanceUnit.NauticMiles;
case "mm", "millimeters" -> DistanceUnit.Millimeters;
case "cm", "centimeters" -> DistanceUnit.Centimeters;
case "mi", "miles" -> DistanceUnit.Miles;
case "m", "meters" -> DistanceUnit.Meters;
default -> null;
};
switch (unit.toLowerCase()) {
case "in":
case "inch":
return DistanceUnit.Inches;
case "yd":
case "yards":
return DistanceUnit.Yards;
case "ft":
case "feet":
return DistanceUnit.Feet;
case "km":
case "kilometers":
return DistanceUnit.Kilometers;
case "nm":
case "nmi":
return DistanceUnit.NauticMiles;
case "mm":
case "millimeters":
return DistanceUnit.Millimeters;
case "cm":
case "centimeters":
return DistanceUnit.Centimeters;
case "mi":
case "miles":
return DistanceUnit.Miles;
case "m":
case "meters":
return DistanceUnit.Meters;
}
return null;
}
@Nullable
@@ -103,48 +130,48 @@ final class TypeUtils {
}
switch (fieldValue._kind()) {
case Double -> {
case Double:
return String.valueOf(fieldValue.doubleValue());
}
case Long -> {
case Long:
return String.valueOf(fieldValue.longValue());
}
case Boolean -> {
case Boolean:
return String.valueOf(fieldValue.booleanValue());
}
case String -> {
case String:
return fieldValue.stringValue();
}
case Null -> {
case Null:
return null;
}
case Any -> {
case Any:
return fieldValue.anyValue().toString();
}
default -> throw new IllegalStateException("Unexpected value: " + fieldValue._kind());
default:
throw new IllegalStateException("Unexpected value: " + fieldValue._kind());
}
}
@Nullable
static GeoDistanceType geoDistanceType(GeoDistanceOrder.DistanceType distanceType) {
return switch (distanceType) {
case arc -> GeoDistanceType.Arc;
case plane -> GeoDistanceType.Plane;
};
switch (distanceType) {
case arc:
return GeoDistanceType.Arc;
case plane:
return GeoDistanceType.Plane;
}
return null;
}
@Nullable
static HighlighterFragmenter highlighterFragmenter(@Nullable String value) {
if (value != null) {
return switch (value.toLowerCase()) {
case "simple" -> HighlighterFragmenter.Simple;
case "span" -> HighlighterFragmenter.Span;
default -> null;
};
switch (value.toLowerCase()) {
case "simple":
return HighlighterFragmenter.Simple;
case "span":
return HighlighterFragmenter.Span;
default:
return null;
}
}
return null;
@@ -166,12 +193,16 @@ final class TypeUtils {
static HighlighterType highlighterType(@Nullable String value) {
if (value != null) {
return switch (value.toLowerCase()) {
case "unified" -> HighlighterType.Unified;
case "plain" -> HighlighterType.Plain;
case "fvh" -> HighlighterType.FastVector;
default -> null;
};
switch (value.toLowerCase()) {
case "unified":
return HighlighterType.of(b -> b.builtin(BuiltinHighlighterType.Unified));
case "plain":
return HighlighterType.of(b -> b.builtin(BuiltinHighlighterType.Plain));
case "fvh":
return HighlighterType.of(b -> b.builtin(BuiltinHighlighterType.FastVector));
default:
return null;
}
}
return null;
@@ -181,11 +212,14 @@ final class TypeUtils {
static HighlighterEncoder highlighterEncoder(@Nullable String value) {
if (value != null) {
return switch (value.toLowerCase()) {
case "default" -> HighlighterEncoder.Default;
case "html" -> HighlighterEncoder.Html;
default -> null;
};
switch (value.toLowerCase()) {
case "default":
return HighlighterEncoder.Default;
case "html":
return HighlighterEncoder.Html;
default:
return null;
}
}
return null;
@@ -207,10 +241,12 @@ final class TypeUtils {
static OpType opType(@Nullable IndexQuery.OpType opType) {
if (opType != null) {
return switch (opType) {
case INDEX -> OpType.Index;
case CREATE -> OpType.Create;
};
switch (opType) {
case INDEX:
return OpType.Index;
case CREATE:
return OpType.Create;
}
}
return null;
}
@@ -221,11 +257,15 @@ final class TypeUtils {
return Refresh.False;
}
return switch (refreshPolicy) {
case IMMEDIATE -> Refresh.True;
case WAIT_UNTIL -> Refresh.WaitFor;
case NONE -> Refresh.False;
};
switch (refreshPolicy) {
case IMMEDIATE:
return Refresh.True;
case WAIT_UNTIL:
return Refresh.WaitFor;
case NONE:
default:
return Refresh.False;
}
}
@Nullable
@@ -235,14 +275,20 @@ final class TypeUtils {
return null;
}
return switch (result) {
case Created -> UpdateResponse.Result.CREATED;
case Updated -> UpdateResponse.Result.UPDATED;
case Deleted -> UpdateResponse.Result.DELETED;
case NotFound -> UpdateResponse.Result.NOT_FOUND;
case NoOp -> UpdateResponse.Result.NOOP;
};
switch (result) {
case Created:
return UpdateResponse.Result.CREATED;
case Updated:
return UpdateResponse.Result.UPDATED;
case Deleted:
return UpdateResponse.Result.DELETED;
case NotFound:
return UpdateResponse.Result.NOT_FOUND;
case NoOp:
return UpdateResponse.Result.NOOP;
}
return null;
}
@Nullable
@@ -252,15 +298,22 @@ final class TypeUtils {
return null;
}
return switch (scoreMode) {
case Default -> null;
case Avg -> ScoreMode.Avg;
case Max -> ScoreMode.Max;
case Min -> ScoreMode.Min;
case Total -> ScoreMode.Total;
case Multiply -> ScoreMode.Multiply;
};
switch (scoreMode) {
case Default:
return null;
case Avg:
return ScoreMode.Avg;
case Max:
return ScoreMode.Max;
case Min:
return ScoreMode.Min;
case Total:
return ScoreMode.Total;
case Multiply:
return ScoreMode.Multiply;
}
return null;
}
@Nullable
@@ -270,33 +323,31 @@ final class TypeUtils {
return null;
}
return switch (searchType) {
case QUERY_THEN_FETCH -> SearchType.QueryThenFetch;
case DFS_QUERY_THEN_FETCH -> SearchType.DfsQueryThenFetch;
};
}
@Nullable
static Slices slices(@Nullable Long count) {
if (count == null) {
return null;
switch (searchType) {
case QUERY_THEN_FETCH:
return SearchType.QueryThenFetch;
case DFS_QUERY_THEN_FETCH:
return SearchType.DfsQueryThenFetch;
}
return Slices.of(s -> s.value(Math.toIntExact(count)));
return null;
}
@Nullable
static SortMode sortMode(Order.Mode mode) {
return switch (mode) {
case min -> SortMode.Min;
case max -> SortMode.Max;
case median -> SortMode.Median;
case avg -> SortMode.Avg;
};
switch (mode) {
case min:
return SortMode.Min;
case max:
return SortMode.Max;
case median:
return SortMode.Median;
case avg:
return SortMode.Avg;
}
return null;
}
@Nullable
@@ -324,12 +375,16 @@ final class TypeUtils {
@Nullable org.springframework.data.elasticsearch.annotations.Document.VersionType versionType) {
if (versionType != null) {
return switch (versionType) {
case INTERNAL -> VersionType.Internal;
case EXTERNAL -> VersionType.External;
case EXTERNAL_GTE -> VersionType.ExternalGte;
case FORCE -> VersionType.Force;
};
switch (versionType) {
case INTERNAL:
return VersionType.Internal;
case EXTERNAL:
return VersionType.External;
case EXTERNAL_GTE:
return VersionType.ExternalGte;
case FORCE:
return VersionType.Force;
}
}
return null;
@@ -350,15 +405,4 @@ final class TypeUtils {
}
}
/**
* Converts a Long to a Float, returning null if the input is null.
*
* @param value the long value
* @return a FLoat with the given value
* @since 5.0
*/
@Nullable
static Float toFloat(@Nullable Long value) {
return value != null ? Float.valueOf(value) : null;
}
}
@@ -1,39 +0,0 @@
/*
* Copyright 2023 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.aot;
import java.util.Arrays;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.lang.Nullable;
/**
* @author Peter-Josef Meisch
* @since 5.0.1
*/
public class ElasticsearchClientRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
// 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);
}
}
@@ -1,25 +0,0 @@
/*
* Copyright 2022-2023 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.
*/
/**
* This package contains classes that use the old Elasticsearch 7 libraries to access Elasticsearch either directly by
* using the RestHighLevelClient or indirectly by using code copied from Elasticsearch libraries (reactive
* implementation). These classes are deprectaed in favour of using the implementations using the new Elasticsearch
* Client.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.client.erhlc;
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.reactive;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -95,10 +95,12 @@ import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.ClientLogger;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.data.elasticsearch.client.NoReachableHostException;
import org.springframework.data.elasticsearch.client.erhlc.HostProvider.Verification;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient.Cluster;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient.Indices;
import org.springframework.data.elasticsearch.client.reactive.HostProvider.Verification;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Cluster;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices;
import org.springframework.data.elasticsearch.client.util.NamedXContents;
import org.springframework.data.elasticsearch.client.util.ScrollState;
import org.springframework.data.elasticsearch.core.ResponseConverter;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.util.Lazy;
import org.springframework.http.HttpHeaders;
@@ -132,14 +134,12 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe
* @since 3.2
* @see ClientConfiguration
* @see ReactiveRestClients
* @deprecated since 5.0
*/
@Deprecated
public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, Indices, Cluster {
private final HostProvider<?> hostProvider;
private final RequestCreator requestCreator;
private Supplier<org.springframework.data.elasticsearch.support.HttpHeaders> headersSupplier = org.springframework.data.elasticsearch.support.HttpHeaders::new;
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
/**
* Create a new {@link DefaultReactiveElasticsearchClient} using the given {@link HostProvider} to obtain server
@@ -181,10 +181,8 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
Assert.notNull(headers, "HttpHeaders must not be null");
Assert.notEmpty(hosts, "Elasticsearch Cluster needs to consist of at least one host");
var httpHeaders = new org.springframework.data.elasticsearch.support.HttpHeaders();
httpHeaders.addAll(headers);
ClientConfiguration clientConfiguration = ClientConfiguration.builder().connectedTo(hosts)
.withDefaultHeaders(httpHeaders).build();
.withDefaultHeaders(headers).build();
return create(clientConfiguration);
}
@@ -228,7 +226,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
return client;
}
public void setHeadersSupplier(Supplier<org.springframework.data.elasticsearch.support.HttpHeaders> headersSupplier) {
public void setHeadersSupplier(Supplier<HttpHeaders> headersSupplier) {
Assert.notNull(headersSupplier, "headersSupplier must not be null");
@@ -718,25 +716,25 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
if (RawActionResponse.class.equals(responseType)) {
ClientLogger.logRawResponse(logId, response.statusCode().value());
ClientLogger.logRawResponse(logId, response.statusCode());
return Mono.just(responseType.cast(RawActionResponse.create(response)));
}
if (response.statusCode().is5xxServerError()) {
ClientLogger.logRawResponse(logId, response.statusCode().value());
ClientLogger.logRawResponse(logId, response.statusCode());
return handleServerError(request, response);
}
if (response.statusCode().is4xxClientError()) {
ClientLogger.logRawResponse(logId, response.statusCode().value());
ClientLogger.logRawResponse(logId, response.statusCode());
return handleClientError(logId, response, responseType);
}
return response.body(BodyExtractors.toMono(byte[].class)) //
.map(it -> new String(it, StandardCharsets.UTF_8)) //
.doOnNext(it -> ClientLogger.logResponse(logId, response.statusCode().value(), it)) //
.doOnNext(it -> ClientLogger.logResponse(logId, response.statusCode(), it)) //
.flatMap(content -> doDecode(response, responseType, content));
}
@@ -813,7 +811,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
return response.body(BodyExtractors.toMono(byte[].class)) //
.map(bytes -> new String(bytes, StandardCharsets.UTF_8)) //
.flatMap(content -> contentOrError(content, mediaType, status)) //
.doOnNext(content -> ClientLogger.logResponse(logId, response.statusCode().value(), content)) //
.doOnNext(content -> ClientLogger.logResponse(logId, response.statusCode(), content)) //
.flatMap(content -> doDecode(response, responseType, content));
}
@@ -895,7 +893,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient.Status#hosts()
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Status#hosts()
*/
@Override
public Collection<ElasticsearchHost> hosts() {
@@ -13,12 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.reactive;
/**
* @author Roman Puchkovskiy
* @since 4.0
* @deprecated since 5.0
*/
@Deprecated
class DefaultRequestCreator implements RequestCreator {}
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.reactive;
import java.net.InetSocketAddress;
import java.util.Map;
@@ -37,9 +37,7 @@ import org.springframework.web.util.DefaultUriBuilderFactory;
* @author Huw Ayling-Miller
* @author Peter-Josef Meisch
* @since 3.2
* @deprecated since 5.0
*/
@Deprecated
class DefaultWebClientProvider implements WebClientProvider {
private final Map<InetSocketAddress, WebClient> cachedClients;
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.reactive;
import reactor.core.publisher.Mono;
@@ -24,7 +24,7 @@ import java.util.function.Supplier;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.data.elasticsearch.client.NoReachableHostException;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
@@ -36,9 +36,7 @@ import org.springframework.web.reactive.function.client.WebClient;
* @author Mark Paluch
* @author Peter-Josef Meisch
* @since 3.2
* @deprecated since 5.0
*/
@Deprecated
public interface HostProvider<T extends HostProvider<T>> {
/**
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.reactive;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -45,9 +45,7 @@ import org.springframework.web.reactive.function.client.WebClient;
* @author Mark Paluch
* @author Peter-Josef Meisch
* @since 3.2
* @deprecated since 5.0
*/
@Deprecated
class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
private final static Log LOGGER = LogFactory.getLog(MultiNodeHostProvider.class);
@@ -70,7 +68,7 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.erhlc.HostProvider#clusterInfo()
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#clusterInfo()
*/
@Override
public Mono<ClusterInformation> clusterInfo() {
@@ -80,7 +78,7 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.erhlc.HostProvider#createWebClient(java.net.InetSocketAddress)
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#createWebClient(java.net.InetSocketAddress)
*/
@Override
public WebClient createWebClient(InetSocketAddress endpoint) {
@@ -89,7 +87,7 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.erhlc.HostProvider#lookupActiveHost(org.springframework.data.elasticsearch.client.erhlc.HostProvider.Verification)
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#lookupActiveHost(org.springframework.data.elasticsearch.client.reactive.HostProvider.Verification)
*/
@Override
public Mono<InetSocketAddress> lookupActiveHost(Verification verification) {
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.reactive;
import reactor.core.publisher.Mono;
@@ -21,7 +21,7 @@ import java.io.IOException;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.reactive.function.client.ClientResponse;
@@ -32,11 +32,8 @@ import org.springframework.web.reactive.function.client.ClientResponse;
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Mark Paluch
* @author Oliver Drotbohm
* @since 3.2
* @deprecated since 5.0
*/
@Deprecated
class RawActionResponse extends ActionResponse {
private final ClientResponse delegate;
@@ -49,7 +46,7 @@ class RawActionResponse extends ActionResponse {
return new RawActionResponse(response);
}
public HttpStatusCode statusCode() {
public HttpStatus statusCode() {
return delegate.statusCode();
}
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.reactive;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -81,9 +81,7 @@ import org.springframework.web.reactive.function.client.WebClient;
* @since 3.2
* @see ClientConfiguration
* @see ReactiveRestClients
* @deprecated since 5.0
*/
@Deprecated
public interface ReactiveElasticsearchClient {
/**
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.reactive;
import java.util.function.Function;
@@ -29,9 +29,7 @@ import org.springframework.web.reactive.function.client.WebClient;
* @author Mark Paluch
* @author Roman Puchkovskiy
* @since 3.2
* @deprecated since 5.0
*/
@Deprecated
public final class ReactiveRestClients {
private ReactiveRestClients() {}
@@ -72,9 +70,7 @@ public final class ReactiveRestClients {
* the ReactiveElasticsearchClient with a {@link WebClient}
*
* @since 4.3
* @deprecated
*/
@Deprecated
public interface WebClientConfigurationCallback extends ClientConfiguration.ClientConfigurationCallback<WebClient> {
static WebClientConfigurationCallback from(Function<WebClient, WebClient> webClientCallback) {
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.reactive;
import org.springframework.web.reactive.function.client.WebClientException;
@@ -23,10 +23,8 @@ import org.springframework.web.reactive.function.client.WebClientException;
* @author Christoph Strobl
* @author Mark Paluch
* @since 3.2
* @deprecated since 5.0
*/
@Deprecated public class RequestBodyEncodingException extends WebClientException {
public class RequestBodyEncodingException extends WebClientException {
private static final long serialVersionUID = 472776714118912855L;
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.reactive;
import java.io.IOException;
import java.util.function.Function;
@@ -53,15 +53,14 @@ import org.elasticsearch.index.reindex.ReindexRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.client.util.RequestConverters;
/**
* @author Roman Puchkovskiy
* @author Farid Faoudi
* @author George Popides
* @since 4.0
* @deprecated since 5.0
*/
@Deprecated
public interface RequestCreator {
default Function<SearchRequest, Request> search() {
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.reactive;
import reactor.core.publisher.Mono;
@@ -32,9 +32,7 @@ import org.springframework.web.reactive.function.client.WebClient;
* @author Mark Paluch
* @author Peter-Josef Meisch
* @since 3.2
* @deprecated since 5.0
*/
@Deprecated
class SingleNodeHostProvider implements HostProvider<SingleNodeHostProvider> {
private final WebClientProvider clientProvider;
@@ -50,7 +48,7 @@ class SingleNodeHostProvider implements HostProvider<SingleNodeHostProvider> {
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.erhlc.HostProvider#clusterInfo()
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#clusterInfo()
*/
@Override
public Mono<ClusterInformation> clusterInfo() {
@@ -73,7 +71,7 @@ class SingleNodeHostProvider implements HostProvider<SingleNodeHostProvider> {
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.erhlc.HostProvider#createWebClient(java.net.InetSocketAddress)
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#createWebClient(java.net.InetSocketAddress)
*/
@Override
public WebClient createWebClient(InetSocketAddress endpoint) {
@@ -82,7 +80,7 @@ class SingleNodeHostProvider implements HostProvider<SingleNodeHostProvider> {
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.erhlc.HostProvider#lookupActiveHost(org.springframework.data.elasticsearch.client.erhlc.HostProvider.Verification)
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#lookupActiveHost(org.springframework.data.elasticsearch.client.reactive.HostProvider.Verification)
*/
@Override
public Mono<InetSocketAddress> lookupActiveHost(Verification verification) {
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.reactive;
import io.netty.channel.ChannelOption;
import io.netty.handler.ssl.ApplicationProtocolConfig;
@@ -35,6 +35,7 @@ import java.util.function.Function;
import javax.net.ssl.SSLContext;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
@@ -45,7 +46,8 @@ import org.springframework.web.reactive.function.client.WebClient;
/**
* Provider for {@link WebClient}s using a pre-configured {@code scheme}. This class returns {@link WebClient} for a
* specific {@link InetSocketAddress endpoint} and encapsulates common configuration aspects of {@link WebClient} so
* that code using {@link WebClient} is not required to apply further configuration to the actual client. <br/>
* that code using {@link WebClient} is not required to apply further configuration to the actual client.
* <p/>
* Client instances are typically cached allowing reuse of pooled connections if configured on the
* {@link ClientHttpConnector}.
*
@@ -54,9 +56,7 @@ import org.springframework.web.reactive.function.client.WebClient;
* @author Huw Ayling-Miller
* @author Peter-Josef Meisch
* @since 3.2
* @deprecated since 5.0
*/
@Deprecated
public interface WebClientProvider {
/**
@@ -235,7 +235,13 @@ public interface WebClientProvider {
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurer instanceof ReactiveRestClients.WebClientConfigurationCallback webClientConfigurationCallback) {
if (clientConfigurer instanceof ReactiveRestClients.WebClientConfigurationCallback) {
ReactiveRestClients.WebClientConfigurationCallback webClientConfigurationCallback = (ReactiveRestClients.WebClientConfigurationCallback) clientConfigurer;
webClient = webClientConfigurationCallback.configure(webClient);
}
if (clientConfigurer instanceof ElasticsearchClients.WebClientConfigurationCallback) {
ElasticsearchClients.WebClientConfigurationCallback webClientConfigurationCallback = (ElasticsearchClients.WebClientConfigurationCallback) clientConfigurer;
webClient = webClientConfigurationCallback.configure(webClient);
}
}
@@ -243,26 +249,24 @@ public interface WebClientProvider {
};
provider = provider //
.withDefaultHeaders(HttpHeaders.readOnlyHttpHeaders(clientConfiguration.getDefaultHeaders())) //
.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
.withWebClientConfigurer(webClientConfigurer) //
.withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec //
.headers(httpHeaders -> {
HttpHeaders suppliedHeaders = HttpHeaders
.readOnlyHttpHeaders(clientConfiguration.getHeadersSupplier().get());
HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get();
if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) {
// remove content-type and accept if they are provided by the client configuration (ES7 compatibility headers)
if (suppliedHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
httpHeaders.remove(HttpHeaders.CONTENT_TYPE);
}
if (suppliedHeaders.containsKey(HttpHeaders.ACCEPT)) {
httpHeaders.remove(HttpHeaders.ACCEPT);
}
httpHeaders.addAll(suppliedHeaders);
}
// this WebClientProvider is built with ES 7 and not used on 8 anymore
httpHeaders.add("Accept", "application/vnd.elasticsearch+json;compatible-with=7");
var contentTypeHeader = "Content-Type";
if (httpHeaders.containsKey(contentTypeHeader)) {
httpHeaders.remove(contentTypeHeader);
}
httpHeaders.add(contentTypeHeader, "application/vnd.elasticsearch+json;compatible-with=7");
}));
return provider;
@@ -1,3 +1,3 @@
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.aot;
package org.springframework.data.elasticsearch.client.reactive;
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.util;
import java.util.HashMap;
import java.util.List;
@@ -78,6 +78,7 @@ import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import org.elasticsearch.xcontent.ContextParser;
import org.elasticsearch.xcontent.NamedXContentRegistry;
import org.elasticsearch.xcontent.ParseField;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
/**
* <p>
@@ -91,9 +92,7 @@ import org.elasticsearch.xcontent.ParseField;
*
* @author Russell Parry
* @since 4.0
* @deprecated since 5.0
*/
@Deprecated
public class NamedXContents {
private NamedXContents() {
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.client.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -101,6 +101,7 @@ import org.elasticsearch.xcontent.XContent;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentType;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
@@ -119,9 +120,7 @@ import org.springframework.lang.Nullable;
* @author Peter-Josef Meisch
* @author Farid Faoudi
* @since 3.2
* @deprecated since 5.0
*/
@Deprecated
@SuppressWarnings("JavadocReference")
public class RequestConverters {
@@ -826,7 +825,7 @@ public class RequestConverters {
}
public static Request indexCreate(org.elasticsearch.client.indices.CreateIndexRequest createIndexRequest) {
String endpoint = RequestConverters.endpoint(createIndexRequest.index());
String endpoint = RequestConverters.endpoint(new String[] { createIndexRequest.index() });
Request request = new Request(HttpMethod.PUT.name(), endpoint);
Params parameters = new Params(request);
@@ -1058,7 +1057,7 @@ public class RequestConverters {
return new EndpointBuilder().addPathPart(index, type, id).addPathPartAsIs(endpoint).build();
}
static String endpoint(String... indices) {
static String endpoint(String[] indices) {
return new EndpointBuilder().addCommaSeparatedPathParts(indices).build();
}
@@ -1071,7 +1070,7 @@ public class RequestConverters {
.addPathPartAsIs(endpoint).build();
}
static String endpoint(String[] indices, String endpoint, String... suffixes) {
static String endpoint(String[] indices, String endpoint, String[] suffixes) {
return new EndpointBuilder().addCommaSeparatedPathParts(indices).addPathPartAsIs(endpoint)
.addCommaSeparatedPathParts(suffixes).build();
}
@@ -1139,7 +1138,7 @@ public class RequestConverters {
return this;
}
Params withFields(String... fields) {
Params withFields(String[] fields) {
if (fields != null && fields.length > 0) {
return putParam("fields", String.join(",", fields));
}
@@ -1200,7 +1199,7 @@ public class RequestConverters {
return putParam("routing", routing);
}
Params withStoredFields(String... storedFields) {
Params withStoredFields(String[] storedFields) {
if (storedFields != null && storedFields.length > 0) {
return putParam("stored_fields", String.join(",", storedFields));
}
@@ -1323,14 +1322,14 @@ public class RequestConverters {
return putParam("wait_for_completion", waitForCompletion.toString());
}
Params withNodes(String... nodes) {
Params withNodes(String[] nodes) {
if (nodes != null && nodes.length > 0) {
return putParam("nodes", String.join(",", nodes));
}
return this;
}
Params withActions(String... actions) {
Params withActions(String[] actions) {
if (actions != null && actions.length > 0) {
return putParam("actions", String.join(",", actions));
}
@@ -1433,7 +1432,7 @@ public class RequestConverters {
return this;
}
EndpointBuilder addCommaSeparatedPathParts(String... parts) {
EndpointBuilder addCommaSeparatedPathParts(String[] parts) {
addPathPart(String.join(",", parts));
return this;
}
@@ -15,6 +15,8 @@
*/
package org.springframework.data.elasticsearch.client.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -50,7 +52,7 @@ public class ScrollState {
public List<String> getScrollIds() {
synchronized (lock) {
return List.copyOf(pastIds);
return Collections.unmodifiableList(new ArrayList<>(pastIds));
}
}
@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.config;
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.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
/**
@@ -26,9 +26,7 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte
* @author Peter-Josef Meisch
* @since 3.2
* @see ElasticsearchConfigurationSupport
* @deprecated since 5.0
*/
@Deprecated
public abstract class AbstractElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
/**
@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.config;
import org.elasticsearch.action.support.IndicesOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.lang.Nullable;
@@ -28,9 +29,7 @@ import org.springframework.lang.Nullable;
* @author Peter-Josef Meisch
* @since 3.2
* @see ElasticsearchConfigurationSupport
* @deprecated since 5.0
*/
@Deprecated
public abstract class AbstractReactiveElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
/**
@@ -28,8 +28,8 @@ import org.springframework.data.auditing.config.IsNewAwareAuditingHandlerBeanDef
import org.springframework.data.elasticsearch.core.event.AuditingEntityCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveAuditingEntityCallback;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
@@ -41,9 +41,7 @@ import org.w3c.dom.Element;
*/
public class ElasticsearchAuditingBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
private static final String MAPPING_CONTEXT_BEAN_NAME = "simpleElasticsearchMappingContext";
private static final boolean PROJECT_REACTOR_AVAILABLE = ClassUtils.isPresent("reactor.core.publisher.Mono",
ElasticsearchAuditingRegistrar.class.getClassLoader());
private static String MAPPING_CONTEXT_BEAN_NAME = "simpleElasticsearchMappingContext";
/*
* (non-Javadoc)
@@ -92,7 +90,7 @@ public class ElasticsearchAuditingBeanDefinitionParser extends AbstractSingleBea
parserContext.extractSource(element));
builder.addConstructorArgValue(isNewAwareAuditingHandler);
if (PROJECT_REACTOR_AVAILABLE) {
if (ReactiveWrappers.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR)) {
registerReactiveAuditingEntityCallback(parserContext.getRegistry(), isNewAwareAuditingHandler,
parserContext.extractSource(element));
}
@@ -18,10 +18,10 @@ package org.springframework.data.elasticsearch.config;
import java.lang.annotation.Annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.Ordered;
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
import org.springframework.data.auditing.config.AuditingConfiguration;
@@ -35,8 +35,7 @@ import org.springframework.util.Assert;
* @author Peter-Josef Meisch
* @since 4.0
*/
class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport implements Ordered
{
class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
@Override
protected Class<? extends Annotation> getAnnotation() {
@@ -48,20 +47,18 @@ class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupp
return "elasticsearchAuditingHandler";
}
@Override
protected void postProcess(BeanDefinitionBuilder builder, AuditingConfiguration configuration,
BeanDefinitionRegistry registry) {
builder.setFactoryMethod("from").addConstructorArgReference("elasticsearchMappingContext");
}
@Override
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
Assert.notNull(configuration, "AuditingConfiguration must not be null!");
return configureDefaultAuditHandlerAttributes(configuration,
BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class));
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
builder.addConstructorArgValue(definition.getBeanDefinition());
return configureDefaultAuditHandlerAttributes(configuration, builder);
}
@Override
@@ -76,9 +73,4 @@ class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupp
registerInfrastructureBeanWithId(builder.getBeanDefinition(), AuditingEntityCallback.class.getName(), registry);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
@@ -27,7 +27,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.client.erhlc.AbstractReactiveElasticsearchConfiguration;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
@@ -16,7 +16,6 @@
package org.springframework.data.elasticsearch.config;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchClientBeanDefinitionParser;
import org.springframework.data.elasticsearch.repository.config.ElasticsearchRepositoryConfigExtension;
import org.springframework.data.repository.config.RepositoryBeanDefinitionParser;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
@@ -36,6 +35,6 @@ public class ElasticsearchNamespaceHandler extends NamespaceHandlerSupport {
RepositoryBeanDefinitionParser parser = new RepositoryBeanDefinitionParser(extension);
registerBeanDefinitionParser("repositories", parser);
registerBeanDefinitionParser("elasticsearch-client", new ElasticsearchClientBeanDefinitionParser());
registerBeanDefinitionParser("rest-client", new RestClientBeanDefinitionParser());
}
}
@@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.config;
import java.lang.annotation.Annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
@@ -46,19 +47,18 @@ class ReactiveElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegis
return "reactiveElasticsearchAuditingHandler";
}
@Override
protected void postProcess(BeanDefinitionBuilder builder, AuditingConfiguration configuration,
BeanDefinitionRegistry registry) {
builder.setFactoryMethod("from").addConstructorArgReference("elasticsearchMappingContext");
}
@Override
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
Assert.notNull(configuration, "AuditingConfiguration must not be null!");
return configureDefaultAuditHandlerAttributes(configuration,
BeanDefinitionBuilder.rootBeanDefinition(ReactiveIsNewAwareAuditingHandler.class));
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveIsNewAwareAuditingHandler.class);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
builder.addConstructorArgValue(definition.getBeanDefinition());
return configureDefaultAuditHandlerAttributes(configuration, builder);
}
@Override
@@ -13,24 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.erhlc;
package org.springframework.data.elasticsearch.config;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.elasticsearch.client.RestClientFactoryBean;
import org.w3c.dom.Element;
/**
* @author Don Wellington
* @deprecated since 5.0
*/
@Deprecated
public class RestHighLevelClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
public class RestClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(RestHighLevelClientFactoryBean.class);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(RestClientFactoryBean.class);
setConfigurations(element, builder);
return getSourcedBeanDefinition(builder, element, parserContext);
}
@@ -28,7 +28,6 @@ import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.elasticsearch.client.UnsupportedClientOperationException;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
@@ -49,7 +48,6 @@ import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
import org.springframework.data.elasticsearch.support.VersionInfo;
@@ -60,7 +58,6 @@ import org.springframework.data.util.Streamable;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* This class contains methods that are common to different implementations of the {@link ElasticsearchOperations}
@@ -77,7 +74,6 @@ import org.springframework.util.StringUtils;
* @author Subhobrata Dey
* @author Steven Pearce
* @author Anton Naydenov
* @author Haibo Liu
*/
public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
@@ -149,8 +145,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
/**
* Set the {@link EntityCallbacks} instance to use when invoking {@link EntityCallbacks callbacks} like the
* {@link org.springframework.data.elasticsearch.core.event.BeforeConvertCallback}. Overrides potentially existing
* {@link EntityCallbacks}.
* {@link org.springframework.data.elasticsearch.core.event.BeforeConvertCallback}.
* <p />
* Overrides potentially existing {@link EntityCallbacks}.
*
* @param entityCallbacks must not be {@literal null}.
* @throws IllegalArgumentException if the given instance is {@literal null}.
@@ -308,7 +305,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@Override
public String delete(Object entity, IndexCoordinates index) {
String entityId = getEntityId(entity);
Assert.notNull(entityId, "entity must have an id that is notnull");
Assert.notNull(entityId, "entity must have an if that is notnull");
return this.delete(entityId, index);
}
@@ -317,6 +314,12 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return doDelete(id, routingResolver.getRouting(), index);
}
@Override
@Deprecated
final public String delete(String id, @Nullable String routing, IndexCoordinates index) {
return doDelete(id, routing, index);
}
protected abstract String doDelete(String id, @Nullable String routing, IndexCoordinates index);
@Override
@@ -362,67 +365,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
public abstract List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
IndexCoordinates index);
@Override
public <T> UpdateResponse update(T entity) {
Assert.notNull(entity, "entity must not be null");
return update(buildUpdateQueryByEntity(entity), getIndexCoordinatesFor(entity.getClass()));
}
protected <T> UpdateQuery buildUpdateQueryByEntity(T entity) {
Assert.notNull(entity, "entity must not be null");
String id = getEntityId(entity);
Assert.notNull(id, "entity must have an id that is notnull");
UpdateQuery.Builder updateQueryBuilder = UpdateQuery.builder(id)
.withDocument(elasticsearchConverter.mapObject(entity));
String routing = getEntityRouting(entity);
if (StringUtils.hasText(routing)) {
updateQueryBuilder.withRouting(routing);
}
return updateQueryBuilder.build();
}
protected <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
ElasticsearchPersistentEntity<?> persistentEntity = elasticsearchConverter.getMappingContext()
.getPersistentEntity(entity.getClass());
if (persistentEntity != null) {
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isReadable()
&& idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
}
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
}
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
}
// noinspection unchecked
return (T) propertyAccessor.getBean();
}
return entity;
}
// endregion
// region SearchOperations
@@ -476,27 +418,29 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return search(query, clazz, getIndexCoordinatesFor(clazz));
}
abstract public <T> SearchScrollHits<T> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
/*
* internal use only, not for public API
*/
abstract protected <T> SearchScrollHits<T> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
IndexCoordinates index);
abstract public <T> SearchScrollHits<T> searchScrollContinue(String scrollId, long scrollTimeInMillis, Class<T> clazz,
IndexCoordinates index);
/*
* internal use only, not for public API
*/
abstract protected <T> SearchScrollHits<T> searchScrollContinue(String scrollId, long scrollTimeInMillis,
Class<T> clazz, IndexCoordinates index);
public void searchScrollClear(String scrollId) {
/*
* internal use only, not for public API
*/
protected void searchScrollClear(String scrollId) {
searchScrollClear(Collections.singletonList(scrollId));
}
abstract public void searchScrollClear(List<String> scrollIds);
@Override
public String openPointInTime(IndexCoordinates index, Duration keepAlive, Boolean ignoreUnavailable) {
throw new UnsupportedClientOperationException(getClass(), "openPointInTime");
}
@Override
public Boolean closePointInTime(String pit) {
throw new UnsupportedClientOperationException(getClass(), "closePointInTime");
}
/*
* internal use only, not for public API
*/
abstract protected void searchScrollClear(List<String> scrollIds);
// endregion
@@ -524,6 +468,41 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
}
protected <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
ElasticsearchPersistentEntity<?> persistentEntity = elasticsearchConverter.getMappingContext()
.getPersistentEntity(entity.getClass());
if (persistentEntity != null) {
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isWritable()
&& idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
}
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
}
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
}
// noinspection unchecked
return (T) propertyAccessor.getBean();
}
return entity;
}
ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz);
}
@@ -534,7 +513,11 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
Object id = entityOperations.forEntity(entity, elasticsearchConverter.getConversionService(), routingResolver)
.getId();
return convertId(id);
if (id != null) {
return stringIdRepresentation(id);
}
return null;
}
@Nullable
@@ -606,19 +589,19 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
* @return the version as string if it can be retrieved
*/
@Nullable
public abstract String getClusterVersion();
abstract protected String getClusterVersion();
/**
* @return the vendor name of the used cluster and client library
* @since 4.3
*/
public abstract String getVendor();
abstract protected String getVendor();
/**
* @return the version of the used client runtime library.
* @since 4.3
*/
public abstract String getRuntimeLibraryVersion();
abstract protected String getRuntimeLibraryVersion();
// endregion
@@ -634,7 +617,8 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
protected void maybeCallbackBeforeConvertWithQuery(Object query, IndexCoordinates index) {
if (query instanceof IndexQuery indexQuery) {
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
@@ -675,7 +659,8 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
protected void maybeCallbackAfterSaveWithQuery(Object query, IndexCoordinates index) {
if (query instanceof IndexQuery indexQuery) {
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
@@ -717,7 +702,8 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
for (int i = 0; i < queries.size(); i++) {
Object query = queries.get(i);
if (query instanceof IndexQuery indexQuery) {
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
@@ -760,9 +746,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( //
documentAfterLoad.hasId() ? documentAfterLoad.getId() : null, //
documentAfterLoad.hasSeqNo() ? documentAfterLoad.getSeqNo() : null, //
documentAfterLoad.hasPrimaryTerm() ? documentAfterLoad.getPrimaryTerm() : null, //
documentAfterLoad.hasVersion() ? documentAfterLoad.getVersion() : null); //
documentAfterLoad.getSeqNo(), //
documentAfterLoad.getPrimaryTerm(), //
documentAfterLoad.getVersion()); //
entity = updateIndexedObject(entity, indexedObjectInformation);
return maybeCallbackAfterConvert(entity, documentAfterLoad, index);
@@ -19,7 +19,6 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@@ -30,7 +29,6 @@ import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.elasticsearch.client.UnsupportedClientOperationException;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
@@ -175,8 +173,9 @@ abstract public class AbstractReactiveElasticsearchTemplate
return getVendor() //
.zipWith(getRuntimeLibraryVersion()) //
.zipWith(getClusterVersion()) //
.doOnNext(objects -> VersionInfo.logVersions(objects.getT1().getT1(), objects.getT1().getT2(), objects.getT2()))
.then();
.doOnNext(objects -> {
VersionInfo.logVersions(objects.getT1().getT1(), objects.getT1().getT2(), objects.getT2());
}).then();
}
// endregion
@@ -229,8 +228,8 @@ abstract public class AbstractReactiveElasticsearchTemplate
SeqNoPrimaryTerm seqNoPrimaryTerm = entity.getSeqNoPrimaryTerm();
if (seqNoPrimaryTerm != null) {
query.setSeqNo(seqNoPrimaryTerm.sequenceNumber());
query.setPrimaryTerm(seqNoPrimaryTerm.primaryTerm());
query.setSeqNo(seqNoPrimaryTerm.getSequenceNumber());
query.setPrimaryTerm(seqNoPrimaryTerm.getPrimaryTerm());
usingSeqNo = true;
}
}
@@ -260,7 +259,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isReadable()
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isWritable()
&& idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
}
@@ -311,17 +310,17 @@ abstract public class AbstractReactiveElasticsearchTemplate
Assert.notNull(entity, "Entity must not be null!");
Assert.notNull(index, "index must not be null");
return maybeCallbackBeforeConvert(entity, index)
return maybeCallBeforeConvert(entity, index)
.flatMap(entityAfterBeforeConversionCallback -> doIndex(entityAfterBeforeConversionCallback, index)) //
.map(it -> {
T savedEntity = it.getT1();
IndexResponseMetaData indexResponseMetaData = it.getT2();
return updateIndexedObject(savedEntity, IndexedObjectInformation.of( //
indexResponseMetaData.id(), //
indexResponseMetaData.seqNo(), //
indexResponseMetaData.primaryTerm(), //
indexResponseMetaData.version()));
}).flatMap(saved -> maybeCallbackAfterSave(saved, index));
indexResponseMetaData.getId(), //
indexResponseMetaData.getSeqNo(), //
indexResponseMetaData.getPrimaryTerm(), //
indexResponseMetaData.getVersion()));
}).flatMap(saved -> maybeCallAfterSave(saved, index));
}
abstract protected <T> Mono<Tuple2<T, IndexResponseMetaData>> doIndex(T entity, IndexCoordinates index);
@@ -478,22 +477,11 @@ abstract public class AbstractReactiveElasticsearchTemplate
}
abstract protected Mono<Long> doCount(Query query, Class<?> entityType, IndexCoordinates index);
@Override
public Mono<String> openPointInTime(IndexCoordinates index, Duration keepAlive, Boolean ignoreUnavailable) {
throw new UnsupportedClientOperationException(getClass(), "openPointInTime");
}
@Override
public Mono<Boolean> closePointInTime(String pit) {
throw new UnsupportedClientOperationException(getClass(), "closePointInTime");
}
// endregion
// region callbacks
protected <T> Mono<T> maybeCallbackBeforeConvert(T entity, IndexCoordinates index) {
protected <T> Mono<T> maybeCallBeforeConvert(T entity, IndexCoordinates index) {
if (null != entityCallbacks) {
return entityCallbacks.callback(ReactiveBeforeConvertCallback.class, entity, index);
@@ -502,7 +490,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
return Mono.just(entity);
}
protected <T> Mono<T> maybeCallbackAfterSave(T entity, IndexCoordinates index) {
protected <T> Mono<T> maybeCallAfterSave(T entity, IndexCoordinates index) {
if (null != entityCallbacks) {
return entityCallbacks.callback(ReactiveAfterSaveCallback.class, entity, index);
@@ -511,7 +499,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
return Mono.just(entity);
}
protected <T> Mono<T> maybeCallbackAfterConvert(T entity, Document document, IndexCoordinates index) {
protected <T> Mono<T> maybeCallAfterConvert(T entity, Document document, IndexCoordinates index) {
if (null != entityCallbacks) {
return entityCallbacks.callback(ReactiveAfterConvertCallback.class, entity, document, index);
@@ -528,19 +516,8 @@ abstract public class AbstractReactiveElasticsearchTemplate
return Mono.just(document);
}
/**
* Callback to convert {@link Document} into an entity of type T
*
* @param <T> the entity type
*/
protected interface DocumentCallback<T> {
/**
* Convert a document into an entity
*
* @param document the document to convert
* @return a Mono of the entity
*/
@NonNull
Mono<T> toEntity(@Nullable Document document);
}
@@ -572,35 +549,21 @@ abstract public class AbstractReactiveElasticsearchTemplate
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( //
documentAfterLoad.hasId() ? documentAfterLoad.getId() : null, //
documentAfterLoad.hasSeqNo() ? documentAfterLoad.getSeqNo() : null, //
documentAfterLoad.hasPrimaryTerm() ? documentAfterLoad.getPrimaryTerm() : null, //
documentAfterLoad.hasVersion() ? documentAfterLoad.getVersion() : null); //
documentAfterLoad.getSeqNo(), //
documentAfterLoad.getPrimaryTerm(), //
documentAfterLoad.getVersion()); //
entity = updateIndexedObject(entity, indexedObjectInformation);
return maybeCallbackAfterConvert(entity, documentAfterLoad, index);
return maybeCallAfterConvert(entity, documentAfterLoad, index);
});
}
}
/**
* Callback to convert a {@link SearchDocument} into different other classes
* @param <T> the entity type
*/
protected interface SearchDocumentCallback<T> {
/**
* converts a {@link SearchDocument} to an entity
* @param searchDocument
* @return the entity in a MOno
*/
Mono<T> toEntity(SearchDocument searchDocument);
Mono<T> toEntity(SearchDocument response);
/**
* converts a {@link SearchDocument} into a SearchHit
* @param searchDocument
* @return
*/
Mono<SearchHit<T>> toSearchHit(SearchDocument searchDocument);
Mono<SearchHit<T>> toSearchHit(SearchDocument response);
}
protected class ReadSearchDocumentCallback<T> implements SearchDocumentCallback<T> {
@@ -648,20 +611,47 @@ abstract public class AbstractReactiveElasticsearchTemplate
* @return the vendor name of the used cluster and client library
* @since 4.3
*/
public abstract Mono<String> getVendor();
abstract protected Mono<String> getVendor();
/**
* @return the version of the used client runtime library.
* @since 4.3
*/
public abstract Mono<String> getRuntimeLibraryVersion();
abstract protected Mono<String> getRuntimeLibraryVersion();
public abstract Mono<String> getClusterVersion();
abstract protected Mono<String> getClusterVersion();
/**
* Value class to capture client independent information from a response to an index request.
*/
public record IndexResponseMetaData(String id, long seqNo, long primaryTerm, long version) {
public static class IndexResponseMetaData {
private final String id;
private final long seqNo;
private final long primaryTerm;
private final long version;
public IndexResponseMetaData(String id, long seqNo, long primaryTerm, long version) {
this.id = id;
this.seqNo = seqNo;
this.primaryTerm = primaryTerm;
this.version = version;
}
public String getId() {
return id;
}
public long getSeqNo() {
return seqNo;
}
public long getPrimaryTerm() {
return primaryTerm;
}
public long getVersion() {
return version;
}
}
// endregion
@@ -680,8 +670,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
}
public List<IndexQuery> indexQueries() {
return entities.stream().map(AbstractReactiveElasticsearchTemplate.this::getIndexQuery)
.collect(Collectors.toList());
return entities.stream().map(value -> getIndexQuery(value)).collect(Collectors.toList());
}
public T entityAt(long index) {
@@ -20,7 +20,7 @@ package org.springframework.data.elasticsearch.core;
*
* @author Peter-Josef Meisch
*/
public record ActiveShardCount(int value) {
public class ActiveShardCount {
private static final int ACTIVE_SHARD_COUNT_DEFAULT = -2;
private static final int ALL_ACTIVE_SHARDS = -1;
@@ -28,4 +28,14 @@ public record ActiveShardCount(int value) {
public static final ActiveShardCount ALL = new ActiveShardCount(ALL_ACTIVE_SHARDS);
public static final ActiveShardCount NONE = new ActiveShardCount(0);
public static final ActiveShardCount ONE = new ActiveShardCount(1);
private final int value;
public ActiveShardCount(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}

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