1
0
mirror of synced 2026-07-07 02:30:01 +00:00

Compare commits

..

44 Commits

Author SHA1 Message Date
Spring Builds bad6afdecc Release version 5.0 M6 (2022.0.0).
See #2222
2022-09-19 14:15:27 +00:00
Spring Builds a6a5e3f7f5 Prepare 5.0 M6 (2022.0.0).
See #2222
2022-09-19 14:13:03 +00:00
Peter-Josef Meisch b3a7a44db1 Adapt xml-namespace setup to the new Elasticsearch client.
Original Pull Request #2295
Closes #21294
2022-09-10 17:05:32 +02:00
Peter-Josef Meisch ccf136c8e2 CDI implementation uses the new Elasticsearch client.
Original Pull Request #2293
Closes #2292
2022-09-10 11:09:43 +02:00
Peter-Josef Meisch 77da22598a Fix NPE in RequestFactory when language in UpdateQuery is not set (4.4.x).
Original Pull Request #2288
Closes #2287

(cherry picked from commit 988736dd41)
2022-09-03 08:09:19 +02:00
Peter-Josef Meisch 86634ceb38 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
2022-08-29 20:14:11 +02:00
Peter-Josef Meisch 8377f64a8a Fix update call in reactive client (Elasticsearch 7 client)
Original Pull Request #2281 
Closes #2276
2022-08-26 08:06:37 +02:00
Peter-Josef Meisch b549601d34 Polishing documentation.
Adding missing documentation about the new HttpHeaders class

Original Pull Request #2279
See #2277
2022-08-24 18:37:54 +02:00
Peter-Josef Meisch 3298ba21ce Use custom HttpHeaders.
Original Pull Request #2278
Closes #2277
2022-08-24 13:10:27 +02:00
Peter-Josef Meisch 18be558740 Reactive implementation of the point in time API.
This PR adds the reactive implementation for the point in time API that was missing in #2273.

Original Pull Request #2275
Closes #2274
2022-08-20 18:23:50 +02:00
Peter-Josef Meisch 46cd4cd59e Implement the point in time API.
Original Pull Request #2273
Closes #1684
2022-08-17 13:11:11 +02:00
Peter-Josef Meisch a4ed7300d1 Code cleanup (Java 17).
Original Pull Request #2271
Closes #2270
2022-08-13 15:07:47 +02:00
Peter-Josef Meisch b511756b2b Polishing.
See #2265
2022-08-12 07:27:07 +02:00
Peter-Josef Meisch 68ba4cd39b Polishing.
See #2267
2022-08-11 06:53:07 +02:00
cdalexndr 6b39b39d35 support RuntimeField with no script.
Original Pull Request #2268
Closes #2267
2022-08-11 06:31:17 +02:00
Peter-Josef Meisch 4f4c99ec1f Fix javadoc error.
See #2265
2022-08-10 21:48:05 +02:00
Sascha Woo 6cfb8eeea6 [elc] add support for sorting results by SortOptions to NativeQuery.
Original Pull Request #2264
Closes #2263
2022-08-10 21:34:17 +02:00
Peter-Josef Meisch c4910bf399 Polishing.
See #2265
2022-08-10 21:19:46 +02:00
Sascha Woo c93039410a [elc] add convenient methods to improve access to aggregations.
Original Pull Request #2266
Closes #2265
2022-08-10 21:10:17 +02:00
Peter-Josef Meisch 33c9180ccd Reenable test for the new Elasticsearch Client.
Original Pull Request #2261
Closes #2260
2022-08-07 20:33:07 +02:00
Peter-Josef Meisch df5fd0b97c sourcefilter documentation.
Original Pull Request #2258
Closes #2257
2022-08-07 12:22:46 +02:00
Peter-Josef Meisch 44a79093ce Remove deprecated code.
Original Pull request #2256
Closes #2255
2022-08-06 23:16:38 +02:00
Peter-Josef Meisch cf135f4cdd Implement SourceFilters annotation.
Original Pull Request #2254
Closes #1280
Closes #2062
Closes #2146
Closes #2147
Closes #2151
2022-08-06 21:04:13 +02:00
Peter-Josef Meisch 954d8b0f97 Prefix bean names created by the configuration with "elasticsearch".
Original Pull Request #2253
Closes #2252
2022-08-05 21:14:06 +02:00
Peter-Josef Meisch e71c66949a Add missing properties to BaseQueryBuilder.
Original Pull Request #2251
Closes #2250
2022-08-04 17:50:37 +02:00
Peter-Josef Meisch acf02a1dc9 Don't try to write non-writeable properties.
Original Pull Request #2249
Closes #2230
2022-08-04 08:04:19 +02:00
Peter-Josef Meisch 6ad449c9f4 Polishing.
See #2247
2022-08-01 21:37:06 +02:00
lanicc 94ebc40582 Fix the JDK version required to build from the source code.
Original Pull Request #2248
Closes #2247
2022-08-01 21:25:10 +02:00
Peter-Josef Meisch ca39c1d24c Adapt to changes in Elasticsearch client code. (#2246)
Closes #2211
2022-07-31 18:33:43 +02:00
Peter-Josef Meisch 2049726bcb Dependency updates.
Original Pull Request #2245
Closes #2243
2022-07-30 20:42:12 +02:00
Peter-Josef Meisch 7620222f89 Upgrade to Elasticsearch 8.3.3.
Original Pull Request #2244
Closes #2242
2022-07-30 18:34:22 +02:00
Peter-Josef Meisch 1f1076aa8b Don't rely on collection to object conversion in spring-data-commons.
Original Pull Request #2241
Closes #2152
2022-07-28 07:41:07 +02:00
Peter-Josef Meisch 3e950b8053 SimpleElasticsearchRepository still uses erhlc code.
Original Pull Request #2239
Closes #2238
2022-07-24 22:03:01 +02:00
Peter-Josef Meisch 1c31a31e11 Refine the configuration callbacks in the ClientConfiguration.
Original Pull Request #2237
Closes #2149
2022-07-23 15:32:24 +02:00
Peter-Josef Meisch 373be49f97 Fix exists query for imperative repository implementation.
Original Pull Request #2236
Closes #2162
2022-07-22 21:56:24 +02:00
Peter-Josef Meisch 12b332223f Replace ClassTypeInformation usages with TypeInformation.
Original Pull Request #2235
Closes #2234
2022-07-19 22:37:05 +02:00
Peter-Josef Meisch 47c0e186ec Adapt to spring-data-commons changes.
Original Pull Request #2233
Closes #2232
2022-07-19 22:12:56 +02:00
Peter-Josef Meisch 8b69a8a030 Complete searchMsearchRequest for the new client.
Original Pull Request #2231
Closes #2156
2022-07-19 21:31:14 +02:00
Mark Paluch 8bb0d8cb41 Polishing.
Consistently use credential properties.

See #2222
2022-07-18 09:19:36 +02:00
Peter-Josef Meisch 26a2f54c0f Change visibility of InetSocketAddressParser.
Original Pull Request #2227
Closes #2226
2022-07-17 20:15:03 +02:00
Peter-Josef Meisch 1754f59e70 Upgrade to Elasticsearch 8.3.2.
Original Pull Request #2225
Closes #2214
2022-07-17 08:02:23 +02:00
Patrick Baumgartner 8a166a1a40 Fixing Typo.
Original Pull Request #2219
Closes #2223
2022-07-16 17:55:45 +02:00
Christoph Strobl 5c029602b5 After release cleanups.
See #2167
2022-07-15 15:30:55 +02:00
Christoph Strobl 82a545912e Prepare next development iteration.
See #2167
2022-07-15 15:30:47 +02:00
207 changed files with 6530 additions and 4070 deletions
Vendored
+4 -4
View File
@@ -32,13 +32,13 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
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'
@@ -63,7 +63,7 @@ pipeline {
options { timeout(time: 20, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
+4 -4
View File
@@ -1,4 +1,3 @@
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]]
@@ -40,7 +39,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 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.
It should be possible to use the Elasticsearch 7 client to access a cluster running version 8 by setting the appropriate compatibility headers (see the documentation at https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.clients.configuration). but I encountered and heard of cases where the response from the server is not parseable by the client although the headers are set, so use with care.
== Code of Conduct
@@ -191,7 +190,9 @@ 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 also need JDK 1.8.
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.
[source,bash]
----
@@ -205,7 +206,6 @@ _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.
+16 -12
View File
@@ -5,12 +5,12 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>5.0.0-M5</version>
<version>5.0.0-M6</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M6</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -18,17 +18,21 @@
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
<properties>
<!-- version of the RestHighLevelClient -->
<elasticsearch-rhlc>7.17.4</elasticsearch-rhlc>
<!-- version of the new ElasticsearchClient -->
<elasticsearch-java>8.3.1</elasticsearch-java>
<springdata.commons>3.0.0-M6</springdata.commons>
<log4j>2.17.2</log4j>
<!-- version of the RestHighLevelClient -->
<elasticsearch-rhlc>7.17.5</elasticsearch-rhlc>
<!-- version of the new ElasticsearchClient -->
<elasticsearch-java>8.3.3</elasticsearch-java>
<log4j>2.18.0</log4j>
<netty>4.1.65.Final</netty>
<springdata.commons>3.0.0-M5</springdata.commons>
<testcontainers>1.16.2</testcontainers>
<blockhound-junit>1.0.6.RELEASE</blockhound-junit>
<hoverfly>0.14.2</hoverfly>
<jsonassert>1.5.1</jsonassert>
<testcontainers>1.17.3</testcontainers>
<wiremock>2.33.2</wiremock>
<java-module-name>spring.data.elasticsearch</java-module-name>
@@ -303,14 +307,14 @@
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>1.5.0</version>
<version>${jsonassert}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.32.0</version>
<version>${wiremock}</version>
<scope>test</scope>
<exclusions>
<!-- these exclusions are needed because of Elasticsearch JarHell-->
@@ -328,7 +332,7 @@
<dependency>
<groupId>io.specto</groupId>
<artifactId>hoverfly-java-junit5</artifactId>
<version>0.14.1</version>
<version>${hoverfly}</version>
<scope>test</scope>
</dependency>
+5 -5
View File
@@ -29,15 +29,15 @@ Requires an installation of https://www.elastic.co/products/elasticsearch[Elasti
[[preface.versions]]
=== Versions
The following table shows the Elasticsearch versions that are used by Spring Data release trains and version of
Spring Data Elasticsearch included in that, as well as the Spring Boot versions referring to that particular Spring
Data release train. The Elasticsearch version given shows with which client libraries Spring Data Elasticsearch was
The following table shows the Elasticsearch versions that are used by Spring Data release trains and version of
Spring Data Elasticsearch included in that, as well as the Spring Boot versions referring to that particular Spring
Data release train. The Elasticsearch version given shows with which client libraries Spring Data Elasticsearch was
built and tested.
[cols="^,^,^,^,^",options="header"]
|===
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot
| 2022.0 (Turing) | 5.0.x | 8.3.1 | 6.0.x | 3.0.x?
| 2022.0 (Turing) | 5.0.x | 8.3.3 | 6.0.x | 3.0.x?
| 2021.2 (Raj) | 4.4.x | 7.17.3 | 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
@@ -49,5 +49,5 @@ 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
Support for upcoming versions of Elasticsearch is being tracked and general compatibility should be given assuming
the usage of the <<elasticsearch.operations,ElasticsearchOperations interface>>.
@@ -38,16 +38,17 @@ ElasticsearchClient elasticsearchClient; <.>
@Autowired
RestClient restClient; <.>
----
the following can be injected:
<.> an implementation of `ElasticsearchOperations`
<.> the `co.elastic.clients.elasticsearch.ElasticsearchClient` that is used. This is new Elasticsearch client
implementation.
<.> an implementation of `ElasticsearchOperations`
<.> the `co.elastic.clients.elasticsearch.ElasticsearchClient` that is used.
This is new Elasticsearch client implementation.
<.> 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.
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
@@ -81,21 +82,22 @@ ReactiveElasticsearchClient elasticsearchClient; <.>
@Autowired
RestClient restClient; <.>
----
the following can be injected:
<.> an implementation of `ElasticsearchOperations`
<.> the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient` that is used. This is based
on the new Elasticsearch client implementation.
<.> the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient` that is used.
This is based on the new 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.
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)
The Java RestHighLevelClient is deprecated, but still can be configured like shown (read
The Java RestHighLevelClient is deprecated, but still can be configured like shown (read
<<elasticsearch-migration-guide-4.4-5.0.old-client>> as well):
.RestHighLevelClient
@@ -231,12 +233,49 @@ Default is 5 sec.
<.> Optionally set headers.
<.> Add basic authentication.
<.> 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.
<.> a function configuring the low level REST client (the same for the imperative and reactive stack)
<.> a function to configure the created client (see <<elasticsearch.clients.configuration.callbacks>>), can be added multiple times.
====
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens.
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:
==== Configuration of the low level Elasticsearch `RestClient`:
====
[source,java]
----
ClientConfiguration.builder()
.withClientConfigurer(ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
// configure the Elasticsearch RestClient
return restClientBuilder;
}))
.build();
----
====
==== Configuration of the HttpAsyncClient used by the low level Elasticsearch `RestClient`:
====
[source,java]
----
ClientConfiguration.builder()
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
// configure the HttpAsyncClient
return httpAsyncClientBuilder;
}))
.build();
----
====
=== 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
@@ -1,17 +1,24 @@
[[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.
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
=== `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>>
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>>
=== 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
@@ -20,34 +27,57 @@ Elasticsearch, see <<elasticsearch-migration-guide-4.4-5.0.new-clients>>
==== 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.
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.
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.
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.
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.
=== 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`
=== 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`.
Spring Data Elasticsearch now uses the new `ElasticsearchClient` and has deprecated the use of the previous `RestHighLevelClient`.
=== 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`:
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]
@@ -101,8 +131,7 @@ With this configuration, the following beans will be available in the Spring app
[[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:
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]
@@ -111,7 +140,7 @@ your application as Spring Data Elasticsearch does not pull it in automatically
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.4</version>
<version>7.17.5</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
@@ -122,4 +151,4 @@ your application as Spring Data Elasticsearch does not pull it in automatically
----
====
Make sure to specify the version 7.17.4 explicitly, otherwise maven will resolve to 8.3.1, and this does not exist.
Make sure to specify the version 7.17.5 explicitly, otherwise maven will resolve to 8.3.3, and this does not exist.
@@ -121,7 +121,7 @@ Query searchQuery = NativeQuery.builder()
.withPageable(PageRequest.of(0, 10))
.build();
SearchHitsIterator<SampleEntity> stream = elasticsearchOperations.searchForStream(searchQuery, SampleEntity.class,
SearchHitsIterator<SampleEntity> stream = elasticsearchOperations.searchForStream(searchQuery, SampleEntity.class,
index);
List<SampleEntity> sampleEntities = new ArrayList<>();
@@ -134,7 +134,7 @@ 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
the following methods of the `AbstractElasticsearchTemplate` can be used (this is the base implementation for the
different `ElasticsearchOperations` implementations:
====
@@ -275,3 +275,42 @@ 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
====
@@ -53,7 +53,7 @@ interface BookRepository extends Repository<Book, String> {
@HighlightField(name = "name"),
@HighlightField(name = "summary")
})
List<SearchHit<Book>> findByNameOrSummary(String text, String summary);
SearchHits<Book> findByNameOrSummary(String text, String summary);
}
----
====
@@ -62,6 +62,30 @@ 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.
=== @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
@@ -16,9 +16,11 @@
package org.springframework.data.elasticsearch.annotations;
/**
* 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.
* 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>.
*
* @author Jakub Vavrik
* @author Tim te Beek
@@ -26,19 +28,6 @@ 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"), //
@@ -60,7 +49,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 it's implementation
date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in its implementation
epoch_millis("epoch_millis"), //
epoch_second("epoch_second"), //
hour("HH"), //
@@ -53,49 +53,6 @@ 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,40 +0,0 @@
/*
* Copyright 2019-2022 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;
/**
* 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
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD })
@Documented
@Deprecated
public @interface DynamicMapping {
DynamicMappingValue value() default DynamicMappingValue.True;
}
@@ -53,9 +53,8 @@ public @interface Field {
String value() default "";
/**
* 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.
* 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.
*
* @since 3.2
*/
@@ -15,8 +15,14 @@
*/
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
@@ -34,16 +40,17 @@ import java.lang.annotation.*;
public @interface Query {
/**
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0. Alias for query.
*/
@AliasFor("query")
String value() default "";
/**
* Named Query Named looked up by repository.
*
* @deprecated since 4.2, not implemented and used anywhere
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0. Alias for value
* @since 5.0
*/
String name() default "";
@AliasFor("value")
String query() default "";
/**
* Returns whether the query defined should be executed as count projection.
@@ -0,0 +1,73 @@
/*
* Copyright 2022 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 "";
}
@@ -26,10 +26,7 @@ import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveRestClients;
import org.springframework.data.elasticsearch.client.erhlc.RestClients;
import org.springframework.http.HttpHeaders;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
@@ -171,14 +168,6 @@ 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
@@ -347,29 +336,6 @@ 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,7 +22,6 @@ 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;
@@ -31,9 +30,7 @@ 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.client.erhlc.ReactiveRestClients;
import org.springframework.data.elasticsearch.client.erhlc.RestClients;
import org.springframework.http.HttpHeaders;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
@@ -52,7 +49,7 @@ class ClientConfigurationBuilder
implements ClientConfigurationBuilderWithRequiredEndpoint, MaybeSecureClientConfigurationBuilder {
private final List<InetSocketAddress> hosts = new ArrayList<>();
private HttpHeaders headers = HttpHeaders.EMPTY;
private HttpHeaders headers = new HttpHeaders();
private boolean useSsl;
private @Nullable SSLContext sslContext;
private @Nullable HostnameVerifier hostnameVerifier;
@@ -62,10 +59,10 @@ class ClientConfigurationBuilder
private @Nullable String password;
private @Nullable String pathPrefix;
private @Nullable String proxy;
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<>();
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<>();
/*
* (non-Javadoc)
@@ -76,7 +73,7 @@ class ClientConfigurationBuilder
Assert.notEmpty(hostAndPorts, "At least one host is required");
this.hosts.addAll(Arrays.stream(hostAndPorts).map(ClientConfigurationBuilder::parse).collect(Collectors.toList()));
this.hosts.addAll(Arrays.stream(hostAndPorts).map(ClientConfigurationBuilder::parse).toList());
return this;
}
@@ -202,28 +199,6 @@ 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) {
@@ -251,9 +226,6 @@ class ClientConfigurationBuilder
public ClientConfiguration build() {
if (username != null && password != null) {
if (HttpHeaders.EMPTY.equals(headers)) {
headers = new HttpHeaders();
}
headers.setBasicAuth(username, password);
}
@@ -19,8 +19,6 @@ 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.http.HttpStatusCode;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
@@ -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 HttpStatusCode statusCode) {
public static void logRawResponse(String logId, @Nullable Integer statusCode) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Received raw response: %s", logId, statusCode));
WIRE_LOGGER.trace(String.format("[%s] Received raw response: %d", 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 HttpStatus statusCode, String headers) {
public static void logRawResponse(String logId, @Nullable Integer statusCode, String headers) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Received response: %s%n%s", logId, statusCode, headers));
WIRE_LOGGER.trace(String.format("[%s] Received response: %d%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, HttpStatusCode statusCode, String body) {
public static void logResponse(String logId, Integer statusCode, String body) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Received response: %s%nResponse body: %s", logId, statusCode, body));
WIRE_LOGGER.trace(String.format("[%s] Received response: %d%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 HttpStatus statusCode, String headers, String body) {
public static void logResponse(String logId, @Nullable Integer statusCode, String headers, String body) {
if (isEnabled()) {
WIRE_LOGGER.trace(String.format("[%s] Received response: %s%nHeaders: %s%nResponse body: %s", logId, statusCode,
WIRE_LOGGER.trace(String.format("[%s] Received response: %d%nHeaders: %s%nResponse body: %s", logId, statusCode,
headers, body));
}
}
@@ -17,8 +17,6 @@ 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;
@@ -28,7 +26,7 @@ import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.http.HttpHeaders;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
@@ -63,8 +61,8 @@ class DefaultClientConfiguration implements ClientConfiguration {
Function<WebClient, WebClient> webClientConfigurer, HttpClientConfigCallback httpClientConfigurer,
List<ClientConfigurationCallback<?>> clientConfigurers, Supplier<HttpHeaders> headersSupplier) {
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
this.headers = new HttpHeaders(headers);
this.hosts = List.copyOf(hosts);
this.headers = headers;
this.useSsl = useSsl;
this.sslContext = sslContext;
this.soTimeout = soTimeout;
@@ -129,13 +127,6 @@ 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
*/
class InetSocketAddressParser {
public class InetSocketAddressParser {
/**
* Parse a host and port string into a {@link InetSocketAddress}.
@@ -36,7 +36,7 @@ class InetSocketAddressParser {
* @return a {@link InetSocketAddress} that is unresolved to avoid DNS lookups.
* @see InetSocketAddress#createUnresolved(String, int)
*/
static InetSocketAddress parse(String hostPortString, int defaultPort) {
public static InetSocketAddress parse(String hostPortString, int defaultPort) {
Assert.notNull(hostPortString, "HostPortString must not be null");
String host;
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2022 the original author or authors.
* Copyright 2022 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,27 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
package org.springframework.data.elasticsearch.client;
/**
* values for the {@link DynamicMapping annotation}
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
@Deprecated
public enum DynamicMappingValue {
True("true"), False("false"), Strict("strict");
private final String mappedName;
DynamicMappingValue(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
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));
}
}
@@ -18,6 +18,7 @@ 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;
@@ -32,7 +33,7 @@ import org.springframework.util.Assert;
* @author Peter-Josef Meisch
* @since 4.4
*/
public abstract class ChildTemplate<CLIENT extends ApiClient> {
public abstract class ChildTemplate<T extends Transport, CLIENT extends ApiClient<T, CLIENT>> {
protected final CLIENT client;
protected final RequestConverter requestConverter;
@@ -18,6 +18,7 @@ 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;
@@ -29,7 +30,8 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte
* @author Peter-Josef Meisch
* @since 4.4
*/
public class ClusterTemplate extends ChildTemplate<ElasticsearchClusterClient> implements ClusterOperations {
public class ClusterTemplate extends ChildTemplate<ElasticsearchTransport, 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);
break;
case BBOX:
}
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);
break;
case GEO_INTERSECTS:
}
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");
break;
case GEO_IS_DISJOINT:
}
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");
break;
case GEO_WITHIN:
}
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");
break;
case GEO_CONTAINS:
}
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,8 +176,7 @@ class CriteriaFilterProcessor {
.distance(dist) //
.distanceType(GeoDistanceType.Plane) //
.location(location -> {
if (values[0] instanceof GeoPoint) {
GeoPoint loc = (GeoPoint) values[0];
if (values[0]instanceof GeoPoint loc) {
location.latlon(latlon -> latlon.lat(loc.getLat()).lon(loc.getLon()));
} else if (values[0] instanceof Point) {
GeoPoint loc = GeoPoint.fromPoint((Point) values[0]);
@@ -195,7 +194,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.");
@@ -240,13 +239,12 @@ 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) {
GeoPoint topLeft = (GeoPoint) values[0];
if (values[0]instanceof GeoPoint topLeft) {
GeoPoint bottomRight = (GeoPoint) values[1];
queryBuilder.boundingBox(bb -> bb //
.tlbr(tlbr -> tlbr //
@@ -329,12 +327,8 @@ class CriteriaFilterProcessor {
StringBuilder sb = new StringBuilder();
sb.append((int) distance.getValue());
switch ((Metrics) distance.getMetric()) {
case KILOMETERS:
sb.append("km");
break;
case MILES:
sb.append("mi");
break;
case KILOMETERS -> sb.append("km");
case MILES -> sb.append("mi");
}
return sb.toString();
@@ -288,8 +288,7 @@ class CriteriaQueryProcessor {
break;
case IN:
if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) value;
if (value instanceof Iterable<?> iterable) {
if (isKeywordField) {
queryBuilder.bool(bb -> bb //
.must(mb -> mb //
@@ -310,8 +309,7 @@ class CriteriaQueryProcessor {
}
break;
case NOT_IN:
if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) value;
if (value instanceof Iterable<?> iterable) {
if (isKeywordField) {
queryBuilder.bool(bb -> bb //
.mustNot(mnb -> mnb //
@@ -73,7 +73,7 @@ final class DocumentAdapters {
Map<String, SearchDocumentResponse> innerHits = new LinkedHashMap<>();
hit.innerHits().forEach((name, innerHitsResult) -> {
// noinspection ReturnOfNull
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null,
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, null,
searchDocument -> null, jsonpMapper));
});
@@ -101,12 +101,12 @@ final class DocumentAdapters {
EntityAsMap hitFieldsAsMap = fromFields.apply(hit.fields());
Map<String, List<Object>> documentFields = new LinkedHashMap<>();
hitFieldsAsMap.entrySet().forEach(entry -> {
if (entry.getValue() instanceof List) {
hitFieldsAsMap.forEach((key, value) -> {
if (value instanceof List) {
// noinspection unchecked
documentFields.put(entry.getKey(), (List<Object>) entry.getValue());
documentFields.put(key, (List<Object>) value);
} else {
documentFields.put(entry.getKey(), Collections.singletonList(entry.getValue()));
documentFields.put(key, Collections.singletonList(value));
}
});
@@ -117,8 +117,7 @@ final class DocumentAdapters {
} else {
if (source instanceof EntityAsMap) {
document = Document.from((EntityAsMap) source);
} else if (source instanceof JsonData) {
JsonData jsonData = (JsonData) source;
} else if (source instanceof JsonData jsonData) {
document = Document.from(jsonData.to(EntityAsMap.class));
} else {
@@ -18,45 +18,61 @@ 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(List<ElasticsearchAggregation> aggregations) {
public ElasticsearchAggregations(Map<String, Aggregate> aggregations) {
Assert.notNull(aggregations, "aggregations must not be null");
this.aggregations = aggregations;
}
aggregationsAsMap = new HashMap<>();
aggregations.forEach((name, aggregate) -> aggregationsAsMap //
.put(name, new ElasticsearchAggregation(new Aggregation(name, aggregate))));
/**
* 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))));
this.aggregations = new ArrayList<>(aggregationsAsMap.values());
}
@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);
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2022 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;
}
}
@@ -0,0 +1,97 @@
/*
* Copyright 2022 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;
}
}
@@ -52,11 +52,9 @@ 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.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.data.elasticsearch.support.HttpHeaders;
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
@@ -229,14 +227,20 @@ public final class ElasticsearchClients {
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurer instanceof ElasticsearchClientConfigurationCallback) {
ElasticsearchClientConfigurationCallback restClientConfigurationCallback = (ElasticsearchClientConfigurationCallback) clientConfigurer;
if (clientConfigurer instanceof ElasticsearchHttpClientConfigurationCallback restClientConfigurationCallback) {
clientBuilder = restClientConfigurationCallback.configure(clientBuilder);
}
}
return clientBuilder;
});
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurationCallback : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurationCallback instanceof ElasticsearchRestClientConfigurationCallback configurationCallback) {
builder = configurationCallback.configure(builder);
}
}
return builder;
}
@@ -300,9 +304,9 @@ public final class ElasticsearchClients {
+ ((header.getName().equals("Authorization")) ? ": *****" : ": " + header.getValue()))
.collect(Collectors.joining(", ", "[", "]"));
if (request instanceof HttpEntityEnclosingRequest && ((HttpEntityEnclosingRequest) request).getEntity() != null) {
if (request instanceof HttpEntityEnclosingRequest entityRequest
&& ((HttpEntityEnclosingRequest) request).getEntity() != null) {
HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request;
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
entity.writeTo(buffer);
@@ -331,7 +335,7 @@ public final class ElasticsearchClients {
// 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, HttpStatus.resolve(response.getStatusLine().getStatusCode()), headers);
ClientLogger.logRawResponse(logId, response.getStatusLine().getStatusCode(), headers);
}
}
@@ -352,7 +356,7 @@ public final class ElasticsearchClients {
public void process(HttpRequest request, HttpContext context) {
HttpHeaders httpHeaders = headersSupplier.get();
if (httpHeaders != null && httpHeaders != HttpHeaders.EMPTY) {
if (httpHeaders != null && !httpHeaders.isEmpty()) {
Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader);
}
}
@@ -360,37 +364,37 @@ public final class ElasticsearchClients {
/**
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
* the RestClient with a {@link HttpAsyncClientBuilder}
* the Elasticsearch RestClient's Http client with a {@link HttpAsyncClientBuilder}
*
* @since 4.4
*/
public interface ElasticsearchClientConfigurationCallback
public interface ElasticsearchHttpClientConfigurationCallback
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
static ElasticsearchClientConfigurationCallback from(
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> clientBuilderCallback) {
static ElasticsearchHttpClientConfigurationCallback from(
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> httpClientBuilderCallback) {
Assert.notNull(clientBuilderCallback, "clientBuilderCallback must not be null");
Assert.notNull(httpClientBuilderCallback, "httpClientBuilderCallback must not be null");
// noinspection NullableProblems
return clientBuilderCallback::apply;
return httpClientBuilderCallback::apply;
}
}
/**
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
* the ReactiveElasticsearchClient with a {@link WebClient}
* the RestClient client with a {@link RestClientBuilder}
*
* @since 4.4
* @since 5.0
*/
public interface WebClientConfigurationCallback extends ClientConfiguration.ClientConfigurationCallback<WebClient> {
public interface ElasticsearchRestClientConfigurationCallback
extends ClientConfiguration.ClientConfigurationCallback<RestClientBuilder> {
static WebClientConfigurationCallback from(Function<WebClient, WebClient> webClientCallback) {
static ElasticsearchRestClientConfigurationCallback from(
Function<RestClientBuilder, RestClientBuilder> restClientBuilderCallback) {
Assert.notNull(webClientCallback, "webClientCallback must not be null");
Assert.notNull(restClientBuilderCallback, "restClientBuilderCallback must not be null");
// noinspection NullableProblems
return webClientCallback::apply;
return restClientBuilderCallback::apply;
}
}
}
@@ -42,7 +42,7 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
*
* @return configuration, must not be {@literal null}
*/
@Bean
@Bean(name="elasticsearchClientConfiguration")
public abstract ClientConfiguration clientConfiguration();
/**
@@ -52,7 +52,7 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
* @return RestClient
*/
@Bean
public RestClient restClient(ClientConfiguration clientConfiguration) {
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
@@ -31,7 +31,6 @@ 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
@@ -72,14 +71,13 @@ 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 elasticsearchException = (ElasticsearchException) ex;
if (ex instanceof ElasticsearchException elasticsearchException) {
ErrorResponse response = elasticsearchException.response();
if (response.status() == HttpStatus.NOT_FOUND.value()
&& "index_not_found_exception".equals(response.error().type())) {
if (response.status() == 404 && "index_not_found_exception".equals(response.error().type())) {
// noinspection RegExpRedundantEscape
Pattern pattern = Pattern.compile(".*no such index \\[(.*)\\]");
String index = "";
Matcher matcher = pattern.matcher(response.error().reason());
@@ -109,9 +107,7 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
Integer status = null;
String message = null;
if (exception instanceof ResponseException) {
ResponseException responseException = (ResponseException) exception;
if (exception instanceof ResponseException responseException) {
status = responseException.getResponse().getStatusLine().getStatusCode();
message = responseException.getMessage();
} else if (exception.getCause() != 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.ElasticsearchClient;
@@ -28,6 +27,7 @@ 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,6 +80,17 @@ 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);
@@ -275,11 +286,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
@Override
public String getClusterVersion() {
return execute(client -> {
try (var ignored = DANGEROUS_disableRequiredPropertiesCheck(true)) {
return client.info().version().number();
}
});
return execute(client -> client.info().version().number());
}
@Override
@@ -480,17 +487,30 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
/**
* value class combining the information needed for a single query in a multisearch request.
*/
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;
}
record MultiSearchQueryParameter(Query query, Class<?> clazz, IndexCoordinates 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,6 +18,7 @@ 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;
@@ -56,7 +57,8 @@ import org.springframework.util.Assert;
* @author Peter-Josef Meisch
* @since 4.4
*/
public class IndicesTemplate extends ChildTemplate<ElasticsearchIndicesClient> implements IndexOperations {
public class IndicesTemplate extends ChildTemplate<ElasticsearchTransport, ElasticsearchIndicesClient>
implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(IndicesTemplate.class);
@@ -187,7 +189,6 @@ public class IndicesTemplate extends ChildTemplate<ElasticsearchIndicesClient> i
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) {
@@ -15,6 +15,7 @@
*/
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;
@@ -26,7 +27,6 @@ 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;
@@ -35,6 +35,7 @@ import org.springframework.lang.Nullable;
* Elasticsearch Client library.
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.4
*/
public class NativeQuery extends BaseQuery {
@@ -46,7 +47,7 @@ public class NativeQuery extends BaseQuery {
@Nullable private Suggester suggester;
@Nullable private FieldCollapse fieldCollapse;
private List<ScriptedField> scriptedFields = Collections.emptyList();
private List<RescorerQuery> rescorerQueries = Collections.emptyList();
private List<SortOptions> sortOptions = Collections.emptyList();
public NativeQuery(NativeQueryBuilder builder) {
super(builder);
@@ -56,7 +57,7 @@ public class NativeQuery extends BaseQuery {
this.suggester = builder.getSuggester();
this.fieldCollapse = builder.getFieldCollapse();
this.scriptedFields = builder.getScriptedFields();
this.rescorerQueries = builder.getRescorerQueries();
this.sortOptions = builder.getSortOptions();
}
public NativeQuery(@Nullable Query query) {
@@ -95,8 +96,7 @@ public class NativeQuery extends BaseQuery {
return scriptedFields;
}
@Override
public List<RescorerQuery> getRescorerQueries() {
return rescorerQueries;
public List<SortOptions> getSortOptions() {
return sortOptions;
}
}
@@ -15,6 +15,7 @@
*/
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;
@@ -22,19 +23,21 @@ import co.elastic.clients.elasticsearch.core.search.Suggester;
import co.elastic.clients.util.ObjectBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
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> {
@@ -45,9 +48,10 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
@Nullable private Suggester suggester;
@Nullable private FieldCollapse fieldCollapse;
private final List<ScriptedField> scriptedFields = new ArrayList<>();
private List<RescorerQuery> rescorerQueries = new ArrayList<>();
private List<SortOptions> sortOptions = new ArrayList<>();
public NativeQueryBuilder() {}
public NativeQueryBuilder() {
}
@Nullable
public Query getQuery() {
@@ -77,8 +81,8 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
return scriptedFields;
}
public List<RescorerQuery> getRescorerQueries() {
return rescorerQueries;
public List<SortOptions> getSortOptions() {
return sortOptions;
}
public NativeQueryBuilder withQuery(Query query) {
@@ -135,11 +139,31 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
return this;
}
public NativeQueryBuilder withResorerQuery(RescorerQuery resorerQuery) {
public NativeQueryBuilder withSort(List<SortOptions> values) {
Assert.notNull(resorerQuery, "resorerQuery must not be null");
Assert.notEmpty(values, "values must not be empty");
sortOptions.clear();
sortOptions.addAll(values);
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());
this.rescorerQueries.add(resorerQuery);
return this;
}
@@ -17,6 +17,7 @@ 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;
@@ -29,18 +30,17 @@ import org.springframework.util.Assert;
* @author Peter-Josef Meisch
* @since 4.4
*/
public class ReactiveChildTemplate<CLIENT extends ApiClient> {
public class ReactiveChildTemplate<T extends Transport, CLIENT extends ApiClient<T, CLIENT>> {
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 = client._transport().jsonpMapper();
JsonpMapper jsonpMapper = client._transport().jsonpMapper();
requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper);
responseConverter = new ResponseConverter(jsonpMapper);
exceptionTranslator = new ElasticsearchExceptionTranslator(jsonpMapper);
@@ -17,6 +17,7 @@ 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;
@@ -27,7 +28,8 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte
* @author Peter-Josef Meisch
* @since 4.4
*/
public class ReactiveClusterTemplate extends ReactiveChildTemplate<ReactiveElasticsearchClusterClient>
public class ReactiveClusterTemplate
extends ReactiveChildTemplate<ElasticsearchTransport, ReactiveElasticsearchClusterClient>
implements ReactiveClusterOperations {
public ReactiveClusterTemplate(ReactiveElasticsearchClusterClient client,
@@ -276,6 +276,48 @@ 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
@Bean(name="elasticsearchClientConfiguration")
public abstract ClientConfiguration clientConfiguration();
/**
@@ -51,7 +51,7 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
* @return RestClient
*/
@Bean
public RestClient restClient(ClientConfiguration clientConfiguration) {
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
@@ -30,6 +30,7 @@ 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;
@@ -327,26 +328,25 @@ 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 -> {
return Mono
.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client1 -> client1
.search(searchRequest, EntityAsMap.class))) //
.expand(entityAsMapSearchResponse -> {
state -> 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);
}));
});
}, this::cleanupScroll, (state, ex) -> cleanupScroll(state), this::cleanupScroll);
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())
.map(entityAsMapHit -> DocumentAdapters.from(entityAsMapHit, jsonpMapper));
@@ -409,6 +409,30 @@ 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
@@ -19,6 +19,7 @@ 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;
@@ -53,7 +54,8 @@ import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
*/
public class ReactiveIndicesTemplate extends ReactiveChildTemplate<ReactiveElasticsearchIndicesClient>
public class ReactiveIndicesTemplate
extends ReactiveChildTemplate<ElasticsearchTransport, ReactiveElasticsearchIndicesClient>
implements ReactiveIndexOperations {
@Nullable private final Class<?> boundClass;
@@ -152,7 +154,7 @@ public class ReactiveIndicesTemplate extends ReactiveChildTemplate<ReactiveElast
@Override
public Mono<Void> refresh() {
return Mono.from(execute(client1 -> client1.refresh())).then();
return Mono.from(execute(ReactiveElasticsearchIndicesClient::refresh)).then();
}
@Override
@@ -32,25 +32,18 @@ import co.elastic.clients.elasticsearch._types.mapping.RuntimeFieldType;
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.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.SearchRequest;
import co.elastic.clients.elasticsearch.core.UpdateByQueryRequest;
import co.elastic.clients.elasticsearch.core.UpdateRequest;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
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.*;
import co.elastic.clients.elasticsearch.indices.ExistsRequest;
import co.elastic.clients.elasticsearch.indices.update_aliases.Action;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.JsonpDeserializer;
@@ -100,6 +93,8 @@ import org.springframework.util.StringUtils;
* Class to create Elasticsearch request and request builders.
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @author cdalxndr
* @since 4.4
*/
class RequestConverter {
@@ -181,8 +176,7 @@ class RequestConverter {
Action.Builder actionBuilder = new Action.Builder();
if (aliasAction instanceof AliasAction.Add) {
AliasAction.Add add = (AliasAction.Add) aliasAction;
if (aliasAction instanceof AliasAction.Add add) {
AliasActionParameters parameters = add.getParameters();
actionBuilder.add(addActionBuilder -> {
addActionBuilder //
@@ -211,8 +205,7 @@ class RequestConverter {
});
}
if (aliasAction instanceof AliasAction.Remove) {
AliasAction.Remove remove = (AliasAction.Remove) aliasAction;
if (aliasAction instanceof AliasAction.Remove remove) {
AliasActionParameters parameters = remove.getParameters();
actionBuilder.remove(removeActionBuilder -> {
removeActionBuilder.indices(Arrays.asList(parameters.getIndices()));
@@ -225,8 +218,7 @@ class RequestConverter {
});
}
if (aliasAction instanceof AliasAction.RemoveIndex) {
AliasAction.RemoveIndex removeIndex = (AliasAction.RemoveIndex) aliasAction;
if (aliasAction instanceof AliasAction.RemoveIndex removeIndex) {
AliasActionParameters parameters = removeIndex.getParameters();
actionBuilder.removeIndex(
removeIndexActionBuilder -> removeIndexActionBuilder.indices(Arrays.asList(parameters.getIndices())));
@@ -434,7 +426,6 @@ class RequestConverter {
* so the code needs to be duplicated.
*/
@SuppressWarnings("DuplicatedCode")
public IndexRequest<?> documentIndexRequest(IndexQuery query, IndexCoordinates indexCoordinates,
@Nullable RefreshPolicy refreshPolicy) {
@@ -474,12 +465,8 @@ class RequestConverter {
if (query.getOpType() != null) {
switch (query.getOpType()) {
case INDEX:
builder.opType(OpType.Index);
break;
case CREATE:
builder.opType(OpType.Create);
break;
case INDEX -> builder.opType(OpType.Index);
case CREATE -> builder.opType(OpType.Create);
}
}
@@ -622,20 +609,18 @@ class RequestConverter {
Map<String, JsonData> params = new HashMap<>();
if (scriptData.getParams() != null) {
scriptData.getParams().forEach((key, value) -> {
params.put(key, JsonData.of(value, jsonpMapper));
});
if (scriptData.params() != null) {
scriptData.params().forEach((key, value) -> params.put(key, JsonData.of(value, jsonpMapper)));
}
return co.elastic.clients.elasticsearch._types.Script.of(sb -> {
if (scriptData.getType() == ScriptType.INLINE) {
if (scriptData.type() == ScriptType.INLINE) {
sb.inline(is -> is //
.lang(scriptData.getLanguage()) //
.source(scriptData.getScript()) //
.lang(scriptData.language()) //
.source(scriptData.script()) //
.params(params)); //
} else if (scriptData.getType() == ScriptType.STORED) {
} else if (scriptData.type() == ScriptType.STORED) {
sb.stored(ss -> ss //
.id(scriptData.getScript()) //
.id(scriptData.script()) //
.params(params) //
);
}
@@ -658,7 +643,7 @@ class RequestConverter {
}
if (bulkOptions.getWaitForActiveShards() != null) {
builder.waitForActiveShards(wasb -> wasb.count(bulkOptions.getWaitForActiveShards().getValue()));
builder.waitForActiveShards(wasb -> wasb.count(bulkOptions.getWaitForActiveShards().value()));
}
if (bulkOptions.getPipeline() != null) {
@@ -671,16 +656,14 @@ class RequestConverter {
List<BulkOperation> operations = queries.stream().map(query -> {
BulkOperation.Builder ob = new BulkOperation.Builder();
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
if (query instanceof IndexQuery indexQuery) {
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 updateQuery = (UpdateQuery) query;
} else if (query instanceof UpdateQuery updateQuery) {
ob.update(bulkUpdateOperation(updateQuery, indexCoordinates, refreshPolicy));
}
return ob.build();
@@ -729,8 +712,8 @@ class RequestConverter {
List<MultiGetOperation> multiGetOperations = query.getIdsWithRouting().stream()
.map(idWithRouting -> MultiGetOperation.of(mgo -> mgo //
.index(index.getIndexName()) //
.id(idWithRouting.getId()) //
.routing(idWithRouting.getRouting()) //
.id(idWithRouting.id()) //
.routing(idWithRouting.routing()) //
.source(sourceConfig)))
.collect(Collectors.toList());
@@ -885,9 +868,7 @@ 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 -> {
@@ -1030,6 +1011,7 @@ class RequestConverter {
Assert.notNull(query, "query must not be null");
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);
@@ -1047,22 +1029,119 @@ 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())) //
// todo #2156 add remaining flags for header
) //
.body(bb -> bb //
.query(getQuery(param.query, param.clazz))//
.seqNoPrimaryTerm(persistentEntity.hasSeqNoPrimaryTermProperty()).version(true)
// todo #2156 add remaining flags for body
.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(Object::toString).collect(Collectors.toList()));
}
query.getRescorerQueries().forEach(rescorerQuery -> bb.rescore(getRescore(rescorerQuery)));
if (!query.getRuntimeFields().isEmpty()) {
Map<String, List<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(), Collections.singletonList(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;
} //
) //
);
});
return mrb;
});
}
@@ -1077,10 +1156,24 @@ 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);
}
@@ -1118,10 +1211,6 @@ class RequestConverter {
builder.minScore((double) query.getMinScore());
}
if (query.getPreference() != null) {
builder.preference(query.getPreference());
}
builder.searchType(searchType(query.getSearchType()));
if (query.getSort() != null) {
@@ -1146,10 +1235,6 @@ 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()) {
@@ -1160,20 +1245,18 @@ class RequestConverter {
builder.searchAfter(query.getSearchAfter().stream().map(Object::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, List<RuntimeField>> runtimeMappings = new HashMap<>();
query.getRuntimeFields().forEach(runtimeField -> {
runtimeMappings.put(runtimeField.getName(), Collections.singletonList(RuntimeField.of(rt -> rt //
.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType())) //
.script(s -> s.inline(is -> is.source(runtimeField.getScript()))))));
});
query.getRuntimeFields()
.forEach(runtimeField -> runtimeMappings.put(runtimeField.getName(),
Collections.singletonList(RuntimeField.of(rt -> rt //
.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType())) //
.script(s -> s.inline(is -> is.source(runtimeField.getScript())))))));
builder.runtimeMappings(runtimeMappings);
}
@@ -1192,9 +1275,8 @@ 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);
}
@@ -1225,6 +1307,16 @@ 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());
}
@@ -1235,8 +1327,7 @@ class RequestConverter {
Order.Mode mode = Order.DEFAULT_MODE;
String unmappedType = null;
if (order instanceof Order) {
Order o = (Order) order;
if (order instanceof Order o) {
mode = o.getMode();
unmappedType = o.getUnmappedType();
}
@@ -1250,8 +1341,7 @@ class RequestConverter {
String fieldName = property != null ? property.getFieldName() : order.getProperty();
Order.Mode finalMode = mode;
if (order instanceof GeoDistanceOrder) {
GeoDistanceOrder geoDistanceOrder = (GeoDistanceOrder) order;
if (order instanceof GeoDistanceOrder geoDistanceOrder) {
return SortOptions.of(so -> so //
.geoDistance(gd -> gd //
@@ -1289,16 +1379,34 @@ 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());
}
// todo #2150 searchExt, currently not supported by the new client
}
@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());
@@ -1323,8 +1431,7 @@ 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 nativeQuery = (NativeQuery) query;
} else if (query instanceof NativeQuery nativeQuery) {
if (nativeQuery.getQuery() != null) {
esQuery = nativeQuery.getQuery();
@@ -1398,6 +1505,27 @@ 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
@@ -1458,20 +1586,12 @@ class RequestConverter {
.getVersionType();
if (entityVersionType != null) {
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;
}
versionType = switch (entityVersionType) {
case INTERNAL -> VersionType.Internal;
case EXTERNAL -> VersionType.External;
case EXTERNAL_GTE -> VersionType.ExternalGte;
case FORCE -> VersionType.Force;
};
}
}
@@ -1510,15 +1630,11 @@ class RequestConverter {
return null;
}
switch (refreshPolicy) {
case IMMEDIATE:
return true;
case WAIT_UNTIL:
return null;
case NONE:
default:
return false;
}
return switch (refreshPolicy) {
case IMMEDIATE -> true;
case WAIT_UNTIL -> null;
case NONE -> false;
};
}
// endregion
@@ -17,7 +17,13 @@ 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.*;
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;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.json.JsonpMapper;
import java.util.ArrayList;
@@ -59,7 +65,6 @@ 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) {
@@ -70,8 +75,9 @@ 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, aggregations, suggest, entityCreator, jsonpMapper);
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
}
/**
@@ -88,8 +94,9 @@ class SearchDocumentResponseBuilder {
* @return the {@link SearchDocumentResponse}
*/
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable String scrollId,
@Nullable Map<String, Aggregate> aggregations, Map<String, List<Suggestion<EntityAsMap>>> suggestES,
SearchDocumentResponse.EntityCreator<T> entityCreator, JsonpMapper jsonpMapper) {
@Nullable String pointInTimeId, @Nullable Map<String, Aggregate> aggregations,
Map<String, List<Suggestion<EntityAsMap>>> suggestES, SearchDocumentResponse.EntityCreator<T> entityCreator,
JsonpMapper jsonpMapper) {
Assert.notNull(hitsMetadata, "hitsMetadata must not be null");
@@ -99,16 +106,11 @@ class SearchDocumentResponseBuilder {
TotalHits responseTotalHits = hitsMetadata.total();
if (responseTotalHits != null) {
totalHits = responseTotalHits.value();
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();
}
totalHitsRelation = switch (responseTotalHits.relation().jsonValue()) {
case "eq" -> TotalHitsRelation.EQUAL_TO.name();
case "gte" -> TotalHitsRelation.GREATER_THAN_OR_EQUAL_TO.name();
default -> TotalHitsRelation.OFF.name();
};
} else {
totalHits = hitsMetadata.hits().size();
totalHitsRelation = "OFF";
@@ -126,7 +128,7 @@ class SearchDocumentResponseBuilder {
Suggest suggest = suggestFrom(suggestES, entityCreator);
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments,
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchDocuments,
aggregationsContainer, suggest);
}
@@ -145,20 +147,19 @@ class SearchDocumentResponseBuilder {
if (!suggestionsES.isEmpty()) {
// take the type from the first entry
switch (suggestionsES.get(0)._kind()) {
case Term: {
case Term -> {
suggestions.add(getTermSuggestion(name, suggestionsES));
break;
}
case Phrase: {
case Phrase -> {
suggestions.add(getPhraseSuggestion(name, suggestionsES));
break;
}
case Completion: {
case Completion -> {
suggestions.add(getCompletionSuggestion(name, suggestionsES, entityCreator));
break;
}
default:
break;
default -> {}
}
}
});
@@ -173,12 +174,11 @@ class SearchDocumentResponseBuilder {
List<TermSuggestion.Entry> entries = new ArrayList<>();
suggestionsES.forEach(suggestionES -> {
TermSuggest termSuggest = suggestionES.term();
TermSuggestOption optionES = termSuggest.options();
var termSuggest = suggestionES.term();
var termSuggestOptions = termSuggest.options();
List<TermSuggestion.Entry.Option> options = new ArrayList<>();
options.add(new TermSuggestion.Entry.Option(optionES.text(), null, optionES.score(), null,
Math.toIntExact(optionES.freq())));
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);
@@ -188,10 +188,11 @@ class SearchDocumentResponseBuilder {
List<PhraseSuggestion.Entry> entries = new ArrayList<>();
suggestionsES.forEach(suggestionES -> {
PhraseSuggest phraseSuggest = suggestionES.phrase();
PhraseSuggestOption optionES = phraseSuggest.options();
var phraseSuggest = suggestionES.phrase();
var phraseSuggestOptions = phraseSuggest.options();
List<PhraseSuggestion.Entry.Option> options = new ArrayList<>();
options.add(new PhraseSuggestion.Entry.Option(optionES.text(), optionES.highlighted(), null, null));
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));
});
@@ -49,64 +49,38 @@ final class TypeUtils {
static BoundaryScanner boundaryScanner(@Nullable String value) {
if (value != null) {
switch (value.toLowerCase()) {
case "chars":
return BoundaryScanner.Chars;
case "sentence":
return BoundaryScanner.Sentence;
case "word":
return BoundaryScanner.Word;
default:
return null;
}
return switch (value.toLowerCase()) {
case "chars" -> BoundaryScanner.Chars;
case "sentence" -> BoundaryScanner.Sentence;
case "word" -> BoundaryScanner.Word;
default -> null;
};
}
return null;
}
static Conflicts conflicts(ReindexRequest.Conflicts conflicts) {
switch (conflicts) {
case ABORT:
return Conflicts.Abort;
case PROCEED:
return Conflicts.Proceed;
}
throw new IllegalArgumentException("Cannot map conflicts value " + conflicts.name());
return switch (conflicts) {
case ABORT -> Conflicts.Abort;
case PROCEED -> Conflicts.Proceed;
};
}
@Nullable
static DistanceUnit distanceUnit(String unit) {
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;
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;
};
}
@Nullable
@@ -124,28 +98,22 @@ final class TypeUtils {
@Nullable
static GeoDistanceType geoDistanceType(GeoDistanceOrder.DistanceType distanceType) {
switch (distanceType) {
case arc:
return GeoDistanceType.Arc;
case plane:
return GeoDistanceType.Plane;
}
return switch (distanceType) {
case arc -> GeoDistanceType.Arc;
case plane -> GeoDistanceType.Plane;
};
return null;
}
@Nullable
static HighlighterFragmenter highlighterFragmenter(@Nullable String value) {
if (value != null) {
switch (value.toLowerCase()) {
case "simple":
return HighlighterFragmenter.Simple;
case "span":
return HighlighterFragmenter.Span;
default:
return null;
}
return switch (value.toLowerCase()) {
case "simple" -> HighlighterFragmenter.Simple;
case "span" -> HighlighterFragmenter.Span;
default -> null;
};
}
return null;
@@ -167,16 +135,12 @@ final class TypeUtils {
static HighlighterType highlighterType(@Nullable String value) {
if (value != null) {
switch (value.toLowerCase()) {
case "unified":
return HighlighterType.Unified;
case "plain":
return HighlighterType.Plain;
case "fvh":
return HighlighterType.FastVector;
default:
return null;
}
return switch (value.toLowerCase()) {
case "unified" -> HighlighterType.Unified;
case "plain" -> HighlighterType.Plain;
case "fvh" -> HighlighterType.FastVector;
default -> null;
};
}
return null;
@@ -186,14 +150,11 @@ final class TypeUtils {
static HighlighterEncoder highlighterEncoder(@Nullable String value) {
if (value != null) {
switch (value.toLowerCase()) {
case "default":
return HighlighterEncoder.Default;
case "html":
return HighlighterEncoder.Html;
default:
return null;
}
return switch (value.toLowerCase()) {
case "default" -> HighlighterEncoder.Default;
case "html" -> HighlighterEncoder.Html;
default -> null;
};
}
return null;
@@ -215,12 +176,10 @@ final class TypeUtils {
static OpType opType(@Nullable IndexQuery.OpType opType) {
if (opType != null) {
switch (opType) {
case INDEX:
return OpType.Index;
case CREATE:
return OpType.Create;
}
return switch (opType) {
case INDEX -> OpType.Index;
case CREATE -> OpType.Create;
};
}
return null;
}
@@ -231,15 +190,11 @@ final class TypeUtils {
return Refresh.False;
}
switch (refreshPolicy) {
case IMMEDIATE:
return Refresh.True;
case WAIT_UNTIL:
return Refresh.WaitFor;
case NONE:
default:
return Refresh.False;
}
return switch (refreshPolicy) {
case IMMEDIATE -> Refresh.True;
case WAIT_UNTIL -> Refresh.WaitFor;
case NONE -> Refresh.False;
};
}
@Nullable
@@ -249,20 +204,14 @@ final class TypeUtils {
return null;
}
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 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;
};
return null;
}
@Nullable
@@ -272,22 +221,15 @@ final class TypeUtils {
return null;
}
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 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;
};
return null;
}
@Nullable
@@ -297,14 +239,11 @@ final class TypeUtils {
return null;
}
switch (searchType) {
case QUERY_THEN_FETCH:
return SearchType.QueryThenFetch;
case DFS_QUERY_THEN_FETCH:
return SearchType.DfsQueryThenFetch;
}
return switch (searchType) {
case QUERY_THEN_FETCH -> SearchType.QueryThenFetch;
case DFS_QUERY_THEN_FETCH -> SearchType.DfsQueryThenFetch;
};
return null;
}
@Nullable
@@ -320,18 +259,13 @@ final class TypeUtils {
@Nullable
static SortMode sortMode(Order.Mode mode) {
switch (mode) {
case min:
return SortMode.Min;
case max:
return SortMode.Max;
case median:
return SortMode.Median;
case avg:
return SortMode.Avg;
}
return switch (mode) {
case min -> SortMode.Min;
case max -> SortMode.Max;
case median -> SortMode.Median;
case avg -> SortMode.Avg;
};
return null;
}
@Nullable
@@ -359,16 +293,12 @@ final class TypeUtils {
@Nullable org.springframework.data.elasticsearch.annotations.Document.VersionType versionType) {
if (versionType != null) {
switch (versionType) {
case INTERNAL:
return VersionType.Internal;
case EXTERNAL:
return VersionType.External;
case EXTERNAL_GTE:
return VersionType.ExternalGte;
case FORCE:
return VersionType.Force;
}
return switch (versionType) {
case INTERNAL -> VersionType.Internal;
case EXTERNAL -> VersionType.External;
case EXTERNAL_GTE -> VersionType.ExternalGte;
case FORCE -> VersionType.Force;
};
}
return null;
@@ -105,37 +105,37 @@ class CriteriaFilterProcessor {
QueryBuilder filter = 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.");
filter = withinQuery(fieldName, (Object[]) value);
break;
case BBOX:
}
case BBOX -> {
Assert.isTrue(value instanceof Object[],
"Value of a boundedBy filter should be an array of one or two values.");
filter = boundingBoxQuery(fieldName, (Object[]) value);
break;
case GEO_INTERSECTS:
}
case GEO_INTERSECTS -> {
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_INTERSECTS filter must be a GeoJson object");
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "intersects");
break;
case GEO_IS_DISJOINT:
}
case GEO_IS_DISJOINT -> {
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_IS_DISJOINT filter must be a GeoJson object");
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "disjoint");
break;
case GEO_WITHIN:
}
case GEO_WITHIN -> {
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_WITHIN filter must be a GeoJson object");
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "within");
break;
case GEO_CONTAINS:
}
case GEO_CONTAINS -> {
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_CONTAINS filter must be a GeoJson object");
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "contains");
break;
}
}
return filter;
}
private QueryBuilder withinQuery(String fieldName, Object[] valArray) {
private QueryBuilder withinQuery(String fieldName, Object... valArray) {
GeoDistanceQueryBuilder filter = QueryBuilders.geoDistanceQuery(fieldName);
@@ -154,8 +154,7 @@ class CriteriaFilterProcessor {
dist.append((String) valArray[1]);
}
if (valArray[0] instanceof GeoPoint) {
GeoPoint loc = (GeoPoint) valArray[0];
if (valArray[0]instanceof GeoPoint loc) {
filter.point(loc.getLat(), loc.getLon()).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
} else if (valArray[0] instanceof Point) {
GeoPoint loc = GeoPoint.fromPoint((Point) valArray[0]);
@@ -174,7 +173,7 @@ class CriteriaFilterProcessor {
return filter;
}
private QueryBuilder boundingBoxQuery(String fieldName, Object[] valArray) {
private QueryBuilder boundingBoxQuery(String fieldName, Object... valArray) {
Assert.noNullElements(valArray, "Geo boundedBy filter takes a not null element array as parameter.");
@@ -217,12 +216,8 @@ class CriteriaFilterProcessor {
Metrics metric = (Metrics) distance.getMetric();
switch (metric) {
case KILOMETERS:
sb.append("km");
break;
case MILES:
sb.append("mi");
break;
case KILOMETERS -> sb.append("km");
case MILES -> sb.append("mi");
}
}
@@ -250,11 +245,10 @@ class CriteriaFilterProcessor {
return true;
}
private void twoParameterBBox(GeoBoundingBoxQueryBuilder filter, Object[] values) {
private void twoParameterBBox(GeoBoundingBoxQueryBuilder filter, Object... values) {
Assert.isTrue(isType(values, GeoPoint.class) || isType(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) {
GeoPoint topLeft = (GeoPoint) values[0];
if (values[0]instanceof GeoPoint topLeft) {
GeoPoint bottomRight = (GeoPoint) values[1];
filter.setCorners(topLeft.getLat(), topLeft.getLon(), bottomRight.getLat(), bottomRight.getLon());
} else {
@@ -175,17 +175,10 @@ class CriteriaQueryProcessor {
// operations without a value
switch (key) {
case EXISTS:
query = existsQuery(fieldName);
break;
case EMPTY:
query = boolQuery().must(existsQuery(fieldName)).mustNot(wildcardQuery(fieldName, "*"));
break;
case NOT_EMPTY:
query = wildcardQuery(fieldName, "*");
break;
default:
break;
case EXISTS -> query = existsQuery(fieldName);
case EMPTY -> query = boolQuery().must(existsQuery(fieldName)).mustNot(wildcardQuery(fieldName, "*"));
case NOT_EMPTY -> query = wildcardQuery(fieldName, "*");
default -> {}
}
if (query != null) {
@@ -238,8 +231,7 @@ class CriteriaQueryProcessor {
query = matchQuery(fieldName, value).operator(org.elasticsearch.index.query.Operator.AND);
break;
case IN:
if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) value;
if (value instanceof Iterable<?> iterable) {
if (isKeywordField) {
query = boolQuery().must(termsQuery(fieldName, toStringList(iterable)));
} else {
@@ -248,8 +240,7 @@ class CriteriaQueryProcessor {
}
break;
case NOT_IN:
if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) value;
if (value instanceof Iterable<?> iterable) {
if (isKeywordField) {
query = boolQuery().mustNot(termsQuery(fieldName, toStringList(iterable)));
} else {
@@ -139,7 +139,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
private final HostProvider<?> hostProvider;
private final RequestCreator requestCreator;
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
private Supplier<org.springframework.data.elasticsearch.support.HttpHeaders> headersSupplier = org.springframework.data.elasticsearch.support.HttpHeaders::new;
/**
* Create a new {@link DefaultReactiveElasticsearchClient} using the given {@link HostProvider} to obtain server
@@ -181,8 +181,10 @@ 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(headers).build();
.withDefaultHeaders(httpHeaders).build();
return create(clientConfiguration);
}
@@ -226,7 +228,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
return client;
}
public void setHeadersSupplier(Supplier<HttpHeaders> headersSupplier) {
public void setHeadersSupplier(Supplier<org.springframework.data.elasticsearch.support.HttpHeaders> headersSupplier) {
Assert.notNull(headersSupplier, "headersSupplier must not be null");
@@ -716,25 +718,25 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
if (RawActionResponse.class.equals(responseType)) {
ClientLogger.logRawResponse(logId, response.statusCode());
ClientLogger.logRawResponse(logId, response.statusCode().value());
return Mono.just(responseType.cast(RawActionResponse.create(response)));
}
if (response.statusCode().is5xxServerError()) {
ClientLogger.logRawResponse(logId, response.statusCode());
ClientLogger.logRawResponse(logId, response.statusCode().value());
return handleServerError(request, response);
}
if (response.statusCode().is4xxClientError()) {
ClientLogger.logRawResponse(logId, response.statusCode());
ClientLogger.logRawResponse(logId, response.statusCode().value());
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(), it)) //
.doOnNext(it -> ClientLogger.logResponse(logId, response.statusCode().value(), it)) //
.flatMap(content -> doDecode(response, responseType, content));
}
@@ -811,7 +813,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(), content)) //
.doOnNext(content -> ClientLogger.logResponse(logId, response.statusCode().value(), content)) //
.flatMap(content -> doDecode(response, responseType, content));
}
@@ -717,10 +717,9 @@ public final class DocumentAdapters {
if (this == o) {
return true;
}
if (!(o instanceof SearchDocumentAdapter)) {
if (!(o instanceof SearchDocumentAdapter that)) {
return false;
}
SearchDocumentAdapter that = (SearchDocumentAdapter) o;
return Float.compare(that.score, score) == 0 && delegate.equals(that.delegate);
}
@@ -56,17 +56,14 @@ 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 elasticsearchException = (ElasticsearchException) ex;
if (ex instanceof ElasticsearchException elasticsearchException) {
if (!indexAvailable(elasticsearchException)) {
return new NoSuchIndexException(ObjectUtils.nullSafeToString(elasticsearchException.getMetadata("es.index")),
ex);
}
if (elasticsearchException instanceof ElasticsearchStatusException) {
ElasticsearchStatusException elasticsearchStatusException = (ElasticsearchStatusException) elasticsearchException;
if (elasticsearchException instanceof ElasticsearchStatusException elasticsearchStatusException) {
return new RestStatusException(elasticsearchStatusException.status().getStatus(),
elasticsearchStatusException.getMessage(), elasticsearchStatusException);
}
@@ -74,11 +71,9 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
return new UncategorizedElasticsearchException(ex.getMessage(), ex);
}
if (ex instanceof RestStatusException) {
RestStatusException restStatusException = (RestStatusException) ex;
if (ex instanceof RestStatusException restStatusException) {
Throwable cause = restStatusException.getCause();
if (cause instanceof ElasticsearchException) {
ElasticsearchException elasticsearchException = (ElasticsearchException) cause;
if (cause instanceof ElasticsearchException elasticsearchException) {
if (!indexAvailable(elasticsearchException)) {
return new NoSuchIndexException(ObjectUtils.nullSafeToString(elasticsearchException.getMetadata("es.index")),
@@ -104,16 +99,14 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
Integer status = null;
String message = null;
if (exception instanceof ElasticsearchStatusException) {
if (exception instanceof ElasticsearchStatusException statusException) {
ElasticsearchStatusException statusException = (ElasticsearchStatusException) exception;
status = statusException.status().getStatus();
message = statusException.getMessage();
}
if (exception instanceof RestStatusException) {
if (exception instanceof RestStatusException statusException) {
RestStatusException statusException = (RestStatusException) exception;
status = statusException.getStatus();
message = statusException.getMessage();
}
@@ -123,9 +116,7 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
&& message.contains("version conflict, required seqNo");
}
if (exception instanceof VersionConflictEngineException) {
VersionConflictEngineException versionConflictEngineException = (VersionConflictEngineException) exception;
if (exception instanceof VersionConflictEngineException versionConflictEngineException) {
return versionConflictEngineException.getMessage() != null
&& versionConflictEngineException.getMessage().contains("version conflict, required seqNo");
@@ -133,9 +133,8 @@ public class HighlightQueryBuilder {
builder.highlighterType(parameters.getType());
}
if (builder instanceof HighlightBuilder && parameters instanceof HighlightParameters) {
HighlightBuilder highlightBuilder = (HighlightBuilder) builder;
HighlightParameters highlightParameters = (HighlightParameters) parameters;
if (builder instanceof HighlightBuilder highlightBuilder
&& parameters instanceof HighlightParameters highlightParameters) {
if (StringUtils.hasLength(highlightParameters.getEncoder())) {
highlightBuilder.encoder(highlightParameters.getEncoder());
@@ -146,9 +145,8 @@ public class HighlightQueryBuilder {
}
}
if (builder instanceof HighlightBuilder.Field && parameters instanceof HighlightFieldParameters) {
HighlightBuilder.Field field = (HighlightBuilder.Field) builder;
HighlightFieldParameters fieldParameters = (HighlightFieldParameters) parameters;
if (builder instanceof HighlightBuilder.Field field
&& parameters instanceof HighlightFieldParameters fieldParameters) {
if ((fieldParameters).getFragmentOffset() > -1) {
field.fragmentOffset(fieldParameters.getFragmentOffset());
@@ -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.http.HttpHeaders;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
@@ -81,7 +81,9 @@ import org.springframework.web.reactive.function.client.WebClient;
* @since 3.2
* @see ClientConfiguration
* @see ReactiveRestClients
* @deprecated since 5.0
*/
@Deprecated
public interface ReactiveElasticsearchClient {
/**
@@ -72,7 +72,9 @@ 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) {
@@ -23,8 +23,10 @@ import org.springframework.web.reactive.function.client.WebClientException;
* @author Christoph Strobl
* @author Mark Paluch
* @since 3.2
* @deprecated since 5.0
*/
public class RequestBodyEncodingException extends WebClientException {
@Deprecated public class RequestBodyEncodingException extends WebClientException {
private static final long serialVersionUID = 472776714118912855L;
@@ -337,8 +337,13 @@ public class RequestConverters {
public static Request index(IndexRequest indexRequest) {
String method = Strings.hasLength(indexRequest.id()) ? HttpMethod.PUT.name() : HttpMethod.POST.name();
boolean isCreate = (indexRequest.opType() == DocWriteRequest.OpType.CREATE);
String endpoint = endpoint(indexRequest.index(), indexRequest.type(), indexRequest.id(),
isCreate ? "_create" : null);
String endpoint;
if (indexRequest.opType() == DocWriteRequest.OpType.CREATE) {
endpoint = indexRequest.type().equals("_doc") ? endpoint(indexRequest.index(), "_create", indexRequest.id())
: endpoint(indexRequest.index(), indexRequest.type(), indexRequest.id(), "_create");
} else {
endpoint = endpoint(indexRequest.index(), indexRequest.type(), indexRequest.id());
}
Request request = new Request(method, endpoint);
Params parameters = new Params(request);
@@ -363,7 +368,9 @@ public class RequestConverters {
}
public static Request update(UpdateRequest updateRequest) {
String endpoint = endpoint(updateRequest.index(), updateRequest.type(), updateRequest.id(), "_update");
String endpoint = updateRequest.type().equals("_doc")
? endpoint(updateRequest.index(), "_update", updateRequest.id())
: endpoint(updateRequest.index(), updateRequest.type(), updateRequest.id(), "_update");
Request request = new Request(HttpMethod.POST.name(), endpoint);
Params parameters = new Params(request);
@@ -501,8 +508,11 @@ public class RequestConverters {
}
public static Request explain(ExplainRequest explainRequest) {
String endpoint = explainRequest.type().equals("_doc")
? endpoint(explainRequest.index(), "_explain", explainRequest.id())
: endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), "_explain");
Request request = new Request(HttpMethod.GET.name(),
endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), "_explain"));
endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), endpoint));
Params params = new Params(request);
params.withStoredFields(explainRequest.storedFields());
@@ -816,7 +826,7 @@ public class RequestConverters {
}
public static Request indexCreate(org.elasticsearch.client.indices.CreateIndexRequest createIndexRequest) {
String endpoint = RequestConverters.endpoint(new String[] { createIndexRequest.index() });
String endpoint = RequestConverters.endpoint(createIndexRequest.index());
Request request = new Request(HttpMethod.PUT.name(), endpoint);
Params parameters = new Params(request);
@@ -1048,7 +1058,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();
}
@@ -1061,7 +1071,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();
}
@@ -1129,7 +1139,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));
}
@@ -1190,7 +1200,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));
}
@@ -1313,14 +1323,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));
}
@@ -1423,7 +1433,7 @@ public class RequestConverters {
return this;
}
EndpointBuilder addCommaSeparatedPathParts(String[] parts) {
EndpointBuilder addCommaSeparatedPathParts(String... parts) {
addPathPart(String.join(",", parts));
return this;
}
@@ -166,8 +166,7 @@ class RequestFactory {
IndicesAliasesRequest.AliasActions aliasActionsES = null;
if (aliasAction instanceof AliasAction.Add) {
AliasAction.Add add = (AliasAction.Add) aliasAction;
if (aliasAction instanceof AliasAction.Add add) {
IndicesAliasesRequest.AliasActions addES = IndicesAliasesRequest.AliasActions.add();
AliasActionParameters parameters = add.getParameters();
@@ -193,8 +192,7 @@ class RequestFactory {
}
aliasActionsES = addES;
} else if (aliasAction instanceof AliasAction.Remove) {
AliasAction.Remove remove = (AliasAction.Remove) aliasAction;
} else if (aliasAction instanceof AliasAction.Remove remove) {
IndicesAliasesRequest.AliasActions removeES = IndicesAliasesRequest.AliasActions.remove();
AliasActionParameters parameters = remove.getParameters();
@@ -202,8 +200,7 @@ class RequestFactory {
removeES.aliases(parameters.getAliases());
aliasActionsES = removeES;
} else if (aliasAction instanceof AliasAction.RemoveIndex) {
AliasAction.RemoveIndex removeIndex = (AliasAction.RemoveIndex) aliasAction;
} else if (aliasAction instanceof AliasAction.RemoveIndex removeIndex) {
IndicesAliasesRequest.AliasActions removeIndexES = IndicesAliasesRequest.AliasActions.removeIndex();
AliasActionParameters parameters = removeIndex.getParameters();
@@ -235,7 +232,7 @@ class RequestFactory {
}
if (bulkOptions.getWaitForActiveShards() != null) {
bulkRequest.waitForActiveShards(ActiveShardCount.from(bulkOptions.getWaitForActiveShards().getValue()));
bulkRequest.waitForActiveShards(ActiveShardCount.from(bulkOptions.getWaitForActiveShards().value()));
}
if (bulkOptions.getPipeline() != null) {
@@ -590,9 +587,9 @@ class RequestFactory {
String indexName = index.getIndexName();
for (Query.IdWithRouting idWithRouting : searchQuery.getIdsWithRouting()) {
MultiGetRequest.Item item = new MultiGetRequest.Item(indexName, idWithRouting.getId());
if (idWithRouting.getRouting() != null) {
item = item.routing(idWithRouting.getRouting());
MultiGetRequest.Item item = new MultiGetRequest.Item(indexName, idWithRouting.id());
if (idWithRouting.routing() != null) {
item = item.routing(idWithRouting.routing());
}
// note: multiGet does not have fields, need to set sourceContext to filter
@@ -617,7 +614,7 @@ class RequestFactory {
Object queryObject = query.getObject();
if (queryObject != null) {
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(queryObject) : query.getId();
String id = StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject);
// If we have a query id and a document id, do not ask ES to generate one.
if (id != null) {
indexRequest = new IndexRequest(indexName).id(id);
@@ -653,6 +650,7 @@ class RequestFactory {
}
if (query.getOpType() != null) {
switch (query.getOpType()) {
case INDEX:
indexRequest.opType(DocWriteRequest.OpType.INDEX);
@@ -677,8 +675,7 @@ class RequestFactory {
if (highlightBuilder == null) {
if (query instanceof NativeSearchQuery) {
NativeSearchQuery searchQuery = (NativeSearchQuery) query;
if (query instanceof NativeSearchQuery searchQuery) {
if ((searchQuery.getHighlightFields() != null && searchQuery.getHighlightFields().length > 0)
|| searchQuery.getHighlightBuilder() != null) {
@@ -868,9 +865,8 @@ class RequestFactory {
if (!query.getRuntimeFields().isEmpty()) {
Map<String, Object> runtimeMappings = new HashMap<>();
query.getRuntimeFields().forEach(runtimeField -> {
runtimeMappings.put(runtimeField.getName(), runtimeField.getMapping());
});
query.getRuntimeFields()
.forEach(runtimeField -> runtimeMappings.put(runtimeField.getName(), runtimeField.getMapping()));
sourceBuilder.runtimeMappings(runtimeMappings);
}
@@ -925,8 +921,7 @@ class RequestFactory {
query.getSort().forEach(order -> sourceBuilder.sort(getSortBuilder(order, entity)));
}
if (query instanceof NativeSearchQuery) {
NativeSearchQuery nativeSearchQuery = (NativeSearchQuery) query;
if (query instanceof NativeSearchQuery nativeSearchQuery) {
List<SortBuilder<?>> sorts = nativeSearchQuery.getElasticsearchSorts();
if (sorts != null) {
sorts.forEach(sourceBuilder::sort);
@@ -940,8 +935,7 @@ class RequestFactory {
Order.Mode mode = Order.DEFAULT_MODE;
String unmappedType = null;
if (order instanceof Order) {
Order o = (Order) order;
if (order instanceof Order o) {
mode = o.getMode();
unmappedType = o.getUnmappedType();
}
@@ -956,8 +950,7 @@ class RequestFactory {
: null;
String fieldName = property != null ? property.getFieldName() : order.getProperty();
if (order instanceof GeoDistanceOrder) {
GeoDistanceOrder geoDistanceOrder = (GeoDistanceOrder) order;
if (order instanceof GeoDistanceOrder geoDistanceOrder) {
GeoDistanceSortBuilder sort = SortBuilders.geoDistanceSort(fieldName, geoDistanceOrder.getGeoPoint().getLat(),
geoDistanceOrder.getGeoPoint().getLon());
@@ -1031,7 +1024,14 @@ class RequestFactory {
if (params == null) {
params = new HashMap<>();
}
Script script = new Script(getScriptType(query.getScriptType()), query.getLang(), query.getScript(), params);
org.elasticsearch.script.ScriptType scriptType = getScriptType(query.getScriptType());
String lang = query.getLang();
if (scriptType == org.elasticsearch.script.ScriptType.INLINE && lang == null) {
lang = "painless";
}
Script script = new Script(scriptType, lang, query.getScript(), params);
updateRequest.script(script);
}
@@ -1171,14 +1171,11 @@ class RequestFactory {
private QueryBuilder getQuery(Query query) {
QueryBuilder elasticsearchQuery;
if (query instanceof NativeSearchQuery) {
NativeSearchQuery searchQuery = (NativeSearchQuery) query;
if (query instanceof NativeSearchQuery searchQuery) {
elasticsearchQuery = searchQuery.getQuery();
} else if (query instanceof CriteriaQuery) {
CriteriaQuery criteriaQuery = (CriteriaQuery) query;
} else if (query instanceof CriteriaQuery criteriaQuery) {
elasticsearchQuery = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria());
} else if (query instanceof StringQuery) {
StringQuery stringQuery = (StringQuery) query;
} else if (query instanceof StringQuery stringQuery) {
elasticsearchQuery = wrapperQuery(stringQuery.getSource());
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
@@ -1191,11 +1188,9 @@ class RequestFactory {
private QueryBuilder getFilter(Query query) {
QueryBuilder elasticsearchFilter;
if (query instanceof NativeSearchQuery) {
NativeSearchQuery searchQuery = (NativeSearchQuery) query;
if (query instanceof NativeSearchQuery searchQuery) {
elasticsearchFilter = searchQuery.getFilter();
} else if (query instanceof CriteriaQuery) {
CriteriaQuery criteriaQuery = (CriteriaQuery) query;
} else if (query instanceof CriteriaQuery criteriaQuery) {
elasticsearchFilter = new CriteriaFilterProcessor().createFilter(criteriaQuery.getCriteria());
} else if (query instanceof StringQuery) {
elasticsearchFilter = null;
@@ -1207,15 +1202,11 @@ class RequestFactory {
}
public static WriteRequest.RefreshPolicy toElasticsearchRefreshPolicy(RefreshPolicy refreshPolicy) {
switch (refreshPolicy) {
case IMMEDIATE:
return WriteRequest.RefreshPolicy.IMMEDIATE;
case WAIT_UNTIL:
return WriteRequest.RefreshPolicy.WAIT_UNTIL;
case NONE:
default:
return WriteRequest.RefreshPolicy.NONE;
}
return switch (refreshPolicy) {
case IMMEDIATE -> WriteRequest.RefreshPolicy.IMMEDIATE;
case WAIT_UNTIL -> WriteRequest.RefreshPolicy.WAIT_UNTIL;
case NONE -> WriteRequest.RefreshPolicy.NONE;
};
}
@Nullable
@@ -46,8 +46,7 @@ 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.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.springframework.util.Assert;
/**
@@ -128,8 +127,7 @@ public final class RestClients {
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurer instanceof RestClientConfigurationCallback) {
RestClientConfigurationCallback restClientConfigurationCallback = (RestClientConfigurationCallback) clientConfigurer;
if (clientConfigurer instanceof RestClientConfigurationCallback restClientConfigurationCallback) {
clientBuilder = restClientConfigurationCallback.configure(clientBuilder);
}
}
@@ -199,9 +197,9 @@ public final class RestClients {
context.setAttribute(RestClients.LOG_ID_ATTRIBUTE, logId);
}
if (request instanceof HttpEntityEnclosingRequest && ((HttpEntityEnclosingRequest) request).getEntity() != null) {
if (request instanceof HttpEntityEnclosingRequest entityRequest
&& ((HttpEntityEnclosingRequest) request).getEntity() != null) {
HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request;
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
entity.writeTo(buffer);
@@ -220,7 +218,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, HttpStatus.resolve(response.getStatusLine().getStatusCode()));
ClientLogger.logRawResponse(logId, response.getStatusLine().getStatusCode());
}
}
@@ -241,7 +239,7 @@ public final class RestClients {
public void process(HttpRequest request, HttpContext context) {
HttpHeaders httpHeaders = headersSupplier.get();
if (httpHeaders != null && httpHeaders != HttpHeaders.EMPTY) {
if (httpHeaders != null && !httpHeaders.isEmpty()) {
Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader);
}
}
@@ -252,7 +250,9 @@ 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> {
@@ -13,23 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.config;
package org.springframework.data.elasticsearch.client.erhlc;
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.erhlc.RestClientFactoryBean;
import org.w3c.dom.Element;
/**
* @author Don Wellington
* @deprecated since 5.0
*/
public class RestClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Deprecated
public class RestHighLevelClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(RestClientFactoryBean.class);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(RestHighLevelClientFactoryBean.class);
setConfigurations(element, builder);
return getSourcedBeanDefinition(builder, element, parserContext);
}
@@ -31,16 +31,17 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* RestClientFactoryBean
* RestHighLevelClientFactoryBean
*
* @author Don Wellington
* @author Peter-Josef Meisch
* @deprecated since 5.0
*/
@Deprecated
public class RestClientFactoryBean implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
public class RestHighLevelClientFactoryBean
implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
private static final Log LOGGER = LogFactory.getLog(RestClientFactoryBean.class);
private static final Log LOGGER = LogFactory.getLog(RestHighLevelClientFactoryBean.class);
private @Nullable RestHighLevelClient client;
private String hosts = "http://localhost:9200";
@@ -113,7 +113,8 @@ public class SearchDocumentResponseBuilder {
: null;
Suggest suggest = suggestFrom(suggestES, entityCreator);
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments,
// no pointInTimeId for the deprecated implementation
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, null, searchDocuments,
aggregationsContainer, suggest);
}
@@ -129,8 +130,7 @@ public class SearchDocumentResponseBuilder {
for (org.elasticsearch.search.suggest.Suggest.Suggestion<? extends org.elasticsearch.search.suggest.Suggest.Suggestion.Entry<? extends org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option>> suggestionES : suggestES) {
if (suggestionES instanceof org.elasticsearch.search.suggest.term.TermSuggestion) {
org.elasticsearch.search.suggest.term.TermSuggestion termSuggestionES = (org.elasticsearch.search.suggest.term.TermSuggestion) suggestionES;
if (suggestionES instanceof org.elasticsearch.search.suggest.term.TermSuggestion termSuggestionES) {
List<TermSuggestion.Entry> entries = new ArrayList<>();
for (org.elasticsearch.search.suggest.term.TermSuggestion.Entry entryES : termSuggestionES) {
@@ -150,8 +150,7 @@ public class SearchDocumentResponseBuilder {
suggestFrom(termSuggestionES.getSort())));
}
if (suggestionES instanceof org.elasticsearch.search.suggest.phrase.PhraseSuggestion) {
org.elasticsearch.search.suggest.phrase.PhraseSuggestion phraseSuggestionES = (org.elasticsearch.search.suggest.phrase.PhraseSuggestion) suggestionES;
if (suggestionES instanceof org.elasticsearch.search.suggest.phrase.PhraseSuggestion phraseSuggestionES) {
List<PhraseSuggestion.Entry> entries = new ArrayList<>();
for (org.elasticsearch.search.suggest.phrase.PhraseSuggestion.Entry entryES : phraseSuggestionES) {
@@ -169,8 +168,7 @@ public class SearchDocumentResponseBuilder {
suggestions.add(new PhraseSuggestion(phraseSuggestionES.getName(), phraseSuggestionES.getSize(), entries));
}
if (suggestionES instanceof org.elasticsearch.search.suggest.completion.CompletionSuggestion) {
org.elasticsearch.search.suggest.completion.CompletionSuggestion completionSuggestionES = (org.elasticsearch.search.suggest.completion.CompletionSuggestion) suggestionES;
if (suggestionES instanceof org.elasticsearch.search.suggest.completion.CompletionSuggestion completionSuggestionES) {
List<CompletionSuggestion.Entry<T>> entries = new ArrayList<>();
for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry entryES : completionSuggestionES) {
@@ -35,7 +35,6 @@ 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;
@@ -236,13 +235,7 @@ public interface WebClientProvider {
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
.getClientConfigurers()) {
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;
if (clientConfigurer instanceof ReactiveRestClients.WebClientConfigurationCallback webClientConfigurationCallback) {
webClient = webClientConfigurationCallback.configure(webClient);
}
}
@@ -250,11 +243,12 @@ public interface WebClientProvider {
};
provider = provider //
.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
.withDefaultHeaders(HttpHeaders.readOnlyHttpHeaders(clientConfiguration.getDefaultHeaders())) //
.withWebClientConfigurer(webClientConfigurer) //
.withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec //
.headers(httpHeaders -> {
HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get();
HttpHeaders suppliedHeaders = HttpHeaders
.readOnlyHttpHeaders(clientConfiguration.getHeadersSupplier().get());
if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) {
httpHeaders.addAll(suppliedHeaders);
@@ -15,8 +15,6 @@
*/
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;
@@ -52,7 +50,7 @@ public class ScrollState {
public List<String> getScrollIds() {
synchronized (lock) {
return Collections.unmodifiableList(new ArrayList<>(pastIds));
return List.copyOf(pastIds);
}
}
@@ -16,6 +16,7 @@
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;
@@ -35,6 +36,6 @@ public class ElasticsearchNamespaceHandler extends NamespaceHandlerSupport {
RepositoryBeanDefinitionParser parser = new RepositoryBeanDefinitionParser(extension);
registerBeanDefinitionParser("repositories", parser);
registerBeanDefinitionParser("rest-client", new RestClientBeanDefinitionParser());
registerBeanDefinitionParser("elasticsearch-client", new ElasticsearchClientBeanDefinitionParser());
}
}
@@ -28,6 +28,7 @@ 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;
@@ -313,12 +314,6 @@ 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
@@ -429,6 +424,16 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
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");
}
// endregion
// region Helper methods
@@ -465,7 +470,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (indexedObjectInformation.getId() != null && idProperty != null
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isWritable()
&& idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
}
@@ -604,8 +609,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
protected void maybeCallbackBeforeConvertWithQuery(Object query, IndexCoordinates index) {
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
if (query instanceof IndexQuery indexQuery) {
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
@@ -646,8 +650,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
protected void maybeCallbackAfterSaveWithQuery(Object query, IndexCoordinates index) {
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
if (query instanceof IndexQuery indexQuery) {
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
@@ -689,8 +692,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
for (int i = 0; i < queries.size(); i++) {
Object query = queries.get(i);
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
if (query instanceof IndexQuery indexQuery) {
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
@@ -19,6 +19,7 @@ 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;
@@ -29,6 +30,7 @@ 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;
@@ -173,9 +175,8 @@ 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
@@ -228,8 +229,8 @@ abstract public class AbstractReactiveElasticsearchTemplate
SeqNoPrimaryTerm seqNoPrimaryTerm = entity.getSeqNoPrimaryTerm();
if (seqNoPrimaryTerm != null) {
query.setSeqNo(seqNoPrimaryTerm.getSequenceNumber());
query.setPrimaryTerm(seqNoPrimaryTerm.getPrimaryTerm());
query.setSeqNo(seqNoPrimaryTerm.sequenceNumber());
query.setPrimaryTerm(seqNoPrimaryTerm.primaryTerm());
usingSeqNo = true;
}
}
@@ -259,7 +260,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
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isWritable()
&& idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
}
@@ -316,10 +317,10 @@ abstract public class AbstractReactiveElasticsearchTemplate
T savedEntity = it.getT1();
IndexResponseMetaData indexResponseMetaData = it.getT2();
return updateIndexedObject(savedEntity, IndexedObjectInformation.of( //
indexResponseMetaData.getId(), //
indexResponseMetaData.getSeqNo(), //
indexResponseMetaData.getPrimaryTerm(), //
indexResponseMetaData.getVersion()));
indexResponseMetaData.id(), //
indexResponseMetaData.seqNo(), //
indexResponseMetaData.primaryTerm(), //
indexResponseMetaData.version()));
}).flatMap(saved -> maybeCallAfterSave(saved, index));
}
@@ -477,6 +478,17 @@ 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
@@ -624,34 +636,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
/**
* Value class to capture client independent information from a response to an index request.
*/
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;
}
public record IndexResponseMetaData(String id, long seqNo, long primaryTerm, long version) {
}
// endregion
@@ -670,7 +655,8 @@ abstract public class AbstractReactiveElasticsearchTemplate
}
public List<IndexQuery> indexQueries() {
return entities.stream().map(value -> getIndexQuery(value)).collect(Collectors.toList());
return entities.stream().map(AbstractReactiveElasticsearchTemplate.this::getIndexQuery)
.collect(Collectors.toList());
}
public T entityAt(long index) {
@@ -20,7 +20,7 @@ package org.springframework.data.elasticsearch.core;
*
* @author Peter-Josef Meisch
*/
public class ActiveShardCount {
public record ActiveShardCount(int value) {
private static final int ACTIVE_SHARD_COUNT_DEFAULT = -2;
private static final int ALL_ACTIVE_SHARDS = -1;
@@ -28,14 +28,4 @@ public class ActiveShardCount {
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;
}
}
@@ -245,20 +245,6 @@ public interface DocumentOperations {
*/
String delete(String id, IndexCoordinates index);
/**
* Delete the one object with provided id.
*
* @param id the document to delete
* @param routing the optional routing for the document to be deleted
* @param index the index from which to delete
* @return documentId of the document deleted
* @since 4.1
* @deprecated since 4.2, use {@link ElasticsearchOperations#withRouting(RoutingResolver)} and
* {@link #delete(String, IndexCoordinates)}
*/
@Deprecated
String delete(String id, @Nullable String routing, IndexCoordinates index);
/**
* Delete the one object with provided id.
*
@@ -191,12 +191,10 @@ public class Range<T> {
return true;
}
if (!(o instanceof Range)) {
if (!(o instanceof Range<?> range)) {
return false;
}
Range<?> range = (Range<?>) o;
if (!ObjectUtils.nullSafeEquals(lowerBound, range.lowerBound)) {
return false;
}
@@ -375,12 +373,10 @@ public class Range<T> {
return true;
}
if (!(o instanceof Bound)) {
if (!(o instanceof Bound<?> bound)) {
return false;
}
Bound<?> bound = (Bound<?>) o;
if (inclusive != bound.inclusive)
return false;
@@ -72,4 +72,14 @@ public interface ReactiveSearchHits<T> {
* @return wether the {@link SearchHits} has a suggest response.
*/
boolean hasSuggest();
/**
* When doing a search with a point in time, the response contains a new point in time id value.
*
* @return the new point in time id, if one was returned from Elasticsearch
* @since 5.0
*/
@Nullable
String getPointInTimeId();
}
@@ -78,4 +78,13 @@ public class ReactiveSearchHitsImpl<T> implements ReactiveSearchHits<T> {
public boolean hasSuggest() {
return delegate.hasSuggest();
}
/**
* @since 5.0
*/
@Nullable
@Override
public String getPointInTimeId() {
return delegate.getPointInTimeId();
}
}
@@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.List;
import org.springframework.data.domain.Pageable;
@@ -271,6 +272,38 @@ public interface ReactiveSearchOperations {
*/
Mono<Suggest> suggest(Query query, Class<?> entityType, IndexCoordinates index);
/**
* Opens a point in time (pit) in Elasticsearch.
*
* @param index the index name(s) to use
* @param keepAlive the duration the pit shoult be kept alive
* @return the pit identifier
* @since 5.0
*/
default Mono<String> openPointInTime(IndexCoordinates index, Duration keepAlive) {
return openPointInTime(index, keepAlive, false);
}
/**
* Opens a point in time (pit) in Elasticsearch.
*
* @param index the index name(s) to use
* @param keepAlive the duration the pit shoult be kept alive
* @param ignoreUnavailable if {$literal true} the call will fail if any of the indices is missing or closed
* @return the pit identifier
* @since 5.0
*/
Mono<String> openPointInTime(IndexCoordinates index, Duration keepAlive, Boolean ignoreUnavailable);
/**
* Closes a point in time
*
* @param pit the pit identifier as returned by {@link #openPointInTime(IndexCoordinates, Duration, Boolean)}
* @return {@literal true} on success
* @since 5.0
*/
Mono<Boolean> closePointInTime(String pit);
// region helper
/**
* Creates a {@link Query} to find all documents. Must be implemented by the concrete implementations to provide an
@@ -18,25 +18,30 @@ package org.springframework.data.elasticsearch.core;
import java.util.HashMap;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Defines a runtime field to be added to a Query
*
* @author Peter-Josef Meisch
* @author cdalxndr
* @since 4.3
*/
public class RuntimeField {
private final String name;
private final String type;
private final String script;
@Nullable private final String script;
public RuntimeField(String name, String type, String script) {
public RuntimeField(String name, String type) {
this(name, type, null);
}
public RuntimeField(String name, String type, @Nullable String script) {
Assert.notNull(name, "name must not be null");
Assert.notNull(type, "type must not be null");
Assert.notNull(script, "script must not be null");
this.name = name;
this.type = type;
@@ -51,10 +56,12 @@ public class RuntimeField {
* @return the mapping as a Map like it is needed for the Elasticsearch client
*/
public Map<String, Object> getMapping() {
Map<String, Object> map = new HashMap<>();
map.put("type", type);
map.put("script", script);
if (script != null) {
map.put("script", script);
}
return map;
}
@@ -68,7 +75,7 @@ public class RuntimeField {
/**
* @since 4.4
*/
public String getScript() {
public @Nullable String getScript() {
return script;
}
}
@@ -50,16 +50,6 @@ public class SearchHit<T> {
@Nullable private final Explanation explanation;
private final List<String> matchedQueries = new ArrayList<>();
/**
* @deprecated since 4.2 use
* {@link #SearchHit(String, String, String, float, Object[], Map, Map, NestedMetaData, Explanation, List, Object)}.
*/
@Deprecated
public SearchHit(@Nullable String index, @Nullable String id, @Nullable String routing, float score,
@Nullable Object[] sortValues, @Nullable Map<String, List<String>> highlightFields, T content) {
this(index, id, routing, score, sortValues, highlightFields, null, null, null, null, content);
}
public SearchHit(@Nullable String index, @Nullable String id, @Nullable String routing, float score,
@Nullable Object[] sortValues, @Nullable Map<String, List<String>> highlightFields,
@Nullable Map<String, SearchHits<?>> innerHits, @Nullable NestedMetaData nestedMetaData,
@@ -85,6 +85,7 @@ public class SearchHitMapping<T> {
long totalHits = searchDocumentResponse.getTotalHits();
float maxScore = searchDocumentResponse.getMaxScore();
String scrollId = searchDocumentResponse.getScrollId();
String pointInTimeId = searchDocumentResponse.getPointInTimeId();
List<SearchHit<T>> searchHits = new ArrayList<>();
List<SearchDocument> searchDocuments = searchDocumentResponse.getSearchDocuments();
@@ -100,7 +101,8 @@ public class SearchHitMapping<T> {
Suggest suggest = searchDocumentResponse.getSuggest();
mapHitsInCompletionSuggestion(suggest);
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, searchHits, aggregations, suggest);
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchHits,
aggregations, suggest);
}
@SuppressWarnings("unchecked")
@@ -125,7 +127,7 @@ public class SearchHitMapping<T> {
Assert.notNull(searchDocument, "searchDocument is null");
Assert.notNull(content, "content is null");
return new SearchHit<T>(searchDocument.getIndex(), //
return new SearchHit<>(searchDocument.getIndex(), //
searchDocument.hasId() ? searchDocument.getId() : null, //
searchDocument.getRouting(), //
searchDocument.getScore(), //
@@ -210,7 +212,7 @@ public class SearchHitMapping<T> {
SearchDocument searchDocument = searchHit.getContent();
Object targetObject = converter.read(targetType, searchDocument);
convertedSearchHits.add(new SearchHit<Object>(searchDocument.getIndex(), //
convertedSearchHits.add(new SearchHit<>(searchDocument.getIndex(), //
searchDocument.getId(), //
searchDocument.getRouting(), //
searchDocument.getScore(), //
@@ -232,6 +234,7 @@ public class SearchHitMapping<T> {
searchHits.getTotalHitsRelation(), //
searchHits.getMaxScore(), //
scrollId, //
searchHits.getPointInTimeId(), //
convertedSearchHits, //
searchHits.getAggregations(), //
searchHits.getSuggest());
@@ -67,8 +67,7 @@ public final class SearchHitSupport {
return ((Stream<?>) result).map(SearchHitSupport::unwrapSearchHits);
}
if (result instanceof SearchHits<?>) {
SearchHits<?> searchHits = (SearchHits<?>) result;
if (result instanceof SearchHits<?> searchHits) {
return unwrapSearchHits(searchHits.getSearchHits());
}
@@ -76,16 +75,14 @@ public final class SearchHitSupport {
return unwrapSearchHitsIterator((SearchHitsIterator<?>) result);
}
if (result instanceof SearchPage<?>) {
SearchPage<?> searchPage = (SearchPage<?>) result;
if (result instanceof SearchPage<?> searchPage) {
List<?> content = (List<?>) SearchHitSupport.unwrapSearchHits(searchPage.getSearchHits());
return new PageImpl<>(content, searchPage.getPageable(), searchPage.getTotalElements());
}
if (ReactiveWrappers.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR)) {
if (result instanceof Flux) {
Flux<?> flux = (Flux<?>) result;
if (result instanceof Flux<?> flux) {
return flux.map(SearchHitSupport::unwrapSearchHits);
}
}
@@ -95,7 +92,7 @@ public final class SearchHitSupport {
private static CloseableIterator<?> unwrapSearchHitsIterator(SearchHitsIterator<?> iterator) {
return new CloseableIterator<Object>() {
return new CloseableIterator<>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
@@ -100,4 +100,12 @@ public interface SearchHits<T> extends Streamable<SearchHit<T>> {
return getSearchHits().iterator();
}
/**
* When doing a search with a point in time, the response contains a new point in time id value.
*
* @return the new point in time id, if one was returned from Elasticsearch
* @since 5.0
*/
@Nullable
String getPointInTimeId();
}
@@ -41,6 +41,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
private final Lazy<List<SearchHit<T>>> unmodifiableSearchHits;
@Nullable private final AggregationsContainer<?> aggregations;
@Nullable private final Suggest suggest;
@Nullable private String pointInTimeId;
/**
* @param totalHits the number of total hits for the search
@@ -51,8 +52,8 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
* @param aggregations the aggregations if available
*/
public SearchHitsImpl(long totalHits, TotalHitsRelation totalHitsRelation, float maxScore, @Nullable String scrollId,
List<? extends SearchHit<T>> searchHits, @Nullable AggregationsContainer<?> aggregations,
@Nullable Suggest suggest) {
@Nullable String pointInTimeId, List<? extends SearchHit<T>> searchHits,
@Nullable AggregationsContainer<?> aggregations, @Nullable Suggest suggest) {
Assert.notNull(searchHits, "searchHits must not be null");
@@ -60,6 +61,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
this.totalHitsRelation = totalHitsRelation;
this.maxScore = maxScore;
this.scrollId = scrollId;
this.pointInTimeId = pointInTimeId;
this.searchHits = searchHits;
this.aggregations = aggregations;
this.suggest = suggest;
@@ -110,6 +112,12 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
return suggest;
}
@Nullable
@Override
public String getPointInTimeId() {
return pointInTimeId;
}
@Override
public String toString() {
return "SearchHits{" + //
@@ -117,6 +125,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
", totalHitsRelation=" + totalHitsRelation + //
", maxScore=" + maxScore + //
", scrollId='" + scrollId + '\'' + //
", pointInTimeId='" + pointInTimeId + '\'' + //
", searchHits={" + searchHits.size() + " elements}" + //
", aggregations=" + aggregations + //
'}';
@@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.core;
import java.time.Duration;
import java.util.List;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
@@ -216,4 +217,35 @@ public interface SearchOperations {
* @since 4.3
*/
Query idsQuery(List<String> ids);
/**
* Opens a point in time (pit) in Elasticsearch.
*
* @param index the index name(s) to use
* @param keepAlive the duration the pit shoult be kept alive
* @return the pit identifier
* @since 5.0
*/
default String openPointInTime(IndexCoordinates index, Duration keepAlive) {
return openPointInTime(index, keepAlive, false);
}
/**
* Opens a point in time (pit) in Elasticsearch.
*
* @param index the index name(s) to use
* @param keepAlive the duration the pit shoult be kept alive
* @param ignoreUnavailable if {$literal true} the call will fail if any of the indices is missing or closed
* @return the pit identifier
* @since 5.0
*/
String openPointInTime(IndexCoordinates index, Duration keepAlive, Boolean ignoreUnavailable);
/**
* Closes a point in time
*
* @param pit the pit identifier as returned by {@link #openPointInTime(IndexCoordinates, Duration, Boolean)}
* @return {@literal true} on success
* @since 5.0
*/
Boolean closePointInTime(String pit);
}
@@ -59,7 +59,7 @@ abstract class StreamQueries {
long totalHits = searchHits.getTotalHits();
TotalHitsRelation totalHitsRelation = searchHits.getTotalHitsRelation();
return new SearchHitsIterator<T>() {
return new SearchHitsIterator<>() {
private volatile AtomicInteger currentCount = new AtomicInteger();
private volatile Iterator<SearchHit<T>> currentScrollHits = searchHits.iterator();
@@ -26,7 +26,6 @@ import org.springframework.data.convert.TypeInformationMapper;
import org.springframework.data.mapping.Alias;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -41,7 +40,7 @@ public class DefaultElasticsearchTypeMapper extends DefaultTypeMapper<Map<String
implements ElasticsearchTypeMapper {
@SuppressWarnings("rawtypes") //
private static final TypeInformation<Map> MAP_TYPE_INFO = ClassTypeInformation.from(Map.class);
private static final TypeInformation<Map> MAP_TYPE_INFO = TypeInformation.of(Map.class);
private final @Nullable String typeKey;
@@ -87,8 +87,7 @@ final public class ElasticsearchDateConverter {
Assert.notNull(accessor, "accessor must not be null");
if (accessor instanceof Instant) {
Instant instant = (Instant) accessor;
if (accessor instanceof Instant instant) {
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"));
return dateFormatter.format(zonedDateTime);
}
@@ -157,7 +156,6 @@ final public class ElasticsearchDateConverter {
case weekyear:
case weekyear_week:
case weekyear_week_day:
case custom:
continue;
}
@@ -171,24 +171,16 @@ public class GeoConverters {
String type = GeoConverters.getGeoJsonType(source);
switch (type) {
case "point":
return MapToGeoJsonPointConverter.INSTANCE.convert(source);
case "multipoint":
return MapToGeoJsonMultiPointConverter.INSTANCE.convert(source);
case "linestring":
return MapToGeoJsonLineStringConverter.INSTANCE.convert(source);
case "multilinestring":
return MapToGeoJsonMultiLineStringConverter.INSTANCE.convert(source);
case "polygon":
return MapToGeoJsonPolygonConverter.INSTANCE.convert(source);
case "multipolygon":
return MapToGeoJsonMultiPolygonConverter.INSTANCE.convert(source);
case "geometrycollection":
return MapToGeoJsonGeometryCollectionConverter.INSTANCE.convert(source);
default:
throw new IllegalArgumentException("unknown GeoJson type " + type);
}
return switch (type) {
case "point" -> MapToGeoJsonPointConverter.INSTANCE.convert(source);
case "multipoint" -> MapToGeoJsonMultiPointConverter.INSTANCE.convert(source);
case "linestring" -> MapToGeoJsonLineStringConverter.INSTANCE.convert(source);
case "multilinestring" -> MapToGeoJsonMultiLineStringConverter.INSTANCE.convert(source);
case "polygon" -> MapToGeoJsonPolygonConverter.INSTANCE.convert(source);
case "multipolygon" -> MapToGeoJsonMultiPolygonConverter.INSTANCE.convert(source);
case "geometrycollection" -> MapToGeoJsonGeometryCollectionConverter.INSTANCE.convert(source);
default -> throw new IllegalArgumentException("unknown GeoJson type " + type);
};
}
}
// endregion
@@ -30,6 +30,7 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.CustomConversions;
@@ -53,8 +54,16 @@ import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.*;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
import org.springframework.data.util.TypeInformation;
import org.springframework.format.datetime.DateFormatterRegistrar;
import org.springframework.lang.Nullable;
@@ -192,7 +201,7 @@ public class MappingElasticsearchConverter
}
/**
* Class to do the actual writing. The methods originally were in the MappingElasticsearchConverter class, but are
* Class to do the actual reading. The methods originally were in the MappingElasticsearchConverter class, but are
* refactored to allow for keeping state during the conversion of an object.
*/
private static class Reader extends Base {
@@ -211,10 +220,17 @@ public class MappingElasticsearchConverter
}
@SuppressWarnings("unchecked")
/**
* Reads the given source into the given type.
*
* @param type they type to convert the given source to.
* @param source the source to create an object of the given type from.
* @return the object that was read
*/
<R> R read(Class<R> type, Document source) {
TypeInformation<R> typeHint = ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type));
R r = read(typeHint, source);
TypeInformation<R> typeInformation = TypeInformation.of((Class<R>) ClassUtils.getUserClass(type));
R r = read(typeInformation, source);
if (r == null) {
throw new ConversionException("could not convert into object of class " + type);
@@ -225,11 +241,11 @@ public class MappingElasticsearchConverter
@Nullable
@SuppressWarnings("unchecked")
private <R> R read(TypeInformation<R> type, Map<String, Object> source) {
private <R> R read(TypeInformation<R> typeInformation, Map<String, Object> source) {
Assert.notNull(source, "Source must not be null!");
TypeInformation<? extends R> typeToUse = typeMapper.readType(source, type);
TypeInformation<? extends R> typeToUse = typeMapper.readType(source, typeInformation);
Class<? extends R> rawType = typeToUse.getType();
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
@@ -244,11 +260,11 @@ public class MappingElasticsearchConverter
return readMap(typeToUse, source);
}
if (typeToUse.equals(ClassTypeInformation.OBJECT)) {
if (typeToUse.equals(TypeInformation.OBJECT)) {
return (R) source;
}
// Retrieve persistent entity info
// Retrieve persistent entity info
ElasticsearchPersistentEntity<?> entity = mappingContext.getPersistentEntity(typeToUse);
if (entity == null) {
@@ -286,13 +302,13 @@ public class MappingElasticsearchConverter
}
Object value = entry.getValue();
TypeInformation<?> defaultedValueType = valueType != null ? valueType : ClassTypeInformation.OBJECT;
TypeInformation<?> defaultedValueType = valueType != null ? valueType : TypeInformation.OBJECT;
if (value instanceof Map) {
map.put(key, read(defaultedValueType, (Map<String, Object>) value));
} else if (value instanceof List) {
map.put(key,
readCollectionOrArray(valueType != null ? valueType : ClassTypeInformation.LIST, (List<Object>) value));
readCollectionOrArray(valueType != null ? valueType : TypeInformation.LIST, (List<Object>) value));
} else {
map.put(key, getPotentiallyConvertedSimpleRead(value, rawValueType));
}
@@ -326,14 +342,13 @@ public class MappingElasticsearchConverter
ElasticsearchPropertyValueProvider valueProvider = new ElasticsearchPropertyValueProvider(accessor, evaluator);
R result = readProperties(targetEntity, instance, valueProvider);
if (source instanceof Document) {
Document document = (Document) source;
if (source instanceof Document document) {
if (document.hasId()) {
ElasticsearchPersistentProperty idProperty = targetEntity.getIdProperty();
PersistentPropertyAccessor<R> propertyAccessor = new ConvertingPropertyAccessor<>(
targetEntity.getPropertyAccessor(result), conversionService);
// Only deal with String because ES generated Ids are strings !
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
if (idProperty != null && idProperty.isWritable() && idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, document.getId());
}
}
@@ -359,13 +374,11 @@ public class MappingElasticsearchConverter
}
}
if (source instanceof SearchDocument) {
SearchDocument searchDocument = (SearchDocument) source;
if (source instanceof SearchDocument searchDocument) {
populateScriptFields(targetEntity, result, searchDocument);
}
return result;
}
private ParameterValueProvider<ElasticsearchPersistentProperty> getParameterProvider(
@@ -397,7 +410,7 @@ public class MappingElasticsearchConverter
for (ElasticsearchPersistentProperty prop : entity) {
if (entity.isConstructorArgument(prop) || !prop.isReadable()) {
if (entity.isCreatorArgument(prop) || !prop.isReadable() || !prop.isWritable()) {
continue;
}
@@ -455,12 +468,44 @@ public class MappingElasticsearchConverter
} else if (value.getClass().isArray()) {
return (T) readCollectionOrArray(type, Arrays.asList((Object[]) value));
} else if (value instanceof Map) {
TypeInformation<?> collectionComponentType = getCollectionComponentType(type);
if (collectionComponentType != null) {
Object o = read(collectionComponentType, (Map<String, Object>) value);
return getCollectionWithSingleElement(type, collectionComponentType, o);
}
return (T) read(type, (Map<String, Object>) value);
} else {
TypeInformation<?> collectionComponentType = getCollectionComponentType(type);
if (collectionComponentType != null
&& collectionComponentType.isAssignableFrom(TypeInformation.of(value.getClass()))) {
Object o = getPotentiallyConvertedSimpleRead(value, collectionComponentType);
return getCollectionWithSingleElement(type, collectionComponentType, o);
}
return (T) getPotentiallyConvertedSimpleRead(value, rawType);
}
}
@SuppressWarnings("unchecked")
private static <T> T getCollectionWithSingleElement(TypeInformation<?> collectionType,
TypeInformation<?> componentType, Object element) {
Collection<Object> collection = CollectionFactory.createCollection(collectionType.getType(),
componentType.getType(), 1);
collection.add(element);
return (T) collection;
}
/**
* @param type the type to check
* @return true if type is a collectoin, null otherwise,
*/
@Nullable
TypeInformation<?> getCollectionComponentType(TypeInformation<?> type) {
return type.isCollectionLike() ? type.getComponentType() : null;
}
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter());
@@ -504,7 +549,7 @@ public class MappingElasticsearchConverter
TypeInformation<?> componentType = targetType.getComponentType() != null //
? targetType.getComponentType() //
: ClassTypeInformation.OBJECT;
: TypeInformation.OBJECT;
Class<?> rawComponentType = componentType.getType();
Collection<Object> items = targetType.getType().isArray() //
@@ -559,6 +604,29 @@ public class MappingElasticsearchConverter
return Enum.valueOf((Class<Enum>) target, value.toString());
}
try {
return conversionService.convert(value, target);
} catch (ConverterNotFoundException e) {
return convertFromCollectionToObject(value, target);
}
}
/**
* we need the conversion from a collection to the first element for example in the case when reading the
* constructor parameter of an entity from a scripted return. Originally this was handle in the conversionService,
* but will be removed from spring-data-commons, so we do it here
*/
@Nullable
private Object convertFromCollectionToObject(Object value, @Nullable Class<?> target) {
if (value.getClass().isArray()) {
value = Arrays.asList(value);
}
if (value instanceof Collection<?> collection && !collection.isEmpty()) {
value = collection.iterator().next();
}
return conversionService.convert(value, target);
}
@@ -649,7 +717,8 @@ public class MappingElasticsearchConverter
* @see org.springframework.data.mapping.model.SpELExpressionParameterValueProvider#potentiallyConvertSpelValue(java.lang.Object, org.springframework.data.mapping.PreferredConstructor.Parameter)
*/
@Override
protected <T> T potentiallyConvertSpelValue(Object object, Parameter<T, ElasticsearchPersistentProperty> parameter) {
protected <T> T potentiallyConvertSpelValue(Object object,
Parameter<T, ElasticsearchPersistentProperty> parameter) {
return readValue(object, parameter.getType());
}
}
@@ -694,7 +763,7 @@ public class MappingElasticsearchConverter
writeTypeHints = entity.writeTypeHints();
}
TypeInformation<?> typeInformation = ClassTypeInformation.from(entityType);
TypeInformation<?> typeInformation = TypeInformation.of(entityType);
if (writeTypeHints && requiresTypeHint(entityType)) {
typeMapper.writeType(typeInformation, sink);
@@ -707,7 +776,7 @@ public class MappingElasticsearchConverter
* Internal write conversion method which should be used for nested invocations.
*
* @param source the object to write
* @param sink the write destination
* @param sink the destination
* @param typeInformation type information for the source
*/
@SuppressWarnings("unchecked")
@@ -731,12 +800,12 @@ public class MappingElasticsearchConverter
}
if (Map.class.isAssignableFrom(entityType)) {
writeMapInternal((Map<Object, Object>) source, sink, ClassTypeInformation.MAP);
writeMapInternal((Map<Object, Object>) source, sink, TypeInformation.MAP);
return;
}
if (Collection.class.isAssignableFrom(entityType)) {
writeCollectionInternal((Collection<?>) source, ClassTypeInformation.LIST, (Collection<?>) sink);
writeCollectionInternal((Collection<?>) source, TypeInformation.LIST, (Collection<?>) sink);
return;
}
@@ -749,7 +818,7 @@ public class MappingElasticsearchConverter
* Internal write conversion method which should be used for nested invocations.
*
* @param source the object to write
* @param sink the write destination
* @param sink the destination
* @param entity entity for the source
*/
private void writeInternal(@Nullable Object source, Map<String, Object> sink,
@@ -813,7 +882,7 @@ public class MappingElasticsearchConverter
} else {
Map<String, Object> document = Document.create();
TypeInformation<?> valueTypeInfo = propertyType.isMap() ? propertyType.getMapValueType()
: ClassTypeInformation.OBJECT;
: TypeInformation.OBJECT;
writeInternal(value, document, valueTypeInfo);
sink.put(simpleKey, document);
@@ -923,7 +992,7 @@ public class MappingElasticsearchConverter
return;
}
TypeInformation<?> valueType = ClassTypeInformation.from(value.getClass());
TypeInformation<?> valueType = TypeInformation.of(value.getClass());
TypeInformation<?> type = property.getTypeInformation();
if (valueType.isCollectionLike()) {
@@ -955,7 +1024,7 @@ public class MappingElasticsearchConverter
Map<String, Object> document = existingValue instanceof Map ? (Map<String, Object>) existingValue
: Document.create();
addCustomTypeKeyIfNecessary(value, document, ClassTypeInformation.from(property.getRawType()));
addCustomTypeKeyIfNecessary(value, document, TypeInformation.of(property.getRawType()));
writeInternal(value, document, entity);
sink.set(property, document);
}
@@ -1117,17 +1186,18 @@ public class MappingElasticsearchConverter
Assert.notNull(query, "query must not be null");
if (domainClass != null) {
if (domainClass == null) {
return;
}
updateFieldsAndSourceFilter(query, domainClass);
updatePropertiesInFieldsAndSourceFilter(query, domainClass);
if (query instanceof CriteriaQuery) {
updateCriteriaQuery((CriteriaQuery) query, domainClass);
}
if (query instanceof CriteriaQuery) {
updatePropertiesInCriteriaQuery((CriteriaQuery) query, domainClass);
}
}
private void updateFieldsAndSourceFilter(Query query, Class<?> domainClass) {
private void updatePropertiesInFieldsAndSourceFilter(Query query, Class<?> domainClass) {
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(domainClass);
@@ -1165,14 +1235,22 @@ public class MappingElasticsearchConverter
}
}
private List<String> updateFieldNames(List<String> fields, ElasticsearchPersistentEntity<?> persistentEntity) {
return fields.stream().map(fieldName -> {
/**
* relaces the fieldName with the property name of a property of the persistentEntity with the corresponding
* fieldname. If no such property exists, the original fieldName is kept.
*
* @param fieldNames list of fieldnames
* @param persistentEntity the persistent entity to check
* @return an updated list of field names
*/
private List<String> updateFieldNames(List<String> fieldNames, ElasticsearchPersistentEntity<?> persistentEntity) {
return fieldNames.stream().map(fieldName -> {
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(fieldName);
return persistentProperty != null ? persistentProperty.getFieldName() : fieldName;
}).collect(Collectors.toList());
}
private void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
private void updatePropertiesInCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
Assert.notNull(criteriaQuery, "criteriaQuery must not be null");
Assert.notNull(domainClass, "domainClass must not be null");
@@ -1181,17 +1259,17 @@ public class MappingElasticsearchConverter
if (persistentEntity != null) {
for (Criteria chainedCriteria : criteriaQuery.getCriteria().getCriteriaChain()) {
updateCriteria(chainedCriteria, persistentEntity);
updatePropertiesInCriteria(chainedCriteria, persistentEntity);
}
for (Criteria subCriteria : criteriaQuery.getCriteria().getSubCriteria()) {
for (Criteria chainedCriteria : subCriteria.getCriteriaChain()) {
updateCriteria(chainedCriteria, persistentEntity);
updatePropertiesInCriteria(chainedCriteria, persistentEntity);
}
}
}
}
private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity<?> persistentEntity) {
private void updatePropertiesInCriteria(Criteria criteria, ElasticsearchPersistentEntity<?> persistentEntity) {
Field field = criteria.getField();
@@ -1288,10 +1366,9 @@ public class MappingElasticsearchConverter
String fieldName = property.getFieldName();
if (target instanceof Document) {
if (target instanceof Document document) {
// nested objects may have properties like 'id' which are recognized as isIdProperty() but they are not
// Documents
Document document = (Document) target;
if (property.isIdProperty() && document.hasId()) {
Object id = null;
@@ -258,10 +258,9 @@ public class SearchDocumentAdapter implements SearchDocument {
if (this == o) {
return true;
}
if (!(o instanceof SearchDocumentAdapter)) {
if (!(o instanceof SearchDocumentAdapter that)) {
return false;
}
SearchDocumentAdapter that = (SearchDocumentAdapter) o;
return Float.compare(that.score, score) == 0 && delegate.equals(that.delegate);
}
@@ -39,13 +39,16 @@ public class SearchDocumentResponse {
@Nullable private final AggregationsContainer<?> aggregations;
@Nullable private final Suggest suggest;
@Nullable String pointInTimeId;
public SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, @Nullable String scrollId,
List<SearchDocument> searchDocuments, @Nullable AggregationsContainer<?> aggregationsContainer,
@Nullable Suggest suggest) {
@Nullable String pointInTimeId, List<SearchDocument> searchDocuments,
@Nullable AggregationsContainer<?> aggregationsContainer, @Nullable Suggest suggest) {
this.totalHits = totalHits;
this.totalHitsRelation = totalHitsRelation;
this.maxScore = maxScore;
this.scrollId = scrollId;
this.pointInTimeId = pointInTimeId;
this.searchDocuments = searchDocuments;
this.aggregations = aggregationsContainer;
this.suggest = suggest;
@@ -82,6 +85,14 @@ public class SearchDocumentResponse {
return suggest;
}
/**
* @since 5.0
*/
@Nullable
public String getPointInTimeId() {
return pointInTimeId;
}
/**
* A function to convert a {@link SearchDocument} async into an entity. Asynchronous so that it can be used from the
* imperative and the reactive code.
@@ -33,7 +33,7 @@ public class GeoJsonMultiPolygon implements GeoJson<Iterable<GeoJsonPolygon>> {
public static final String TYPE = "MultiPolygon";
private List<GeoJsonPolygon> coordinates = new ArrayList<GeoJsonPolygon>();
private List<GeoJsonPolygon> coordinates = new ArrayList<>();
private GeoJsonMultiPolygon(List<GeoJsonPolygon> polygons) {
this.coordinates.addAll(polygons);
@@ -157,7 +157,7 @@ public class MappingBuilder {
private class InternalBuilder {
private boolean writeTypeHints = true;
private List<String> excludeFromSource = new ArrayList<>();
private final List<String> excludeFromSource = new ArrayList<>();
private String nestedPropertyPrefix = "";
protected String buildPropertyMapping(ElasticsearchPersistentEntity<?> entity,
@@ -172,8 +172,11 @@ public class MappingBuilder {
// Dynamic templates
addDynamicTemplatesMapping(objectNode, entity);
mapEntity(objectNode, entity, true, "", false, FieldType.Auto, null,
entity.findAnnotation(DynamicMapping.class), runtimeFields);
org.springframework.data.elasticsearch.annotations.Document docAnnotation = entity
.findAnnotation(org.springframework.data.elasticsearch.annotations.Document.class);
var dynamicMapping = docAnnotation != null ? docAnnotation.dynamic() : null;
mapEntity(objectNode, entity, true, "", false, FieldType.Auto, null, dynamicMapping, runtimeFields);
if (!excludeFromSource.isEmpty()) {
ObjectNode sourceNode = objectNode.putObject(SOURCE);
@@ -209,8 +212,8 @@ public class MappingBuilder {
private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentEntity<?> entity,
boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
@Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping,
@Nullable Document runtimeFields) throws IOException {
@Nullable Field parentFieldAnnotation, @Nullable Dynamic dynamicMapping, @Nullable Document runtimeFields)
throws IOException {
if (entity != null && entity.isAnnotationPresent(Mapping.class)) {
Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class);
@@ -258,8 +261,8 @@ public class MappingBuilder {
if (entity != null && entity.dynamic() != Dynamic.INHERIT) {
objectNode.put(TYPE_DYNAMIC, entity.dynamic().getMappedName());
} else if (dynamicMapping != null) {
objectNode.put(TYPE_DYNAMIC, dynamicMapping.value().getMappedName());
} else if (dynamicMapping != null && dynamicMapping != Dynamic.INHERIT) {
objectNode.put(TYPE_DYNAMIC, dynamicMapping.getMappedName());
}
ObjectNode propertiesNode = objectNode.putObject(FIELD_PROPERTIES);
@@ -339,7 +342,7 @@ public class MappingBuilder {
boolean isCompletionProperty = property.isCompletionProperty();
boolean isNestedOrObjectProperty = isNestedOrObjectProperty(property);
DynamicMapping dynamicMapping = property.findAnnotation(DynamicMapping.class);
Dynamic dynamicMapping = fieldAnnotation != null ? fieldAnnotation.dynamic() : null;
if (!isCompletionProperty && property.isEntity() && hasRelevantAnnotation(property)) {
@@ -475,7 +478,7 @@ public class MappingBuilder {
* @throws IOException
*/
private void addSingleFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property,
Field annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) throws IOException {
Field annotation, boolean nestedOrObjectField, @Nullable Dynamic dynamicMapping) throws IOException {
// build the property json, if empty skip it as this is no valid mapping
ObjectNode fieldNode = objectMapper.createObjectNode();
@@ -490,8 +493,8 @@ public class MappingBuilder {
if (nestedOrObjectField) {
if (annotation.dynamic() != Dynamic.INHERIT) {
fieldNode.put(TYPE_DYNAMIC, annotation.dynamic().getMappedName());
} else if (dynamicMapping != null) {
fieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().getMappedName());
} else if (dynamicMapping != null && dynamicMapping != Dynamic.INHERIT) {
fieldNode.put(TYPE_DYNAMIC, dynamicMapping.getMappedName());
}
}
}
@@ -531,8 +534,7 @@ public class MappingBuilder {
* @throws IOException
*/
private void addMultiFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property,
MultiField annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping)
throws IOException {
MultiField annotation, boolean nestedOrObjectField, @Nullable Dynamic dynamicMapping) throws IOException {
// main field
ObjectNode mainFieldNode = objectMapper.createObjectNode();
@@ -541,8 +543,8 @@ public class MappingBuilder {
if (nestedOrObjectField) {
if (annotation.mainField().dynamic() != Dynamic.INHERIT) {
mainFieldNode.put(TYPE_DYNAMIC, annotation.mainField().dynamic().getMappedName());
} else if (dynamicMapping != null) {
mainFieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().getMappedName());
} else if (dynamicMapping != null && dynamicMapping != Dynamic.INHERIT) {
mainFieldNode.put(TYPE_DYNAMIC, dynamicMapping.getMappedName());
}
}
@@ -594,7 +596,7 @@ public class MappingBuilder {
}
}
private boolean isAnyPropertyAnnotatedWithField(@Nullable ElasticsearchPersistentEntity entity) {
private boolean isAnyPropertyAnnotatedWithField(@Nullable ElasticsearchPersistentEntity<?> entity) {
return entity != null && entity.getPersistentProperty(Field.class) != null;
}
@@ -244,9 +244,6 @@ public final class MappingParameters {
// built-in formats
for (DateFormat dateFormat : dateFormats) {
if (dateFormat == DateFormat.none || dateFormat == DateFormat.custom) {
continue;
}
formats.add(dateFormat.toString());
}
@@ -321,19 +318,11 @@ public final class MappingParameters {
if (StringUtils.hasLength(nullValue)) {
switch (nullValueType) {
case Integer:
objectNode.put(FIELD_PARAM_NULL_VALUE, Integer.valueOf(nullValue));
break;
case Long:
objectNode.put(FIELD_PARAM_NULL_VALUE, Long.valueOf(nullValue));
break;
case Double:
objectNode.put(FIELD_PARAM_NULL_VALUE, Double.valueOf(nullValue));
break;
case String:
default:
objectNode.put(FIELD_PARAM_NULL_VALUE, nullValue);
break;
case Integer -> objectNode.put(FIELD_PARAM_NULL_VALUE, Integer.valueOf(nullValue));
case Long -> objectNode.put(FIELD_PARAM_NULL_VALUE, Long.valueOf(nullValue));
case Double -> objectNode.put(FIELD_PARAM_NULL_VALUE, Double.valueOf(nullValue));
case String -> objectNode.put(FIELD_PARAM_NULL_VALUE, nullValue);
default -> objectNode.put(FIELD_PARAM_NULL_VALUE, nullValue);
}
}
@@ -114,8 +114,7 @@ public class Settings extends DefaultStringObjectMap<Settings> {
*/
static private Stream<Map.Entry<String, Object>> doFlatten(Map.Entry<String, Object> entry) {
if (entry.getValue() instanceof Map<?, ?>) {
Map<?, ?> nested = (Map<?, ?>) entry.getValue();
if (entry.getValue()instanceof Map<?, ?> nested) {
// noinspection unchecked
return nested.entrySet().stream() //
@@ -86,7 +86,7 @@ public class TemplateData {
private TemplateDataBuilder() {}
public TemplateDataBuilder withIndexPatterns(String[] indexPatterns) {
public TemplateDataBuilder withIndexPatterns(String... indexPatterns) {
this.indexPatterns = indexPatterns;
return this;
}
@@ -68,10 +68,9 @@ public class JoinField<ID> {
if (this == obj) {
return true;
}
if (!(obj instanceof JoinField)) {
if (!(obj instanceof JoinField<?> other)) {
return false;
}
JoinField other = (JoinField) obj;
return Objects.equals(name, other.name) && Objects.equals(parent, other.parent);
}
}
@@ -15,8 +15,6 @@
*/
package org.springframework.data.elasticsearch.core.mapping;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -35,15 +33,9 @@ public class ElasticsearchSimpleTypes {
static final Set<Class<?>> AUTOGENERATED_ID_TYPES;
static {
Set<Class<?>> classes = new HashSet<>();
classes.add(String.class);
AUTOGENERATED_ID_TYPES = Collections.unmodifiableSet(classes);
AUTOGENERATED_ID_TYPES = Set.of(String.class);
Set<Class<?>> simpleTypes = new HashSet<>();
simpleTypes.add(Document.class);
simpleTypes.add(Map.class);
ELASTICSEARCH_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes);
ELASTICSEARCH_SIMPLE_TYPES = Set.of(Document.class, Map.class);
}
private static final Set<Class<?>> ELASTICSEARCH_SIMPLE_TYPES;
@@ -39,7 +39,7 @@ public class IndexCoordinates {
return new IndexCoordinates(indexNames);
}
private IndexCoordinates(String[] indexNames) {
private IndexCoordinates(String... indexNames) {
Assert.notEmpty(indexNames, "indexNames may not be null or empty");
this.indexNames = indexNames;
}
@@ -395,16 +395,14 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
private SettingsParameter buildSettingsParameter(Class<?> clazz) {
SettingsParameter settingsParameter = new SettingsParameter();
Document documentAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Document.class);
Setting settingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Setting.class);
if (documentAnnotation != null) {
settingsParameter.useServerConfiguration = documentAnnotation.useServerConfiguration();
settingsParameter.shards = documentAnnotation.shards();
settingsParameter.replicas = documentAnnotation.replicas();
settingsParameter.refreshIntervall = documentAnnotation.refreshInterval();
settingsParameter.indexStoreType = documentAnnotation.indexStoreType();
}
// default values
settingsParameter.useServerConfiguration = false;
settingsParameter.shards = 1;
settingsParameter.replicas = 1;
settingsParameter.refreshIntervall = "1s";
settingsParameter.indexStoreType = "fs";
if (settingAnnotation != null) {
processSettingAnnotation(settingAnnotation, settingsParameter);
@@ -261,19 +261,10 @@ public class SimpleElasticsearchPersistentProperty extends
// register converters for built-in formats
for (DateFormat dateFormat : dateFormats) {
switch (dateFormat) {
case none:
case custom:
break;
case weekyear:
case weekyear_week:
case weekyear_week_day:
LOGGER.warn(String.format(
"No default converter available for '%s' and date format '%s'. Use a custom converter instead.",
actualType.getName(), dateFormat.name()));
break;
default:
converters.add(ElasticsearchDateConverter.of(dateFormat));
break;
case weekyear, weekyear_week, weekyear_week_day -> LOGGER.warn(String.format(
"No default converter available for '%s' and date format '%s'. Use a custom converter instead.",
actualType.getName(), dateFormat.name()));
default -> converters.add(ElasticsearchDateConverter.of(dateFormat));
}
}
@@ -47,8 +47,8 @@ import org.springframework.util.Assert;
*/
public class BaseQuery implements Query {
protected Pageable pageable = DEFAULT_PAGE;
@Nullable protected Sort sort;
protected Pageable pageable = DEFAULT_PAGE;
protected List<String> fields = new ArrayList<>();
@Nullable protected List<String> storedFields;
@Nullable protected SourceFilter sourceFilter;
@@ -67,11 +67,12 @@ public class BaseQuery implements Query {
@Nullable protected Duration timeout;
private boolean explain = false;
@Nullable protected List<Object> searchAfter;
@Nullable protected List<IndexBoost> indicesBoost;
protected List<RescorerQuery> rescorerQueries = new ArrayList<>();
@Nullable protected Boolean requestCache;
protected List<IdWithRouting> idsWithRouting = Collections.emptyList();
protected final List<RuntimeField> runtimeFields = new ArrayList<>();
@Nullable protected List<IndexBoost> indicesBoost;
@Nullable protected PointInTime pointInTime;
public BaseQuery() {}
@@ -79,17 +80,29 @@ public class BaseQuery implements Query {
this.sort = builder.getSort();
// do a setPageable after setting the sort, because the pageable may contain an additional sort
this.setPageable(builder.getPageable() != null ? builder.getPageable() : DEFAULT_PAGE);
this.ids = builder.getIds();
this.trackScores = builder.getTrackScores();
this.maxResults = builder.getMaxResults();
this.indicesOptions = builder.getIndicesOptions();
this.minScore = builder.getMinScore();
this.preference = builder.getPreference();
this.sourceFilter = builder.getSourceFilter();
this.fields = builder.getFields();
this.highlightQuery = builder.highlightQuery;
this.storedFields = builder.getStoredFields();
this.sourceFilter = builder.getSourceFilter();
this.minScore = builder.getMinScore();
this.ids = builder.getIds() == null ? null : builder.getIds();
this.route = builder.getRoute();
this.searchType = builder.getSearchType();
this.indicesOptions = builder.getIndicesOptions();
this.trackScores = builder.getTrackScores();
this.preference = builder.getPreference();
this.maxResults = builder.getMaxResults();
this.highlightQuery = builder.getHighlightQuery();
this.trackTotalHits = builder.getTrackTotalHits();
this.trackTotalHitsUpTo = builder.getTrackTotalHitsUpTo();
this.scrollTime = builder.getScrollTime();
this.timeout = builder.getTimeout();
this.explain = builder.getExplain();
this.searchAfter = builder.getSearchAfter();
this.indicesBoost = builder.getIndicesBoost();
this.rescorerQueries = builder.getRescorerQueries();
this.requestCache = builder.getRequestCache();
this.idsWithRouting = builder.getIdsWithRouting();
this.pointInTime = builder.getPointInTime();
}
@Override
@@ -274,7 +287,6 @@ public class BaseQuery implements Query {
/**
* Configures whether to track scores.
*
* @param trackScores
* @since 3.1
*/
public void setTrackScores(boolean trackScores) {
@@ -359,7 +371,6 @@ public class BaseQuery implements Query {
/**
* set the query timeout
*
* @param timeout
* @since 4.2
*/
public void setTimeout(@Nullable Duration timeout) {
@@ -440,4 +451,19 @@ public class BaseQuery implements Query {
public List<IndexBoost> getIndicesBoost() {
return indicesBoost;
}
/**
* @since 5.0
*/
@Nullable
public PointInTime getPointInTime() {
return pointInTime;
}
/**
* @since 5.0
*/
public void setPointInTime(@Nullable PointInTime pointInTime) {
this.pointInTime = pointInTime;
}
}
@@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.core.query;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -23,7 +24,9 @@ import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.RuntimeField;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* base class for query builders. The different implementations of {@link Query} should derive from this class and then
@@ -34,28 +37,52 @@ import org.springframework.lang.Nullable;
*/
public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQueryBuilder<Q, SELF>> {
@Nullable private Pageable pageable;
@Nullable private Sort sort;
@Nullable private Integer maxResults;
@Nullable private Collection<String> ids;
private boolean trackScores;
@Nullable protected IndicesOptions indicesOptions;
private float minScore;
@Nullable private String preference;
@Nullable private Pageable pageable;
private final List<String> fields = new ArrayList<>();
@Nullable private List<String> storedFields;
@Nullable private SourceFilter sourceFilter;
private List<String> fields = new ArrayList<>();
@Nullable protected HighlightQuery highlightQuery;
private float minScore;
private final Collection<String> ids = new ArrayList<>();
@Nullable private String route;
protected Query.SearchType searchType = Query.SearchType.QUERY_THEN_FETCH;
@Nullable protected IndicesOptions indicesOptions;
private boolean trackScores;
@Nullable private String preference;
@Nullable private Integer maxResults;
@Nullable protected HighlightQuery highlightQuery;
@Nullable private Boolean trackTotalHits;
@Nullable protected Integer trackTotalHitsUpTo;
@Nullable protected Duration scrollTime;
@Nullable protected Duration timeout;
boolean explain = false;
@Nullable protected List<Object> searchAfter;
@Nullable private List<IndexBoost> indicesBoost;
protected final List<RescorerQuery> rescorerQueries = new ArrayList<>();
@Nullable protected Boolean requestCache;
protected final List<Query.IdWithRouting> idsWithRouting = new ArrayList<>();
protected final List<RuntimeField> runtimeFields = new ArrayList<>();
@Nullable protected Query.PointInTime pointInTime;
@Nullable
public Sort getSort() {
return sort;
}
@Nullable
public Pageable getPageable() {
return pageable;
}
public List<String> getFields() {
return fields;
}
@Nullable
public Sort getSort() {
return sort;
public List<String> getStoredFields() {
return storedFields;
}
@Nullable
@@ -91,10 +118,6 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
return sourceFilter;
}
public List<String> getFields() {
return fields;
}
@Nullable
public HighlightQuery getHighlightQuery() {
return highlightQuery;
@@ -110,6 +133,64 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
return indicesBoost;
}
public Query.SearchType getSearchType() {
return searchType;
}
@Nullable
public Boolean getTrackTotalHits() {
return trackTotalHits;
}
@Nullable
public Integer getTrackTotalHitsUpTo() {
return trackTotalHitsUpTo;
}
@Nullable
public Duration getScrollTime() {
return scrollTime;
}
@Nullable
public Duration getTimeout() {
return timeout;
}
public boolean getExplain() {
return explain;
}
@Nullable
public List<Object> getSearchAfter() {
return searchAfter;
}
@Nullable
public Boolean getRequestCache() {
return requestCache;
}
public List<Query.IdWithRouting> getIdsWithRouting() {
return idsWithRouting;
}
public List<RuntimeField> getRuntimeFields() {
return runtimeFields;
}
public List<RescorerQuery> getRescorerQueries() {
return rescorerQueries;
}
/**
* @since 5.0
*/
@Nullable
public Query.PointInTime getPointInTime() {
return pointInTime;
}
public SELF withPageable(Pageable pageable) {
this.pageable = pageable;
return self();
@@ -130,12 +211,18 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
}
public SELF withIds(String... ids) {
this.ids = Arrays.asList(ids);
this.ids.clear();
this.ids.addAll(Arrays.asList(ids));
return self();
}
public SELF withIds(Collection<String> ids) {
this.ids = ids;
Assert.notNull(ids, "ids must not be null");
this.ids.clear();
this.ids.addAll(ids);
return self();
}
@@ -165,11 +252,17 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
}
public SELF withFields(String... fields) {
this.fields.clear();
Collections.addAll(this.fields, fields);
return self();
}
public SELF withFields(Collection<String> fields) {
Assert.notNull(fields, "fields must not be null");
this.fields.clear();
this.fields.addAll(fields);
return self();
}
@@ -184,16 +277,104 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
return self();
}
public SELF withIndicesBoost(List<IndexBoost> indicesBoost) {
public SELF withIndicesBoost(@Nullable List<IndexBoost> indicesBoost) {
this.indicesBoost = indicesBoost;
return self();
}
public SELF withStoredFields(@Nullable List<String> storedFields) {
this.storedFields = storedFields;
return self();
}
public SELF withIndicesBoost(IndexBoost... indicesBoost) {
this.indicesBoost = Arrays.asList(indicesBoost);
return self();
}
public SELF withSearchType(Query.SearchType searchType) {
this.searchType = searchType;
return self();
}
public SELF withTrackTotalHits(@Nullable Boolean trackTotalHits) {
this.trackTotalHits = trackTotalHits;
return self();
}
public SELF withTrackTotalHitsUpTo(@Nullable Integer trackTotalHitsUpTo) {
this.trackTotalHitsUpTo = trackTotalHitsUpTo;
return self();
}
public SELF withTimeout(@Nullable Duration timeout) {
this.timeout = timeout;
return self();
}
public SELF withScrollTime(@Nullable Duration scrollTime) {
this.scrollTime = scrollTime;
return self();
}
public SELF withExplain(boolean explain) {
this.explain = explain;
return self();
}
public SELF withSearchAfter(@Nullable List<Object> searchAfter) {
this.searchAfter = searchAfter;
return self();
}
public SELF withRequestCache(@Nullable Boolean requestCache) {
this.requestCache = requestCache;
return self();
}
public SELF withIdsWithRouting(List<Query.IdWithRouting> idsWithRouting) {
Assert.notNull(idsWithRouting, "idsWithRouting must not be null");
this.idsWithRouting.clear();
this.idsWithRouting.addAll(idsWithRouting);
return self();
}
public SELF withRuntimeFields(List<RuntimeField> runtimeFields) {
Assert.notNull(runtimeFields, "runtimeFields must not be null");
this.runtimeFields.clear();
this.runtimeFields.addAll(runtimeFields);
return self();
}
public SELF withRescorerQueries(List<RescorerQuery> rescorerQueries) {
Assert.notNull(rescorerQueries, "rescorerQueries must not be null");
this.rescorerQueries.clear();
this.rescorerQueries.addAll(rescorerQueries);
return self();
}
public SELF withRescorerQuery(RescorerQuery rescorerQuery) {
Assert.notNull(rescorerQuery, "rescorerQuery must not be null");
this.rescorerQueries.add(rescorerQuery);
return self();
}
/**
* @since 5.0
*/
public SELF withPointInTime(@Nullable Query.PointInTime pointInTime) {
this.pointInTime = pointInTime;
return self();
}
public abstract Q build();
private SELF self() {
@@ -71,8 +71,8 @@ public class IndexQueryBuilder {
}
public IndexQueryBuilder withSeqNoPrimaryTerm(SeqNoPrimaryTerm seqNoPrimaryTerm) {
this.seqNo = seqNoPrimaryTerm.getSequenceNumber();
this.primaryTerm = seqNoPrimaryTerm.getPrimaryTerm();
this.seqNo = seqNoPrimaryTerm.sequenceNumber();
this.primaryTerm = seqNoPrimaryTerm.primaryTerm();
return this;
}
@@ -439,6 +439,15 @@ public interface Query {
@Nullable
List<IndexBoost> getIndicesBoost();
/**
* @return the point in time id to use in the query
* @since 5.0
*/
@Nullable
default PointInTime getPointInTime() {
return null;
};
/**
* @since 4.3
*/
@@ -451,25 +460,22 @@ public interface Query {
*
* @since 4.3
*/
final class IdWithRouting {
private final String id;
@Nullable private final String routing;
public IdWithRouting(String id, @Nullable String routing) {
record IdWithRouting(String id, @Nullable String routing) {
public IdWithRouting {
Assert.notNull(id, "id must not be null");
this.id = id;
this.routing = routing;
}
public String getId() {
return id;
}
}
@Nullable
public String getRouting() {
return routing;
}
/**
* Desscribes the point in time parameters for a query
*
* @param id the point in time id
* @param keepAlive the new keep alive value to be sent with the query
* @since 5.0
*/
record PointInTime(String id, Duration keepAlive) {
}
}
@@ -26,45 +26,6 @@ import org.springframework.lang.Nullable;
* @author Peter-Josef Meisch
* @since 4.4
*/
public final class ScriptData {
@Nullable private final ScriptType type;
@Nullable private final String language;
@Nullable private final String script;
@Nullable private final String scriptName;
@Nullable private final Map<String, Object> params;
public ScriptData(@Nullable ScriptType type, @Nullable String language, @Nullable String script,
@Nullable String scriptName, @Nullable Map<String, Object> params) {
this.type = type;
this.language = language;
this.script = script;
this.scriptName = scriptName;
this.params = params;
}
@Nullable
public ScriptType getType() {
return type;
}
@Nullable
public String getLanguage() {
return language;
}
@Nullable
public String getScript() {
return script;
}
@Nullable
public String getScriptName() {
return scriptName;
}
@Nullable
public Map<String, Object> getParams() {
return params;
}
public record ScriptData(@Nullable ScriptType type, @Nullable String language, @Nullable String script,
@Nullable String scriptName, @Nullable Map<String, Object> params) {
}

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