mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-08-07 01:53:44 +00:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6697755b45 | ||
|
e49bb63df4 | ||
|
006cda6de6 | ||
|
f51efa2cad | ||
|
6e30801a59 | ||
|
e64d0ada62 | ||
|
7f7cf3f52e | ||
|
55d470fc91 | ||
|
b7290b5d9c | ||
|
40414f0bde | ||
|
cb71caa504 | ||
|
ed9abce7af | ||
|
12ddb74fae | ||
|
6324b72707 | ||
|
6e49980c7c | ||
|
f9509f2696 | ||
|
7f53944e1b | ||
|
a9d2aaa93d | ||
|
158f5fc342 | ||
|
6268133506 | ||
|
923787c2d1 | ||
|
62fcbd44fa | ||
|
77ba620fc6 | ||
|
06704d974d | ||
|
897cb0a957 | ||
|
a8557a36dc | ||
|
2678cdc7b6 | ||
|
eb42312ebe | ||
|
af13fe0247 | ||
|
e81810c0d7 | ||
|
e9c7c0ee95 | ||
|
3a4425053e | ||
|
09984f86e6 | ||
|
710526c5f6 | ||
|
1acd392af7 | ||
|
76fe240a24 | ||
|
e298bc9f7a | ||
|
49d5dee5aa | ||
|
df6a127629 | ||
|
08a1ef3a28 | ||
|
62a34cf09c | ||
|
cc5f149c5a | ||
|
0728c8e4aa | ||
|
ebbe242a72 | ||
|
0ce9a1c400 | ||
|
22763d17a7 | ||
|
9870de1e77 | ||
|
8c9d9ae1e7 | ||
|
945179e4eb | ||
|
ea38ef1d41 | ||
|
acbfba94ac | ||
|
5a0f556a3b | ||
|
a07ac3c93d | ||
|
9d025dd469 | ||
|
925921f174 | ||
|
2f0a259045 | ||
|
9ffcb092db |
@ -8,3 +8,7 @@
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
|
||||
--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
|
||||
--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
|
||||
--add-opens=java.base/java.util=ALL-UNNAMED
|
||||
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED
|
||||
--add-opens=java.base/java.text=ALL-UNNAMED
|
||||
--add-opens=java.desktop/java.awt.font=ALL-UNNAMED
|
||||
|
4
.mvn/wrapper/maven-wrapper.properties
vendored
4
.mvn/wrapper/maven-wrapper.properties
vendored
@ -1,3 +1,3 @@
|
||||
#Thu Nov 07 09:47:28 CET 2024
|
||||
#Thu Jul 17 13:59:56 CEST 2025
|
||||
wrapperUrl=https\://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
|
||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
|
||||
|
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@ -9,7 +9,7 @@ pipeline {
|
||||
|
||||
triggers {
|
||||
pollSCM 'H/10 * * * *'
|
||||
upstream(upstreamProjects: "spring-data-commons/4.0.x", threshold: hudson.model.Result.SUCCESS)
|
||||
upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS)
|
||||
}
|
||||
|
||||
options {
|
||||
|
@ -1,25 +1,20 @@
|
||||
# Java versions
|
||||
java.main.tag=17.0.13_11-jdk-focal
|
||||
java.next.tag=23.0.1_11-jdk-noble
|
||||
java.main.tag=24.0.1_9-jdk-noble
|
||||
java.next.tag=24.0.1_9-jdk-noble
|
||||
|
||||
# Docker container images - standard
|
||||
docker.java.main.image=library/eclipse-temurin:${java.main.tag}
|
||||
docker.java.next.image=library/eclipse-temurin:${java.next.tag}
|
||||
|
||||
# Supported versions of MongoDB
|
||||
docker.mongodb.4.4.version=4.4.25
|
||||
docker.mongodb.5.0.version=5.0.21
|
||||
docker.mongodb.6.0.version=6.0.10
|
||||
docker.mongodb.7.0.version=7.0.2
|
||||
docker.mongodb.8.0.version=8.0.0
|
||||
docker.mongodb.6.0.version=6.0.23
|
||||
docker.mongodb.7.0.version=7.0.20
|
||||
docker.mongodb.8.0.version=8.0.9
|
||||
|
||||
# Supported versions of Redis
|
||||
docker.redis.6.version=6.2.13
|
||||
docker.redis.7.version=7.2.4
|
||||
|
||||
# Supported versions of Cassandra
|
||||
docker.cassandra.3.version=3.11.16
|
||||
|
||||
# Docker environment settings
|
||||
docker.java.inside.basic=-v $HOME:/tmp/jenkins-home
|
||||
docker.java.inside.docker=-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home
|
||||
|
47
pom.xml
47
pom.xml
@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<version>6.0.0-M2</version>
|
||||
<version>6.0.0-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>4.0.0-M2</version>
|
||||
<version>4.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Spring Data Elasticsearch</name>
|
||||
@ -18,10 +18,10 @@
|
||||
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
|
||||
|
||||
<properties>
|
||||
<springdata.commons>4.0.0-M2</springdata.commons>
|
||||
<springdata.commons>4.0.0-SNAPSHOT</springdata.commons>
|
||||
|
||||
<!-- version of the ElasticsearchClient -->
|
||||
<elasticsearch-java>8.17.4</elasticsearch-java>
|
||||
<elasticsearch-java>9.1.0</elasticsearch-java>
|
||||
|
||||
<hoverfly>0.19.0</hoverfly>
|
||||
<log4j>2.23.1</log4j>
|
||||
@ -132,6 +132,27 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- the old RestCLient is an optional dependency for user that still want to use it-->
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>elasticsearch-rest-client</artifactId>
|
||||
<version>${elasticsearch-java}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.querydsl</groupId>
|
||||
<artifactId>querydsl-core</artifactId>
|
||||
<version>${querydsl}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson JSON Mapper -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
@ -450,8 +471,20 @@
|
||||
</profiles>
|
||||
|
||||
<repositories>
|
||||
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
</project>
|
||||
|
@ -12,6 +12,7 @@
|
||||
*** xref:migration-guides/migration-guide-5.2-5.3.adoc[]
|
||||
*** xref:migration-guides/migration-guide-5.3-5.4.adoc[]
|
||||
*** xref:migration-guides/migration-guide-5.4-5.5.adoc[]
|
||||
*** xref:migration-guides/migration-guide-5.5-6.0.adoc[]
|
||||
|
||||
|
||||
* xref:elasticsearch.adoc[]
|
||||
|
@ -6,10 +6,10 @@ This chapter illustrates configuration and usage of supported Elasticsearch clie
|
||||
Spring Data Elasticsearch operates upon an Elasticsearch client (provided by Elasticsearch client libraries) that is connected to a single Elasticsearch node or a cluster.
|
||||
Although the Elasticsearch Client can be used directly to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of xref:elasticsearch/template.adoc[Elasticsearch Operations] and xref:elasticsearch/repositories/elasticsearch-repositories.adoc[Elasticsearch Repositories].
|
||||
|
||||
[[elasticsearch.clients.restclient]]
|
||||
== Imperative Rest Client
|
||||
[[elasticsearch.clients.rest5client]]
|
||||
== Imperative Rest5Client
|
||||
|
||||
To use the imperative (non-reactive) client, a configuration bean must be configured like this:
|
||||
To use the imperative (non-reactive) Rest5Client, a configuration bean must be configured like this:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
@ -31,7 +31,7 @@ public class MyClientConfig extends ElasticsearchConfiguration {
|
||||
<.> for a detailed description of the builder methods see xref:elasticsearch/clients.adoc#elasticsearch.clients.configuration[Client Configuration]
|
||||
====
|
||||
|
||||
The javadoc:org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration[]] class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
|
||||
The javadoc:org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration[] class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
|
||||
|
||||
|
||||
The following beans can then be injected in other Spring components:
|
||||
@ -46,7 +46,81 @@ ElasticsearchOperations operations; <.>
|
||||
ElasticsearchClient elasticsearchClient; <.>
|
||||
|
||||
@Autowired
|
||||
RestClient restClient; <.>
|
||||
Rest5Client rest5Client; <.>
|
||||
|
||||
@Autowired
|
||||
JsonpMapper jsonpMapper; <.>
|
||||
----
|
||||
|
||||
<.> an implementation of javadoc:org.springframework.data.elasticsearch.core.ElasticsearchOperations[]
|
||||
<.> the `co.elastic.clients.elasticsearch.ElasticsearchClient` that is used.
|
||||
<.> the low level `Rest5Client` from the Elasticsearch libraries
|
||||
<.> the `JsonpMapper` user by the Elasticsearch `Transport`
|
||||
====
|
||||
|
||||
Basically one should just use the javadoc:org.springframework.data.elasticsearch.core.ElasticsearchOperations[] to interact with the Elasticsearch cluster.
|
||||
When using repositories, this instance is used under the hood as well.
|
||||
|
||||
[[elasticsearch.clients.restclient]]
|
||||
== Deprecated Imperative RestClient
|
||||
|
||||
To use the imperative (non-reactive) RestClient - deprecated since version 6 - , the following dependency needs to be added, adapt the correct version. The exclusion is needed in a Spring Boot application:
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>elasticsearch-rest-client</artifactId>
|
||||
<version>${elasticsearch-client.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
----
|
||||
====
|
||||
|
||||
The configuration bean must be configured like this:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchLegacyRestClientConfiguration;
|
||||
|
||||
@Configuration
|
||||
public class MyClientConfig extends ElasticsearchLegacyRestClientConfiguration {
|
||||
|
||||
@Override
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() <.>
|
||||
.connectedTo("localhost:9200")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
<.> for a detailed description of the builder methods see xref:elasticsearch/clients.adoc#elasticsearch.clients.configuration[Client Configuration]
|
||||
====
|
||||
|
||||
The javadoc:org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration[] class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
|
||||
|
||||
|
||||
The following beans can then be injected in other Spring components:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.beans.factory.annotation.Autowired;@Autowired
|
||||
ElasticsearchOperations operations; <.>
|
||||
|
||||
@Autowired
|
||||
ElasticsearchClient elasticsearchClient; <.>
|
||||
|
||||
@Autowired
|
||||
RestClient restClient; <.>
|
||||
|
||||
@Autowired
|
||||
JsonpMapper jsonpMapper; <.>
|
||||
@ -61,8 +135,8 @@ JsonpMapper jsonpMapper; <.>
|
||||
Basically one should just use the javadoc:org.springframework.data.elasticsearch.core.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
|
||||
[[elasticsearch.clients.reactiverest5client]]
|
||||
== Reactive Rest5Client
|
||||
|
||||
When working with the reactive stack, the configuration must be derived from a different class:
|
||||
|
||||
@ -99,6 +173,65 @@ ReactiveElasticsearchOperations operations; <.>
|
||||
@Autowired
|
||||
ReactiveElasticsearchClient elasticsearchClient; <.>
|
||||
|
||||
@Autowired
|
||||
Rest5Client rest5Client; <.>
|
||||
|
||||
@Autowired
|
||||
JsonpMapper jsonpMapper; <.>
|
||||
----
|
||||
|
||||
the following can be injected:
|
||||
|
||||
<.> an implementation of javadoc:org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations[]
|
||||
<.> the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient` that is used.
|
||||
This is a reactive implementation based on the Elasticsearch client implementation.
|
||||
<.> the low level `RestClient` from the Elasticsearch libraries
|
||||
<.> the `JsonpMapper` user by the Elasticsearch `Transport`
|
||||
====
|
||||
|
||||
Basically one should just use the javadoc:org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations[] to interact with the Elasticsearch cluster.
|
||||
When using repositories, this instance is used under the hood as well.
|
||||
|
||||
[[elasticsearch.clients.reactiverestclient]]
|
||||
== Deprecated Reactive RestClient
|
||||
|
||||
See the section above for the imperative code to use the deprecated RestClient for the necessary dependencies to include.
|
||||
|
||||
When working with the reactive stack, the configuration must be derived from a different class:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchLegacyRestClientConfiguration;
|
||||
|
||||
@Configuration
|
||||
public class MyClientConfig extends ReactiveElasticsearchLegacyRestClientConfiguration {
|
||||
|
||||
@Override
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() <.>
|
||||
.connectedTo("localhost:9200")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
<.> for a detailed description of the builder methods see xref:elasticsearch/clients.adoc#elasticsearch.clients.configuration[Client Configuration]
|
||||
====
|
||||
|
||||
The javadoc:org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchConfiguration[] class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
|
||||
|
||||
The following beans can then be injected in other Spring components:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Autowired
|
||||
ReactiveElasticsearchOperations operations; <.>
|
||||
|
||||
@Autowired
|
||||
ReactiveElasticsearchClient elasticsearchClient; <.>
|
||||
|
||||
@Autowired
|
||||
RestClient restClient; <.>
|
||||
|
||||
@ -183,8 +316,8 @@ In the case this is not enough, the user can add callback functions by using the
|
||||
|
||||
The following callbacks are provided:
|
||||
|
||||
[[elasticsearch.clients.configuration.callbacks.rest]]
|
||||
==== Configuration of the low level Elasticsearch `RestClient`:
|
||||
[[elasticsearch.clients.configuration.callbacks.rest5]]
|
||||
==== Configuration of the low level Elasticsearch `Rest5Client`:
|
||||
|
||||
This callback provides a `org.elasticsearch.client.RestClientBuilder` that can be used to configure the Elasticsearch
|
||||
`RestClient`:
|
||||
@ -193,7 +326,24 @@ This callback provides a `org.elasticsearch.client.RestClientBuilder` that can b
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||
.withClientConfigurer(Rest5Clients.ElasticsearchRest5ClientConfigurationCallback.from(restClientBuilder -> {
|
||||
// configure the Elasticsearch Rest5Client
|
||||
return restClientBuilder;
|
||||
}))
|
||||
.build();
|
||||
----
|
||||
====
|
||||
[[elasticsearch.clients.configuration.callbacks.rest]]
|
||||
==== Configuration of the deprecated low level Elasticsearch `RestClient`:
|
||||
|
||||
This callback provides a `org.elasticsearch.client.RestClientBuilder` that can be used to configure the Elasticsearch
|
||||
`RestClient`:
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withClientConfigurer(RestClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||
// configure the Elasticsearch RestClient
|
||||
return restClientBuilder;
|
||||
}))
|
||||
@ -201,10 +351,29 @@ ClientConfiguration.builder()
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.configurationcallbacks.httpasync]]
|
||||
==== Configuration of the HttpAsyncClient used by the low level Elasticsearch `RestClient`:
|
||||
[[elasticsearch.clients.configurationcallbacks.httpasync5]]
|
||||
==== Configuration of the HttpAsyncClient used by the low level Elasticsearch `Rest5Client`:
|
||||
|
||||
This callback provides a `org.apache.http.impl.nio.client.HttpAsyncClientBuilder` to configure the HttpCLient that is
|
||||
This callback provides a `org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder` to configure the HttpClient that is
|
||||
used by the `Rest5Client`.
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withClientConfigurer(Rest5Clients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
|
||||
// configure the HttpAsyncClient
|
||||
return httpAsyncClientBuilder;
|
||||
}))
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.configurationcallbacks.httpasync]]
|
||||
==== Configuration of the HttpAsyncClient used by the deprecated low level Elasticsearch `RestClient`:
|
||||
|
||||
This callback provides a `org.apache.http.impl.nio.client.HttpAsyncClientBuilder` to configure the HttpClient that is
|
||||
used by the `RestClient`.
|
||||
|
||||
====
|
||||
@ -212,7 +381,7 @@ used by the `RestClient`.
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
|
||||
.withClientConfigurer(RestClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
|
||||
// configure the HttpAsyncClient
|
||||
return httpAsyncClientBuilder;
|
||||
}))
|
||||
@ -220,6 +389,63 @@ ClientConfiguration.builder()
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.configurationcallbacks.connectionconfig]]
|
||||
==== Configuration of the ConnectionConfig used by the low level Elasticsearch `Rest5Client`:
|
||||
|
||||
This callback provides a `org.apache.hc.client5.http.config.ConnectionConfig` to configure the connection that is
|
||||
used by the `Rest5Client`.
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withClientConfigurer(Rest5Clients.ElasticsearchConnectionConfigurationCallback.from(connectionConfigBuilder -> {
|
||||
// configure the connection
|
||||
return connectionConfigBuilder;
|
||||
}))
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.configurationcallbacks.connectioncmanager]]
|
||||
==== Configuration of the ConnectionManager used by the low level Elasticsearch `Rest5Client`:
|
||||
|
||||
This callback provides a `org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder` to configure the connection manager that is
|
||||
used by the `Rest5Client`.
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withClientConfigurer(Rest5Clients.ElasticsearchConnectionManagerCallback.from(connectionManagerBuilder -> {
|
||||
// configure the connection manager
|
||||
return connectionManagerBuilder;
|
||||
}))
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.configurationcallbacks.requestconfig]]
|
||||
==== Configuration of the RequestConfig used by the low level Elasticsearch `Rest5Client`:
|
||||
|
||||
This callback provides a `org.apache.hc.client5.http.config.RequestConfig` to configure the RequestConfig that is
|
||||
used by the `Rest5Client`.
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withClientConfigurer(Rest5Clients.ElasticsearchRequestConfigCallback.from(requestConfigBuilder -> {
|
||||
// configure the request config
|
||||
return requestConfigBuilder;
|
||||
}))
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.logging]]
|
||||
== Client Logging
|
||||
|
||||
|
@ -1,10 +1,19 @@
|
||||
[[new-features]]
|
||||
= What's new
|
||||
|
||||
[[new-features.6-0-0]]
|
||||
== New in Spring Data Elasticsearch 6.0
|
||||
|
||||
* Upgarde to Spring 7
|
||||
* Switch to jspecify nullability annotations
|
||||
* Upgrade to Elasticsearch 9.1.0
|
||||
* Use the new Elasticsearch Rest5Client as default
|
||||
|
||||
|
||||
[[new-features.5-5-0]]
|
||||
== New in Spring Data Elasticsearch 5.5
|
||||
|
||||
* Upgrade to Elasticsearch 8.17.4.
|
||||
* Upgrade to Elasticsearch 8.18.1.
|
||||
* Add support for the `@SearchTemplateQuery` annotation on repository methods.
|
||||
* Scripted field properties of type collection can be populated from scripts returning arrays.
|
||||
|
||||
|
@ -6,11 +6,11 @@ The following table shows the Elasticsearch and Spring versions that are used by
|
||||
[cols="^,^,^,^",options="header"]
|
||||
|===
|
||||
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework
|
||||
| 2025.1 (in development) | 6.0.x | 8.17.2 | 6.2.x
|
||||
| 2025.0 (in development) | 5.5.x | 8.17.2 | 6.2.x
|
||||
| 2025.1 (in development) | 6.0.x | 9.1.0 | 7.0.x
|
||||
| 2025.0 | 5.5.x | 8.18.1 | 6.2.x
|
||||
| 2024.1 | 5.4.x | 8.15.5 | 6.1.x
|
||||
| 2024.0 | 5.3.x | 8.13.4 | 6.1.x
|
||||
| 2023.1 (Vaughan) | 5.2.xfootnote:oom[Out of maintenance] | 8.11.1 | 6.1.x
|
||||
| 2024.0 | 5.3.xfootnote:oom[Out of maintenance] | 8.13.4 | 6.1.x
|
||||
| 2023.1 (Vaughan) | 5.2.xfootnote:oom[] | 8.11.1 | 6.1.x
|
||||
| 2023.0 (Ullmann) | 5.1.xfootnote:oom[] | 8.7.1 | 6.0.x
|
||||
| 2022.0 (Turing) | 5.0.xfootnote:oom[] | 8.5.3 | 6.0.x
|
||||
| 2021.2 (Raj) | 4.4.xfootnote:oom[] | 7.17.3 | 5.3.x
|
||||
|
@ -0,0 +1,27 @@
|
||||
[[elasticsearch-migration-guide-5.5-6.0]]
|
||||
= Upgrading from 5.5.x to 6.0.x
|
||||
|
||||
This section describes breaking changes from version 5.5.x to 6.0.x and how removed features can be replaced by new introduced features.
|
||||
|
||||
[[elasticsearch-migration-guide-5.5-6.0.breaking-changes]]
|
||||
== Breaking Changes
|
||||
|
||||
From version 6.0 on, Spring Data Elasticsearch uses the Elasticsearch 9 libraries and as default the new `Rest5Client` provided by these libraries. It is still possible to use the old `RestClient`, check xref:elasticsearch/clients.adoc[Elasticsearch clients] for information. The configuration callbacks for this `RestClient` have been moved from `org.springframework.data.elasticsearch.client.elc.ElasticsearchClients` to the `org.springframework.data.elasticsearch.client.elc.rest_client.RestClients` class.
|
||||
|
||||
In the `org.springframework.data.elasticsearch.core.query.UpdateQuery` class the type of the two fields `ifSeqNo` and `ifPrimaryTerm` has changed from `Integer` to `Long` to align with the normal query and the underlying Elasticsearch client.
|
||||
|
||||
[[elasticsearch-migration-guide-5.5-6.0.deprecations]]
|
||||
== Deprecations
|
||||
|
||||
All the code using the old `RestClient` has been moved to the `org.springframework.data.elasticsearch.client.elc.rest_client` package and has been deprecated. Users should switch to the classes from the `org.springframework.data.elasticsearch.client.elc.rest5_client` package.
|
||||
|
||||
|
||||
=== Removals
|
||||
|
||||
The `org.springframework.data.elasticsearch.core.query.ScriptType` enum has been removed. To distinguish between an inline and a stored script set the appropriate values in the `org.springframework.data.elasticsearch.core.query.ScriptData` record.
|
||||
|
||||
These methods have been removed because the Elasticsearch Client 9 does not support them anymore:
|
||||
```
|
||||
org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchIndicesClient.unfreeze(UnfreezeRequest)
|
||||
org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchIndicesClient.unfreeze(Function<UnfreezeRequest.Builder, ObjectBuilder<UnfreezeRequest>>)
|
||||
```
|
@ -127,10 +127,16 @@ public interface ClientConfiguration {
|
||||
Optional<String> getCaFingerprint();
|
||||
|
||||
/**
|
||||
* Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured.
|
||||
* Returns the {@link HostnameVerifier} to use. Must be {@link Optional#empty()} if not configured.
|
||||
* Cannot be used with the Rest5Client used from Elasticsearch 9 on as the underlying Apache http components 5 does not offer a way
|
||||
* to set this. Users that need a hostname verifier must integrate this in a SSLContext.
|
||||
* Returning a value here is ignored in this case
|
||||
*
|
||||
* @return the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured.
|
||||
* @deprecated since 6.0
|
||||
*/
|
||||
// todo #3117 document this
|
||||
@Deprecated(since = "6.0", forRemoval=true)
|
||||
Optional<HostnameVerifier> getHostNameVerifier();
|
||||
|
||||
/**
|
||||
|
@ -24,14 +24,6 @@ import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||
import co.elastic.clients.elasticsearch.core.search.NestedIdentity;
|
||||
import co.elastic.clients.json.JsonData;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@ -44,6 +36,13 @@ import org.springframework.data.elasticsearch.core.document.SearchDocumentAdapte
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Utility class to adapt different Elasticsearch responses to a
|
||||
* {@link org.springframework.data.elasticsearch.core.document.Document}
|
||||
@ -55,187 +54,188 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
final class DocumentAdapters {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(DocumentAdapters.class);
|
||||
private static final Log LOGGER = LogFactory.getLog(DocumentAdapters.class);
|
||||
|
||||
private DocumentAdapters() {}
|
||||
private DocumentAdapters() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SearchDocument} from a {@link Hit} returned by the Elasticsearch client.
|
||||
*
|
||||
* @param hit the hit object
|
||||
* @param jsonpMapper to map JsonData objects
|
||||
* @return the created {@link SearchDocument}
|
||||
*/
|
||||
public static SearchDocument from(Hit<?> hit, JsonpMapper jsonpMapper) {
|
||||
/**
|
||||
* Creates a {@link SearchDocument} from a {@link Hit} returned by the Elasticsearch client.
|
||||
*
|
||||
* @param hit the hit object
|
||||
* @param jsonpMapper to map JsonData objects
|
||||
* @return the created {@link SearchDocument}
|
||||
*/
|
||||
public static SearchDocument from(Hit<?> hit, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(hit, "hit must not be null");
|
||||
Assert.notNull(hit, "hit must not be null");
|
||||
|
||||
Map<String, List<String>> highlightFields = hit.highlight();
|
||||
Map<String, List<String>> highlightFields = hit.highlight();
|
||||
|
||||
Map<String, SearchDocumentResponse> innerHits = new LinkedHashMap<>();
|
||||
hit.innerHits().forEach((name, innerHitsResult) -> {
|
||||
// noinspection ReturnOfNull
|
||||
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, 0, null, null,
|
||||
searchDocument -> null, jsonpMapper));
|
||||
});
|
||||
Map<String, SearchDocumentResponse> innerHits = new LinkedHashMap<>();
|
||||
hit.innerHits().forEach((name, innerHitsResult) -> {
|
||||
// noinspection ReturnOfNull
|
||||
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, 0, null, null,
|
||||
searchDocument -> null, jsonpMapper));
|
||||
});
|
||||
|
||||
NestedMetaData nestedMetaData = from(hit.nested());
|
||||
NestedMetaData nestedMetaData = from(hit.nested());
|
||||
|
||||
Explanation explanation = from(hit.explanation());
|
||||
Explanation explanation = from(hit.explanation());
|
||||
|
||||
List<String> matchedQueries = hit.matchedQueries();
|
||||
Map<String, Double> matchedQueries = hit.matchedQueries();
|
||||
|
||||
Function<Map<String, JsonData>, EntityAsMap> fromFields = fields -> {
|
||||
StringBuilder sb = new StringBuilder("{");
|
||||
final boolean[] firstField = { true };
|
||||
hit.fields().forEach((key, jsonData) -> {
|
||||
if (!firstField[0]) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append('"').append(key).append("\":") //
|
||||
.append(jsonData.toJson(jsonpMapper).toString());
|
||||
firstField[0] = false;
|
||||
});
|
||||
sb.append('}');
|
||||
return new EntityAsMap().fromJson(sb.toString());
|
||||
};
|
||||
Function<Map<String, JsonData>, EntityAsMap> fromFields = fields -> {
|
||||
StringBuilder sb = new StringBuilder("{");
|
||||
final boolean[] firstField = {true};
|
||||
hit.fields().forEach((key, jsonData) -> {
|
||||
if (!firstField[0]) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append('"').append(key).append("\":") //
|
||||
.append(jsonData.toJson(jsonpMapper).toString());
|
||||
firstField[0] = false;
|
||||
});
|
||||
sb.append('}');
|
||||
return new EntityAsMap().fromJson(sb.toString());
|
||||
};
|
||||
|
||||
EntityAsMap hitFieldsAsMap = fromFields.apply(hit.fields());
|
||||
EntityAsMap hitFieldsAsMap = fromFields.apply(hit.fields());
|
||||
|
||||
Map<String, List<Object>> documentFields = new LinkedHashMap<>();
|
||||
hitFieldsAsMap.forEach((key, value) -> {
|
||||
if (value instanceof List) {
|
||||
// noinspection unchecked
|
||||
documentFields.put(key, (List<Object>) value);
|
||||
} else {
|
||||
documentFields.put(key, Collections.singletonList(value));
|
||||
}
|
||||
});
|
||||
Map<String, List<Object>> documentFields = new LinkedHashMap<>();
|
||||
hitFieldsAsMap.forEach((key, value) -> {
|
||||
if (value instanceof List) {
|
||||
// noinspection unchecked
|
||||
documentFields.put(key, (List<Object>) value);
|
||||
} else {
|
||||
documentFields.put(key, Collections.singletonList(value));
|
||||
}
|
||||
});
|
||||
|
||||
Document document;
|
||||
Object source = hit.source();
|
||||
if (source == null) {
|
||||
document = Document.from(hitFieldsAsMap);
|
||||
} else {
|
||||
if (source instanceof EntityAsMap entityAsMap) {
|
||||
document = Document.from(entityAsMap);
|
||||
} else if (source instanceof JsonData jsonData) {
|
||||
document = Document.from(jsonData.to(EntityAsMap.class));
|
||||
} else {
|
||||
Document document;
|
||||
Object source = hit.source();
|
||||
if (source == null) {
|
||||
document = Document.from(hitFieldsAsMap);
|
||||
} else {
|
||||
if (source instanceof EntityAsMap entityAsMap) {
|
||||
document = Document.from(entityAsMap);
|
||||
} else if (source instanceof JsonData jsonData) {
|
||||
document = Document.from(jsonData.to(EntityAsMap.class));
|
||||
} else {
|
||||
|
||||
if (LOGGER.isWarnEnabled()) {
|
||||
LOGGER.warn(String.format("Cannot map from type " + source.getClass().getName()));
|
||||
}
|
||||
document = Document.create();
|
||||
}
|
||||
}
|
||||
document.setIndex(hit.index());
|
||||
document.setId(hit.id());
|
||||
if (LOGGER.isWarnEnabled()) {
|
||||
LOGGER.warn(String.format("Cannot map from type " + source.getClass().getName()));
|
||||
}
|
||||
document = Document.create();
|
||||
}
|
||||
}
|
||||
document.setIndex(hit.index());
|
||||
document.setId(hit.id());
|
||||
|
||||
if (hit.version() != null) {
|
||||
document.setVersion(hit.version());
|
||||
}
|
||||
document.setSeqNo(hit.seqNo() != null && hit.seqNo() >= 0 ? hit.seqNo() : -2); // -2 was the default value in the
|
||||
// old client
|
||||
document.setPrimaryTerm(hit.primaryTerm() != null && hit.primaryTerm() > 0 ? hit.primaryTerm() : 0);
|
||||
if (hit.version() != null) {
|
||||
document.setVersion(hit.version());
|
||||
}
|
||||
document.setSeqNo(hit.seqNo() != null && hit.seqNo() >= 0 ? hit.seqNo() : -2); // -2 was the default value in the
|
||||
// old client
|
||||
document.setPrimaryTerm(hit.primaryTerm() != null && hit.primaryTerm() > 0 ? hit.primaryTerm() : 0);
|
||||
|
||||
float score = hit.score() != null ? hit.score().floatValue() : Float.NaN;
|
||||
return new SearchDocumentAdapter(document, score, hit.sort().stream().map(TypeUtils::toObject).toArray(),
|
||||
documentFields, highlightFields, innerHits, nestedMetaData, explanation, matchedQueries, hit.routing());
|
||||
}
|
||||
float score = hit.score() != null ? hit.score().floatValue() : Float.NaN;
|
||||
return new SearchDocumentAdapter(document, score, hit.sort().stream().map(TypeUtils::toObject).toArray(),
|
||||
documentFields, highlightFields, innerHits, nestedMetaData, explanation, matchedQueries, hit.routing());
|
||||
}
|
||||
|
||||
public static SearchDocument from(CompletionSuggestOption<EntityAsMap> completionSuggestOption) {
|
||||
public static SearchDocument from(CompletionSuggestOption<EntityAsMap> completionSuggestOption) {
|
||||
|
||||
Document document = completionSuggestOption.source() != null ? Document.from(completionSuggestOption.source())
|
||||
: Document.create();
|
||||
document.setIndex(completionSuggestOption.index());
|
||||
Document document = completionSuggestOption.source() != null ? Document.from(completionSuggestOption.source())
|
||||
: Document.create();
|
||||
document.setIndex(completionSuggestOption.index());
|
||||
|
||||
if (completionSuggestOption.id() != null) {
|
||||
document.setId(completionSuggestOption.id());
|
||||
}
|
||||
if (completionSuggestOption.id() != null) {
|
||||
document.setId(completionSuggestOption.id());
|
||||
}
|
||||
|
||||
float score = completionSuggestOption.score() != null ? completionSuggestOption.score().floatValue() : Float.NaN;
|
||||
return new SearchDocumentAdapter(document, score, new Object[] {}, Collections.emptyMap(), Collections.emptyMap(),
|
||||
Collections.emptyMap(), null, null, null, completionSuggestOption.routing());
|
||||
}
|
||||
float score = completionSuggestOption.score() != null ? completionSuggestOption.score().floatValue() : Float.NaN;
|
||||
return new SearchDocumentAdapter(document, score, new Object[]{}, Collections.emptyMap(), Collections.emptyMap(),
|
||||
Collections.emptyMap(), null, null, null, completionSuggestOption.routing());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Explanation from(co.elastic.clients.elasticsearch.core.explain.@Nullable Explanation explanation) {
|
||||
@Nullable
|
||||
private static Explanation from(co.elastic.clients.elasticsearch.core.explain.@Nullable Explanation explanation) {
|
||||
|
||||
if (explanation == null) {
|
||||
return null;
|
||||
}
|
||||
List<Explanation> details = explanation.details().stream().map(DocumentAdapters::from).collect(Collectors.toList());
|
||||
return new Explanation(true, (double) explanation.value(), explanation.description(), details);
|
||||
}
|
||||
if (explanation == null) {
|
||||
return null;
|
||||
}
|
||||
List<Explanation> details = explanation.details().stream().map(DocumentAdapters::from).collect(Collectors.toList());
|
||||
return new Explanation(true, (double) explanation.value(), explanation.description(), details);
|
||||
}
|
||||
|
||||
private static Explanation from(ExplanationDetail explanationDetail) {
|
||||
private static Explanation from(ExplanationDetail explanationDetail) {
|
||||
|
||||
List<Explanation> details = explanationDetail.details().stream().map(DocumentAdapters::from)
|
||||
.collect(Collectors.toList());
|
||||
return new Explanation(null, (double) explanationDetail.value(), explanationDetail.description(), details);
|
||||
}
|
||||
List<Explanation> details = explanationDetail.details().stream().map(DocumentAdapters::from)
|
||||
.collect(Collectors.toList());
|
||||
return new Explanation(null, (double) explanationDetail.value(), explanationDetail.description(), details);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static NestedMetaData from(@Nullable NestedIdentity nestedIdentity) {
|
||||
@Nullable
|
||||
private static NestedMetaData from(@Nullable NestedIdentity nestedIdentity) {
|
||||
|
||||
if (nestedIdentity == null) {
|
||||
return null;
|
||||
}
|
||||
if (nestedIdentity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
NestedMetaData child = from(nestedIdentity.nested());
|
||||
return NestedMetaData.of(nestedIdentity.field(), nestedIdentity.offset(), child);
|
||||
}
|
||||
NestedMetaData child = from(nestedIdentity.nested());
|
||||
return NestedMetaData.of(nestedIdentity.field(), nestedIdentity.offset(), child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Document} from a {@link GetResponse} where the found document is contained as {@link EntityAsMap}.
|
||||
*
|
||||
* @param getResponse the response instance
|
||||
* @return the Document
|
||||
*/
|
||||
@Nullable
|
||||
public static Document from(GetResult<EntityAsMap> getResponse) {
|
||||
/**
|
||||
* Creates a {@link Document} from a {@link GetResponse} where the found document is contained as {@link EntityAsMap}.
|
||||
*
|
||||
* @param getResponse the response instance
|
||||
* @return the Document
|
||||
*/
|
||||
@Nullable
|
||||
public static Document from(GetResult<EntityAsMap> getResponse) {
|
||||
|
||||
Assert.notNull(getResponse, "getResponse must not be null");
|
||||
Assert.notNull(getResponse, "getResponse must not be null");
|
||||
|
||||
if (!getResponse.found()) {
|
||||
return null;
|
||||
}
|
||||
if (!getResponse.found()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Document document = getResponse.source() != null ? Document.from(getResponse.source()) : Document.create();
|
||||
document.setIndex(getResponse.index());
|
||||
document.setId(getResponse.id());
|
||||
Document document = getResponse.source() != null ? Document.from(getResponse.source()) : Document.create();
|
||||
document.setIndex(getResponse.index());
|
||||
document.setId(getResponse.id());
|
||||
|
||||
if (getResponse.version() != null) {
|
||||
document.setVersion(getResponse.version());
|
||||
}
|
||||
if (getResponse.version() != null) {
|
||||
document.setVersion(getResponse.version());
|
||||
}
|
||||
|
||||
if (getResponse.seqNo() != null) {
|
||||
document.setSeqNo(getResponse.seqNo());
|
||||
}
|
||||
if (getResponse.seqNo() != null) {
|
||||
document.setSeqNo(getResponse.seqNo());
|
||||
}
|
||||
|
||||
if (getResponse.primaryTerm() != null) {
|
||||
document.setPrimaryTerm(getResponse.primaryTerm());
|
||||
}
|
||||
if (getResponse.primaryTerm() != null) {
|
||||
document.setPrimaryTerm(getResponse.primaryTerm());
|
||||
}
|
||||
|
||||
return document;
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of {@link MultiGetItem}s from a {@link MgetResponse} where the data is contained as
|
||||
* {@link EntityAsMap} instances.
|
||||
*
|
||||
* @param mgetResponse the response instance
|
||||
* @return list of multiget items
|
||||
*/
|
||||
public static List<MultiGetItem<Document>> from(MgetResponse<EntityAsMap> mgetResponse) {
|
||||
/**
|
||||
* Creates a list of {@link MultiGetItem}s from a {@link MgetResponse} where the data is contained as
|
||||
* {@link EntityAsMap} instances.
|
||||
*
|
||||
* @param mgetResponse the response instance
|
||||
* @return list of multiget items
|
||||
*/
|
||||
public static List<MultiGetItem<Document>> from(MgetResponse<EntityAsMap> mgetResponse) {
|
||||
|
||||
Assert.notNull(mgetResponse, "mgetResponse must not be null");
|
||||
Assert.notNull(mgetResponse, "mgetResponse must not be null");
|
||||
|
||||
return mgetResponse.docs().stream() //
|
||||
.map(itemResponse -> MultiGetItem.of( //
|
||||
itemResponse.isFailure() ? null : from(itemResponse.result()), //
|
||||
ResponseConverter.getFailure(itemResponse)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return mgetResponse.docs().stream() //
|
||||
.map(itemResponse -> MultiGetItem.of( //
|
||||
itemResponse.isFailure() ? null : from(itemResponse.result()), //
|
||||
ResponseConverter.getFailure(itemResponse)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -15,44 +15,38 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import static org.springframework.data.elasticsearch.client.elc.rest5_client.Rest5Clients.*;
|
||||
import static org.springframework.data.elasticsearch.client.elc.rest_client.RestClients.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.TransportUtils;
|
||||
import co.elastic.clients.transport.Version;
|
||||
import co.elastic.clients.transport.rest5_client.Rest5ClientOptions;
|
||||
import co.elastic.clients.transport.rest5_client.Rest5ClientTransport;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.RequestOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
|
||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||
import co.elastic.clients.transport.rest_client.RestClientTransport;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpRequestInterceptor;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.RestClientBuilder;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
import org.springframework.data.elasticsearch.support.VersionInfo;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility class to create the different Elasticsearch clients
|
||||
* Utility class to create the different Elasticsearch clients. The RestClient class is the one used in Elasticsearch
|
||||
* until version 9, it is still available, but it's use is deprecated. The Rest5Client class is the one that should be
|
||||
* used from Elasticsearch 9 on.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
@ -119,18 +113,32 @@ public final class ElasticsearchClients {
|
||||
*
|
||||
* @param restClient the underlying {@link RestClient}
|
||||
* @return the {@link ReactiveElasticsearchClient}
|
||||
* @deprecated since 6.0, use the version with a Rest5Client.
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static ReactiveElasticsearchClient createReactive(RestClient restClient) {
|
||||
return createReactive(restClient, null, DEFAULT_JSONP_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveElasticsearchClient}.
|
||||
*
|
||||
* @param rest5Client the underlying {@link RestClient}
|
||||
* @return the {@link ReactiveElasticsearchClient}
|
||||
*/
|
||||
public static ReactiveElasticsearchClient createReactive(Rest5Client rest5Client) {
|
||||
return createReactive(rest5Client, null, DEFAULT_JSONP_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveElasticsearchClient}.
|
||||
*
|
||||
* @param restClient the underlying {@link RestClient}
|
||||
* @param transportOptions options to be added to each request.
|
||||
* @return the {@link ReactiveElasticsearchClient}
|
||||
* @deprecated since 6.0, use the version with a Rest5Client.
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static ReactiveElasticsearchClient createReactive(RestClient restClient,
|
||||
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
|
||||
|
||||
@ -139,6 +147,21 @@ public final class ElasticsearchClients {
|
||||
var transport = getElasticsearchTransport(restClient, REACTIVE_CLIENT, transportOptions, jsonpMapper);
|
||||
return createReactive(transport);
|
||||
}
|
||||
/**
|
||||
* Creates a new {@link ReactiveElasticsearchClient}.
|
||||
*
|
||||
* @param rest5Client the underlying {@link RestClient}
|
||||
* @param transportOptions options to be added to each request.
|
||||
* @return the {@link ReactiveElasticsearchClient}
|
||||
*/
|
||||
public static ReactiveElasticsearchClient createReactive(Rest5Client rest5Client,
|
||||
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(rest5Client, "restClient must not be null");
|
||||
|
||||
var transport = getElasticsearchTransport(rest5Client, REACTIVE_CLIENT, transportOptions, jsonpMapper);
|
||||
return createReactive(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveElasticsearchClient} that uses the given {@link ElasticsearchTransport}.
|
||||
@ -156,17 +179,21 @@ public final class ElasticsearchClients {
|
||||
|
||||
// region imperative client
|
||||
/**
|
||||
* Creates a new imperative {@link ElasticsearchClient}
|
||||
* Creates a new imperative {@link ElasticsearchClient}. This uses a RestClient, if the old RestClient is needed, this
|
||||
* must be created with the {@link org.springframework.data.elasticsearch.client.elc.rest_client.RestClients} class
|
||||
* and passed in as parameter.
|
||||
*
|
||||
* @param clientConfiguration configuration options, must not be {@literal null}.
|
||||
* @return the {@link ElasticsearchClient}
|
||||
*/
|
||||
public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration) {
|
||||
return createImperative(getRestClient(clientConfiguration), null, DEFAULT_JSONP_MAPPER);
|
||||
return createImperative(getRest5Client(clientConfiguration), null, DEFAULT_JSONP_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new imperative {@link ElasticsearchClient}
|
||||
* Creates a new imperative {@link ElasticsearchClient}. This uses a RestClient, if the old RestClient is needed, this
|
||||
* must be created with the {@link org.springframework.data.elasticsearch.client.elc.rest_client.RestClients} class
|
||||
* and passed in as parameter.
|
||||
*
|
||||
* @param clientConfiguration configuration options, must not be {@literal null}.
|
||||
* @param transportOptions options to be added to each request.
|
||||
@ -174,7 +201,7 @@ public final class ElasticsearchClients {
|
||||
*/
|
||||
public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration,
|
||||
TransportOptions transportOptions) {
|
||||
return createImperative(getRestClient(clientConfiguration), transportOptions, DEFAULT_JSONP_MAPPER);
|
||||
return createImperative(getRest5Client(clientConfiguration), transportOptions, DEFAULT_JSONP_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -182,11 +209,23 @@ public final class ElasticsearchClients {
|
||||
*
|
||||
* @param restClient the RestClient to use
|
||||
* @return the {@link ElasticsearchClient}
|
||||
* @deprecated since 6.0, use the version with a Rest5Client.
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static ElasticsearchClient createImperative(RestClient restClient) {
|
||||
return createImperative(restClient, null, DEFAULT_JSONP_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new imperative {@link ElasticsearchClient}
|
||||
*
|
||||
* @param rest5Client the Rest5Client to use
|
||||
* @return the {@link ElasticsearchClient}
|
||||
*/
|
||||
public static ElasticsearchClient createImperative(Rest5Client rest5Client) {
|
||||
return createImperative(rest5Client, null, DEFAULT_JSONP_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new imperative {@link ElasticsearchClient}
|
||||
*
|
||||
@ -194,7 +233,9 @@ public final class ElasticsearchClients {
|
||||
* @param transportOptions options to be added to each request.
|
||||
* @param jsonpMapper the mapper for the transport to use
|
||||
* @return the {@link ElasticsearchClient}
|
||||
* @deprecated since 6.0, use the version with a Rest5Client.
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static ElasticsearchClient createImperative(RestClient restClient, @Nullable TransportOptions transportOptions,
|
||||
JsonpMapper jsonpMapper) {
|
||||
|
||||
@ -206,6 +247,27 @@ public final class ElasticsearchClients {
|
||||
return createImperative(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new imperative {@link ElasticsearchClient}
|
||||
*
|
||||
* @param rest5Client the Rest5Client to use
|
||||
* @param transportOptions options to be added to each request.
|
||||
* @param jsonpMapper the mapper for the transport to use
|
||||
* @return the {@link ElasticsearchClient}
|
||||
* @since 6.0
|
||||
*/
|
||||
public static ElasticsearchClient createImperative(Rest5Client rest5Client,
|
||||
@Nullable TransportOptions transportOptions,
|
||||
JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(rest5Client, "restClient must not be null");
|
||||
|
||||
ElasticsearchTransport transport = getElasticsearchTransport(rest5Client, IMPERATIVE_CLIENT, transportOptions,
|
||||
jsonpMapper);
|
||||
|
||||
return createImperative(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ElasticsearchClient} that uses the given {@link ElasticsearchTransport}.
|
||||
*
|
||||
@ -220,96 +282,6 @@ public final class ElasticsearchClients {
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region low level RestClient
|
||||
private static RestClientOptions.Builder getRestClientOptionsBuilder(@Nullable TransportOptions transportOptions) {
|
||||
|
||||
if (transportOptions instanceof RestClientOptions restClientOptions) {
|
||||
return restClientOptions.toBuilder();
|
||||
}
|
||||
|
||||
var builder = new RestClientOptions.Builder(RequestOptions.DEFAULT.toBuilder());
|
||||
|
||||
if (transportOptions != null) {
|
||||
transportOptions.headers().forEach(header -> builder.addHeader(header.getKey(), header.getValue()));
|
||||
transportOptions.queryParameters().forEach(builder::setParameter);
|
||||
builder.onWarnings(transportOptions.onWarnings());
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a low level {@link RestClient} for the given configuration.
|
||||
*
|
||||
* @param clientConfiguration must not be {@literal null}
|
||||
* @return the {@link RestClient}
|
||||
*/
|
||||
public static RestClient getRestClient(ClientConfiguration clientConfiguration) {
|
||||
return getRestClientBuilder(clientConfiguration).build();
|
||||
}
|
||||
|
||||
private static RestClientBuilder getRestClientBuilder(ClientConfiguration clientConfiguration) {
|
||||
HttpHost[] httpHosts = formattedHosts(clientConfiguration.getEndpoints(), clientConfiguration.useSsl()).stream()
|
||||
.map(HttpHost::create).toArray(HttpHost[]::new);
|
||||
RestClientBuilder builder = RestClient.builder(httpHosts);
|
||||
|
||||
if (clientConfiguration.getPathPrefix() != null) {
|
||||
builder.setPathPrefix(clientConfiguration.getPathPrefix());
|
||||
}
|
||||
|
||||
HttpHeaders headers = clientConfiguration.getDefaultHeaders();
|
||||
|
||||
if (!headers.isEmpty()) {
|
||||
builder.setDefaultHeaders(toHeaderArray(headers));
|
||||
}
|
||||
|
||||
builder.setHttpClientConfigCallback(clientBuilder -> {
|
||||
if (clientConfiguration.getCaFingerprint().isPresent()) {
|
||||
clientBuilder
|
||||
.setSSLContext(TransportUtils.sslContextFromCaFingerprint(clientConfiguration.getCaFingerprint().get()));
|
||||
}
|
||||
clientConfiguration.getSslContext().ifPresent(clientBuilder::setSSLContext);
|
||||
clientConfiguration.getHostNameVerifier().ifPresent(clientBuilder::setSSLHostnameVerifier);
|
||||
clientBuilder.addInterceptorLast(new CustomHeaderInjector(clientConfiguration.getHeadersSupplier()));
|
||||
|
||||
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
|
||||
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||
|
||||
if (!connectTimeout.isNegative()) {
|
||||
requestConfigBuilder.setConnectTimeout(Math.toIntExact(connectTimeout.toMillis()));
|
||||
}
|
||||
|
||||
Duration socketTimeout = clientConfiguration.getSocketTimeout();
|
||||
|
||||
if (!socketTimeout.isNegative()) {
|
||||
requestConfigBuilder.setSocketTimeout(Math.toIntExact(socketTimeout.toMillis()));
|
||||
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(socketTimeout.toMillis()));
|
||||
}
|
||||
|
||||
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
|
||||
|
||||
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
|
||||
|
||||
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
|
||||
.getClientConfigurers()) {
|
||||
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;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Elasticsearch transport
|
||||
/**
|
||||
* Creates an {@link ElasticsearchTransport} that will use the given client that additionally is customized with a
|
||||
@ -320,7 +292,9 @@ public final class ElasticsearchClients {
|
||||
* @param transportOptions options for the transport
|
||||
* @param jsonpMapper mapper for the transport
|
||||
* @return ElasticsearchTransport
|
||||
* @deprecated since 6.0, use the version taking a Rest5Client
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static ElasticsearchTransport getElasticsearchTransport(RestClient restClient, String clientType,
|
||||
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
|
||||
|
||||
@ -329,7 +303,7 @@ public final class ElasticsearchClients {
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
TransportOptions.Builder transportOptionsBuilder = transportOptions != null ? transportOptions.toBuilder()
|
||||
: new RestClientOptions(RequestOptions.DEFAULT, false).toBuilder();
|
||||
: new RestClientOptions(org.elasticsearch.client.RequestOptions.DEFAULT, false).toBuilder();
|
||||
|
||||
RestClientOptions.Builder restClientOptionsBuilder = getRestClientOptionsBuilder(transportOptions);
|
||||
|
||||
@ -353,70 +327,35 @@ public final class ElasticsearchClients {
|
||||
|
||||
return new RestClientTransport(restClient, jsonpMapper, restClientOptionsBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link ElasticsearchTransport} that will use the given client that additionally is customized with a
|
||||
* header to contain the clientType
|
||||
*
|
||||
* @param rest5Client the client to use
|
||||
* @param clientType the client type to pass in each request as header
|
||||
* @param transportOptions options for the transport
|
||||
* @param jsonpMapper mapper for the transport
|
||||
* @return ElasticsearchTransport
|
||||
*/
|
||||
public static ElasticsearchTransport getElasticsearchTransport(Rest5Client rest5Client, String clientType,
|
||||
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(rest5Client, "restClient must not be null");
|
||||
Assert.notNull(clientType, "clientType must not be null");
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
TransportOptions.Builder transportOptionsBuilder = transportOptions != null ? transportOptions.toBuilder()
|
||||
: new Rest5ClientOptions(RequestOptions.DEFAULT, false).toBuilder();
|
||||
|
||||
Rest5ClientOptions.Builder rest5ClientOptionsBuilder = getRest5ClientOptionsBuilder(transportOptions);
|
||||
|
||||
rest5ClientOptionsBuilder.addHeader(X_SPRING_DATA_ELASTICSEARCH_CLIENT,
|
||||
VersionInfo.clientVersions() + " / " + clientType);
|
||||
|
||||
return new Rest5ClientTransport(rest5Client, jsonpMapper, rest5ClientOptionsBuilder.build());
|
||||
}
|
||||
// endregion
|
||||
|
||||
private static List<String> formattedHosts(List<InetSocketAddress> hosts, boolean useSsl) {
|
||||
return hosts.stream().map(it -> (useSsl ? "https" : "http") + "://" + it.getHostString() + ':' + it.getPort())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static org.apache.http.Header[] toHeaderArray(HttpHeaders headers) {
|
||||
return headers.entrySet().stream() //
|
||||
.flatMap(entry -> entry.getValue().stream() //
|
||||
.map(value -> new BasicHeader(entry.getKey(), value))) //
|
||||
.toArray(org.apache.http.Header[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interceptor to inject custom supplied headers.
|
||||
*
|
||||
* @since 4.4
|
||||
*/
|
||||
private record CustomHeaderInjector(Supplier<HttpHeaders> headersSupplier) implements HttpRequestInterceptor {
|
||||
|
||||
@Override
|
||||
public void process(HttpRequest request, HttpContext context) {
|
||||
HttpHeaders httpHeaders = headersSupplier.get();
|
||||
|
||||
if (httpHeaders != null && !httpHeaders.isEmpty()) {
|
||||
Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||
* the Elasticsearch RestClient's Http client with a {@link HttpAsyncClientBuilder}
|
||||
*
|
||||
* @since 4.4
|
||||
*/
|
||||
public interface ElasticsearchHttpClientConfigurationCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
|
||||
|
||||
static ElasticsearchHttpClientConfigurationCallback from(
|
||||
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> httpClientBuilderCallback) {
|
||||
|
||||
Assert.notNull(httpClientBuilderCallback, "httpClientBuilderCallback must not be null");
|
||||
|
||||
return httpClientBuilderCallback::apply;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||
* the RestClient client with a {@link RestClientBuilder}
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface ElasticsearchRestClientConfigurationCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<RestClientBuilder> {
|
||||
|
||||
static ElasticsearchRestClientConfigurationCallback from(
|
||||
Function<RestClientBuilder, RestClientBuilder> restClientBuilderCallback) {
|
||||
|
||||
Assert.notNull(restClientBuilderCallback, "restClientBuilderCallback must not be null");
|
||||
|
||||
return restClientBuilderCallback::apply;
|
||||
}
|
||||
}
|
||||
// todo #3117 remove and document that ElasticsearchHttpClientConfigurationCallback has been move to RestClients.
|
||||
}
|
||||
|
@ -20,12 +20,13 @@ import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||
import co.elastic.clients.transport.rest5_client.Rest5ClientOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.RequestOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
|
||||
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest5_client.Rest5Clients;
|
||||
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
@ -38,7 +39,9 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
/**
|
||||
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
|
||||
* connection using the Elasticsearch Client. This class exposes different parts of the setup as Spring beans. Deriving
|
||||
* classes must provide the {@link ClientConfiguration} to use.
|
||||
* classes must provide the {@link ClientConfiguration} to use. From Version 6.0 on, this class uses the new Rest5Client
|
||||
* from Elasticsearch 9. The old implementation using the RestClient is still available under the name
|
||||
* {@link ElasticsearchLegacyRestClientConfiguration}.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
@ -60,27 +63,27 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
|
||||
* @return RestClient
|
||||
*/
|
||||
@Bean
|
||||
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
|
||||
public Rest5Client elasticsearchRest5Client(ClientConfiguration clientConfiguration) {
|
||||
|
||||
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
||||
|
||||
return ElasticsearchClients.getRestClient(clientConfiguration);
|
||||
return Rest5Clients.getRest5Client(clientConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the Elasticsearch transport to be used. The default implementation uses the {@link RestClient} bean and
|
||||
* Provides the Elasticsearch transport to be used. The default implementation uses the {@link Rest5Client} bean and
|
||||
* the {@link JsonpMapper} bean provided in this class.
|
||||
*
|
||||
* @return the {@link ElasticsearchTransport}
|
||||
* @since 5.2
|
||||
*/
|
||||
@Bean
|
||||
public ElasticsearchTransport elasticsearchTransport(RestClient restClient, JsonpMapper jsonpMapper) {
|
||||
public ElasticsearchTransport elasticsearchTransport(Rest5Client rest5Client, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(restClient, "restClient must not be null");
|
||||
Assert.notNull(rest5Client, "restClient must not be null");
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
return ElasticsearchClients.getElasticsearchTransport(restClient, ElasticsearchClients.IMPERATIVE_CLIENT,
|
||||
return ElasticsearchClients.getElasticsearchTransport(rest5Client, ElasticsearchClients.IMPERATIVE_CLIENT,
|
||||
transportOptions(), jsonpMapper);
|
||||
}
|
||||
|
||||
@ -115,7 +118,7 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the JsonpMapper bean that is used in the {@link #elasticsearchTransport(RestClient, JsonpMapper)} method.
|
||||
* Provides the JsonpMapper bean that is used in the {@link #elasticsearchTransport(Rest5Client, JsonpMapper)} method.
|
||||
*
|
||||
* @return the {@link JsonpMapper} to use
|
||||
* @since 5.2
|
||||
@ -135,6 +138,6 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
|
||||
* @return the options that should be added to every request. Must not be {@literal null}
|
||||
*/
|
||||
public TransportOptions transportOptions() {
|
||||
return new RestClientOptions(RequestOptions.DEFAULT, false);
|
||||
return new Rest5ClientOptions(RequestOptions.DEFAULT, false);
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.elasticsearch.client.ResponseException;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
@ -33,6 +34,7 @@ import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||
import org.springframework.data.elasticsearch.ResourceNotFoundException;
|
||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.VersionConflictException;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Simple {@link PersistenceExceptionTranslator} for Elasticsearch. Convert the given runtime exception to an
|
||||
@ -45,6 +47,9 @@ import org.springframework.data.elasticsearch.VersionConflictException;
|
||||
*/
|
||||
public class ElasticsearchExceptionTranslator implements PersistenceExceptionTranslator {
|
||||
|
||||
public static final boolean LEGACY_RESTCLIENT_PRESENT = ClassUtils
|
||||
.isPresent("org.elasticsearch.client.ResponseException", ElasticsearchExceptionTranslator.class.getClassLoader());
|
||||
|
||||
private final JsonpMapper jsonpMapper;
|
||||
|
||||
public ElasticsearchExceptionTranslator(JsonpMapper jsonpMapper) {
|
||||
@ -68,7 +73,7 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
|
||||
public @Nullable DataAccessException translateExceptionIfPossible(RuntimeException ex) {
|
||||
|
||||
checkForConflictException(ex);
|
||||
|
||||
@ -118,15 +123,20 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
||||
Integer status = null;
|
||||
String message = null;
|
||||
|
||||
if (exception instanceof ResponseException responseException) {
|
||||
if (LEGACY_RESTCLIENT_PRESENT && exception instanceof ResponseException responseException) {
|
||||
// this code is for the old RestClient
|
||||
status = responseException.getResponse().getStatusLine().getStatusCode();
|
||||
message = responseException.getMessage();
|
||||
} else if (exception instanceof ElasticsearchException elasticsearchException) {
|
||||
// using the RestClient throws this
|
||||
status = elasticsearchException.status();
|
||||
message = elasticsearchException.getMessage();
|
||||
} else if (exception.getCause() != null) {
|
||||
checkForConflictException(exception.getCause());
|
||||
}
|
||||
|
||||
if (status != null && message != null) {
|
||||
if (status == 409 && message.contains("type\":\"version_conflict_engine_exception"))
|
||||
if (status == 409 && message.contains("version_conflict_engine_exception"))
|
||||
if (message.contains("version conflict, required seqNo")) {
|
||||
throw new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict",
|
||||
exception);
|
||||
|
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2021-2025 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 co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest_client.RestClients;
|
||||
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
|
||||
* connection using the Elasticsearch Client. This class exposes different parts of the setup as Spring beans. Deriving
|
||||
* classes must provide the {@link ClientConfiguration} to use. <br/>
|
||||
* This class uses the Elasticsearch RestClient which was replaced by the Rest5Client in Elasticsearch 9. It is still
|
||||
* available here but deprecated.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
* @deprecated since 6.0, use {@link ElasticsearchConfiguration}
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval=true)
|
||||
public abstract class ElasticsearchLegacyRestClientConfiguration extends ElasticsearchConfigurationSupport {
|
||||
|
||||
/**
|
||||
* Must be implemented by deriving classes to provide the {@link ClientConfiguration}.
|
||||
*
|
||||
* @return configuration, must not be {@literal null}
|
||||
*/
|
||||
@Bean(name = "elasticsearchClientConfiguration")
|
||||
public abstract ClientConfiguration clientConfiguration();
|
||||
|
||||
/**
|
||||
* Provides the underlying low level Elasticsearch RestClient.
|
||||
*
|
||||
* @param clientConfiguration configuration for the client, must not be {@literal null}
|
||||
* @return RestClient
|
||||
*/
|
||||
@Bean
|
||||
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
|
||||
|
||||
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
||||
|
||||
return RestClients.getRestClient(clientConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the Elasticsearch transport to be used. The default implementation uses the {@link RestClient} bean and
|
||||
* the {@link JsonpMapper} bean provided in this class.
|
||||
*
|
||||
* @return the {@link ElasticsearchTransport}
|
||||
* @since 5.2
|
||||
*/
|
||||
@Bean
|
||||
public ElasticsearchTransport elasticsearchTransport(RestClient restClient, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(restClient, "restClient must not be null");
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
return ElasticsearchClients.getElasticsearchTransport(restClient, ElasticsearchClients.IMPERATIVE_CLIENT,
|
||||
transportOptions(), jsonpMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the {@link ElasticsearchClient} to be used.
|
||||
*
|
||||
* @param transport the {@link ElasticsearchTransport} to use
|
||||
* @return ElasticsearchClient instance
|
||||
*/
|
||||
@Bean
|
||||
public ElasticsearchClient elasticsearchClient(ElasticsearchTransport transport) {
|
||||
|
||||
Assert.notNull(transport, "transport must not be null");
|
||||
|
||||
return ElasticsearchClients.createImperative(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ElasticsearchOperations} implementation using an {@link ElasticsearchClient}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
|
||||
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
|
||||
ElasticsearchClient elasticsearchClient) {
|
||||
|
||||
ElasticsearchTemplate template = new ElasticsearchTemplate(elasticsearchClient, elasticsearchConverter);
|
||||
template.setRefreshPolicy(refreshPolicy());
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the JsonpMapper bean that is used in the {@link #elasticsearchTransport(RestClient, JsonpMapper)} method.
|
||||
*
|
||||
* @return the {@link JsonpMapper} to use
|
||||
* @since 5.2
|
||||
*/
|
||||
@Bean
|
||||
public JsonpMapper jsonpMapper() {
|
||||
// we need to create our own objectMapper that keeps null values in order to provide the storeNullValue
|
||||
// functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get
|
||||
// into this mapper, so we can safely keep them here.
|
||||
var objectMapper = (new ObjectMapper())
|
||||
.configure(SerializationFeature.INDENT_OUTPUT, false)
|
||||
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
|
||||
return new JacksonJsonpMapper(objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the options that should be added to every request. Must not be {@literal null}
|
||||
*/
|
||||
public TransportOptions transportOptions() {
|
||||
return new RestClientOptions(RequestOptions.DEFAULT, false);
|
||||
}
|
||||
}
|
@ -658,7 +658,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
QueryResponse response = sqlClient.query(requestConverter.sqlQueryRequest(query));
|
||||
|
||||
return responseConverter.sqlResponse(response);
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
throw exceptionTranslator.translateException(e);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
|
||||
|
||||
import co.elastic.clients.util.NamedValue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -60,10 +62,11 @@ class HighlightQueryBuilder {
|
||||
|
||||
for (HighlightField highlightField : highlight.getFields()) {
|
||||
String mappedName = mapFieldName(highlightField.getName(), type);
|
||||
highlightBuilder.fields(mappedName, hf -> {
|
||||
addParameters(highlightField.getParameters(), hf, type);
|
||||
return hf;
|
||||
});
|
||||
highlightBuilder.fields(
|
||||
NamedValue.of(mappedName, co.elastic.clients.elasticsearch.core.search.HighlightField.of(hf -> {
|
||||
addParameters(highlightField.getParameters(), hf, type);
|
||||
return hf;
|
||||
})));
|
||||
}
|
||||
|
||||
return highlightBuilder.build();
|
||||
|
@ -30,6 +30,7 @@ import java.util.Set;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.elasticsearch.ResourceNotFoundException;
|
||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||
import org.springframework.data.elasticsearch.core.IndexInformation;
|
||||
@ -315,15 +316,20 @@ public class IndicesTemplate extends ChildTemplate<ElasticsearchTransport, Elast
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateData getTemplate(GetTemplateRequest getTemplateRequest) {
|
||||
public @Nullable TemplateData getTemplate(GetTemplateRequest getTemplateRequest) {
|
||||
|
||||
Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null");
|
||||
|
||||
co.elastic.clients.elasticsearch.indices.GetTemplateRequest getTemplateRequestES = requestConverter
|
||||
.indicesGetTemplateRequest(getTemplateRequest);
|
||||
GetTemplateResponse getTemplateResponse = execute(client -> client.getTemplate(getTemplateRequestES));
|
||||
|
||||
return responseConverter.indicesGetTemplateData(getTemplateResponse, getTemplateRequest.getTemplateName());
|
||||
try {
|
||||
GetTemplateResponse getTemplateResponse = execute(client -> client.getTemplate(getTemplateRequestES));
|
||||
return responseConverter.indicesGetTemplateData(getTemplateResponse, getTemplateRequest.getTemplateName());
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// since Elasticsearch 9.1.0 we get an exception (404) instead of an empty result
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -19,12 +19,18 @@ import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.rest5_client.Rest5ClientOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.RequestOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
|
||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest5_client.Rest5Clients;
|
||||
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
@ -55,11 +61,11 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
|
||||
* @return RestClient
|
||||
*/
|
||||
@Bean
|
||||
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
|
||||
public Rest5Client elasticsearchRestClient(ClientConfiguration clientConfiguration) {
|
||||
|
||||
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
||||
|
||||
return ElasticsearchClients.getRestClient(clientConfiguration);
|
||||
return Rest5Clients.getRest5Client(clientConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,12 +76,12 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
|
||||
* @since 5.2
|
||||
*/
|
||||
@Bean
|
||||
public ElasticsearchTransport elasticsearchTransport(RestClient restClient, JsonpMapper jsonpMapper) {
|
||||
public ElasticsearchTransport elasticsearchTransport(Rest5Client rest5Client, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(restClient, "restClient must not be null");
|
||||
Assert.notNull(rest5Client, "restClient must not be null");
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
return ElasticsearchClients.getElasticsearchTransport(restClient, ElasticsearchClients.REACTIVE_CLIENT,
|
||||
return ElasticsearchClients.getElasticsearchTransport(rest5Client, ElasticsearchClients.REACTIVE_CLIENT,
|
||||
transportOptions(), jsonpMapper);
|
||||
}
|
||||
|
||||
@ -110,7 +116,7 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the JsonpMapper that is used in the {@link #elasticsearchTransport(RestClient, JsonpMapper)} method and
|
||||
* Provides the JsonpMapper that is used in the {@link #elasticsearchTransport(Rest5Client, JsonpMapper)} method and
|
||||
* exposes it as a bean.
|
||||
*
|
||||
* @return the {@link JsonpMapper} to use
|
||||
@ -118,13 +124,19 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
|
||||
*/
|
||||
@Bean
|
||||
public JsonpMapper jsonpMapper() {
|
||||
return new JacksonJsonpMapper();
|
||||
// we need to create our own objectMapper that keeps null values in order to provide the storeNullValue
|
||||
// functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get
|
||||
// into this mapper, so we can safely keep them here.
|
||||
var objectMapper = (new ObjectMapper())
|
||||
.configure(SerializationFeature.INDENT_OUTPUT, false)
|
||||
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
|
||||
return new JacksonJsonpMapper(objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the options that should be added to every request. Must not be {@literal null}
|
||||
*/
|
||||
public TransportOptions transportOptions() {
|
||||
return new RestClientOptions(RequestOptions.DEFAULT, false).toBuilder().build();
|
||||
return new Rest5ClientOptions(RequestOptions.DEFAULT, false);
|
||||
}
|
||||
}
|
||||
|
@ -539,14 +539,6 @@ public class ReactiveElasticsearchIndicesClient
|
||||
return stats(builder -> builder);
|
||||
}
|
||||
|
||||
public Mono<UnfreezeResponse> unfreeze(UnfreezeRequest request) {
|
||||
return Mono.fromFuture(transport.performRequestAsync(request, UnfreezeRequest._ENDPOINT, transportOptions));
|
||||
}
|
||||
|
||||
public Mono<UnfreezeResponse> unfreeze(Function<UnfreezeRequest.Builder, ObjectBuilder<UnfreezeRequest>> fn) {
|
||||
return unfreeze(fn.apply(new UnfreezeRequest.Builder()).build());
|
||||
}
|
||||
|
||||
public Mono<UpdateAliasesResponse> updateAliases(UpdateAliasesRequest request) {
|
||||
return Mono.fromFuture(transport.performRequestAsync(request, UpdateAliasesRequest._ENDPOINT, transportOptions));
|
||||
}
|
||||
|
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2021-2025 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.json.JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest_client.RestClients;
|
||||
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
|
||||
* connection using the {@link ReactiveElasticsearchClient}. This class exposes different parts of the setup as Spring
|
||||
* beans. Deriving * classes must provide the {@link ClientConfiguration} to use. <br/>
|
||||
* This class uses the Elasticsearch RestClient which was replaced b y the Rest5Client in Elasticsearch 9. It is still
|
||||
* available here but deprecated. *
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
* @deprecated since 6.0 use {@link ReactiveElasticsearchConfiguration}
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval=true)
|
||||
public abstract class ReactiveElasticsearchLegacyRestClientConfiguration extends ElasticsearchConfigurationSupport {
|
||||
|
||||
/**
|
||||
* Must be implemented by deriving classes to provide the {@link ClientConfiguration}.
|
||||
*
|
||||
* @return configuration, must not be {@literal null}
|
||||
*/
|
||||
@Bean(name = "elasticsearchClientConfiguration")
|
||||
public abstract ClientConfiguration clientConfiguration();
|
||||
|
||||
/**
|
||||
* Provides the underlying low level RestClient.
|
||||
*
|
||||
* @param clientConfiguration configuration for the client, must not be {@literal null}
|
||||
* @return RestClient
|
||||
*/
|
||||
@Bean
|
||||
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
|
||||
|
||||
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
||||
|
||||
return RestClients.getRestClient(clientConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the Elasticsearch transport to be used. The default implementation uses the {@link RestClient} bean and
|
||||
* the {@link JsonpMapper} bean provided in this class.
|
||||
*
|
||||
* @return the {@link ElasticsearchTransport}
|
||||
* @since 5.2
|
||||
*/
|
||||
@Bean
|
||||
public ElasticsearchTransport elasticsearchTransport(RestClient restClient, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(restClient, "restClient must not be null");
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
return ElasticsearchClients.getElasticsearchTransport(restClient, ElasticsearchClients.REACTIVE_CLIENT,
|
||||
transportOptions(), jsonpMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the {@link ReactiveElasticsearchClient} instance used.
|
||||
*
|
||||
* @param transport the ElasticsearchTransport to use
|
||||
* @return ReactiveElasticsearchClient instance.
|
||||
*/
|
||||
@Bean
|
||||
public ReactiveElasticsearchClient reactiveElasticsearchClient(ElasticsearchTransport transport) {
|
||||
|
||||
Assert.notNull(transport, "transport must not be null");
|
||||
|
||||
return ElasticsearchClients.createReactive(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link ReactiveElasticsearchOperations}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean(name = { "reactiveElasticsearchOperations", "reactiveElasticsearchTemplate" })
|
||||
public ReactiveElasticsearchOperations reactiveElasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
|
||||
ReactiveElasticsearchClient reactiveElasticsearchClient) {
|
||||
|
||||
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient,
|
||||
elasticsearchConverter);
|
||||
template.setRefreshPolicy(refreshPolicy());
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the JsonpMapper that is used in the {@link #elasticsearchTransport(RestClient, JsonpMapper)} method and
|
||||
* exposes it as a bean.
|
||||
*
|
||||
* @return the {@link JsonpMapper} to use
|
||||
* @since 5.2
|
||||
*/
|
||||
@Bean
|
||||
public JsonpMapper jsonpMapper() {
|
||||
// we need to create our own objectMapper that keeps null values in order to provide the storeNullValue
|
||||
// functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get
|
||||
// into this mapper, so we can safely keep them here.
|
||||
var objectMapper = (new ObjectMapper())
|
||||
.configure(SerializationFeature.INDENT_OUTPUT, false)
|
||||
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
|
||||
return new JacksonJsonpMapper(objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the options that should be added to every request. Must not be {@literal null}
|
||||
*/
|
||||
public TransportOptions transportOptions() {
|
||||
return new RestClientOptions(RequestOptions.DEFAULT, false).toBuilder().build();
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import co.elastic.clients.elasticsearch.core.search.ResponseBody;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.transport.Version;
|
||||
import co.elastic.clients.transport.endpoints.BooleanResponse;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
@ -162,7 +163,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
ExistsRequest existsRequest = requestConverter.documentExistsRequest(id, routingResolver.getRouting(), index);
|
||||
|
||||
return Mono.from(execute(
|
||||
((ClientCallback<Publisher<BooleanResponse>>) client -> client.exists(existsRequest))))
|
||||
((ClientCallback<@NonNull Publisher<BooleanResponse>>) client -> client.exists(existsRequest))))
|
||||
.map(BooleanResponse::value) //
|
||||
.onErrorReturn(NoSuchIndexException.class, false);
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ 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 org.springframework.data.elasticsearch.ResourceNotFoundException;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@ -389,7 +390,8 @@ public class ReactiveIndicesTemplate
|
||||
co.elastic.clients.elasticsearch.indices.GetTemplateRequest getTemplateRequestES = requestConverter
|
||||
.indicesGetTemplateRequest(getTemplateRequest);
|
||||
Mono<GetTemplateResponse> getTemplateResponse = Mono
|
||||
.from(execute(client -> client.getTemplate(getTemplateRequestES)));
|
||||
.from(execute(client -> client.getTemplate(getTemplateRequestES)))
|
||||
.onErrorComplete(ResourceNotFoundException.class);
|
||||
|
||||
return getTemplateResponse.flatMap(response -> {
|
||||
if (response != null) {
|
||||
|
@ -42,10 +42,10 @@ import co.elastic.clients.elasticsearch.core.bulk.CreateOperation;
|
||||
import co.elastic.clients.elasticsearch.core.bulk.IndexOperation;
|
||||
import co.elastic.clients.elasticsearch.core.bulk.UpdateOperation;
|
||||
import co.elastic.clients.elasticsearch.core.mget.MultiGetOperation;
|
||||
import co.elastic.clients.elasticsearch.core.msearch.MultisearchBody;
|
||||
import co.elastic.clients.elasticsearch.core.msearch.MultisearchHeader;
|
||||
import co.elastic.clients.elasticsearch.core.search.Highlight;
|
||||
import co.elastic.clients.elasticsearch.core.search.Rescore;
|
||||
import co.elastic.clients.elasticsearch.core.search.SearchRequestBody;
|
||||
import co.elastic.clients.elasticsearch.core.search.SourceConfig;
|
||||
import co.elastic.clients.elasticsearch.indices.*;
|
||||
import co.elastic.clients.elasticsearch.indices.ExistsIndexTemplateRequest;
|
||||
@ -55,6 +55,7 @@ import co.elastic.clients.elasticsearch.sql.query.SqlFormat;
|
||||
import co.elastic.clients.json.JsonData;
|
||||
import co.elastic.clients.json.JsonpDeserializer;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.util.NamedValue;
|
||||
import co.elastic.clients.util.ObjectBuilder;
|
||||
import jakarta.json.stream.JsonParser;
|
||||
|
||||
@ -72,6 +73,7 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@ -118,9 +120,6 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(RequestConverter.class);
|
||||
|
||||
// the default max result window size of Elasticsearch
|
||||
public static final Integer INDEX_MAX_RESULT_WINDOW = 10_000;
|
||||
|
||||
protected final JsonpMapper jsonpMapper;
|
||||
protected final ElasticsearchConverter elasticsearchConverter;
|
||||
|
||||
@ -724,12 +723,11 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
return a;
|
||||
});
|
||||
|
||||
uob //
|
||||
.routing(query.getRouting()) //
|
||||
.ifSeqNo(query.getIfSeqNo() != null ? Long.valueOf(query.getIfSeqNo()) : null) //
|
||||
.ifPrimaryTerm(query.getIfPrimaryTerm() != null ? Long.valueOf(query.getIfPrimaryTerm()) : null) //
|
||||
.retryOnConflict(query.getRetryOnConflict()) //
|
||||
;
|
||||
uob
|
||||
.routing(query.getRouting())
|
||||
.ifSeqNo(query.getIfSeqNo())
|
||||
.ifPrimaryTerm(query.getIfPrimaryTerm())
|
||||
.retryOnConflict(query.getRetryOnConflict());
|
||||
|
||||
// no refresh, timeout, waitForActiveShards on UpdateOperation or UpdateAction
|
||||
|
||||
@ -749,11 +747,10 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
}
|
||||
return co.elastic.clients.elasticsearch._types.Script.of(sb -> {
|
||||
sb.lang(scriptData.language())
|
||||
.params(params);
|
||||
if (scriptData.type() == ScriptType.INLINE) {
|
||||
sb.source(scriptData.script());
|
||||
} else if (scriptData.type() == ScriptType.STORED) {
|
||||
sb.id(scriptData.script());
|
||||
.params(params)
|
||||
.id(scriptData.scriptName());
|
||||
if (scriptData.script() != null) {
|
||||
sb.source(s -> s.scriptString(scriptData.script()));
|
||||
}
|
||||
return sb;
|
||||
});
|
||||
@ -925,9 +922,13 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
|
||||
ReindexRequest.Script script = reindexRequest.getScript();
|
||||
if (script != null) {
|
||||
builder.script(sb -> sb
|
||||
.lang(script.getLang())
|
||||
.source(script.getSource()));
|
||||
builder.script(sb -> {
|
||||
if (script.getSource() != null) {
|
||||
sb.source(s -> s.scriptString(script.getSource()));
|
||||
}
|
||||
sb.lang(script.getLang());
|
||||
return sb;
|
||||
});
|
||||
}
|
||||
|
||||
builder.timeout(time(reindexRequest.getTimeout())) //
|
||||
@ -1084,27 +1085,25 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
|
||||
uqb.script(sb -> {
|
||||
sb.lang(query.getLang()).params(params);
|
||||
|
||||
if (query.getScriptType() == ScriptType.INLINE) {
|
||||
sb.source(query.getScript()); //
|
||||
} else if (query.getScriptType() == ScriptType.STORED) {
|
||||
sb.id(query.getScript());
|
||||
if (query.getScript() != null) {
|
||||
sb.source(s -> s.scriptString(query.getScript()));
|
||||
}
|
||||
sb.id(query.getId());
|
||||
|
||||
return sb;
|
||||
});
|
||||
}
|
||||
|
||||
uqb //
|
||||
.doc(query.getDocument()) //
|
||||
.upsert(query.getUpsert()) //
|
||||
.routing(query.getRouting() != null ? query.getRouting() : routing) //
|
||||
.scriptedUpsert(query.getScriptedUpsert()) //
|
||||
.docAsUpsert(query.getDocAsUpsert()) //
|
||||
.ifSeqNo(query.getIfSeqNo() != null ? Long.valueOf(query.getIfSeqNo()) : null) //
|
||||
.ifPrimaryTerm(query.getIfPrimaryTerm() != null ? Long.valueOf(query.getIfPrimaryTerm()) : null) //
|
||||
.refresh(query.getRefreshPolicy() != null ? refresh(query.getRefreshPolicy()) : refresh(refreshPolicy)) //
|
||||
.retryOnConflict(query.getRetryOnConflict()) //
|
||||
;
|
||||
uqb
|
||||
.doc(query.getDocument())
|
||||
.upsert(query.getUpsert())
|
||||
.routing(query.getRouting() != null ? query.getRouting() : routing)
|
||||
.scriptedUpsert(query.getScriptedUpsert())
|
||||
.docAsUpsert(query.getDocAsUpsert())
|
||||
.ifSeqNo(query.getIfSeqNo())
|
||||
.ifPrimaryTerm(query.getIfPrimaryTerm())
|
||||
.refresh(query.getRefreshPolicy() != null ? refresh(query.getRefreshPolicy()) : refresh(refreshPolicy))
|
||||
.retryOnConflict(query.getRetryOnConflict());
|
||||
|
||||
if (query.getFetchSource() != null) {
|
||||
uqb.source(sc -> sc.fetch(query.getFetchSource()));
|
||||
@ -1252,11 +1251,11 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
mtrb.searchTemplates(stb -> stb
|
||||
.header(msearchHeaderBuilder(query, param.index(), routing))
|
||||
.body(bb -> {
|
||||
bb //
|
||||
.explain(query.getExplain()) //
|
||||
.id(query.getId()) //
|
||||
.source(query.getSource()) //
|
||||
;
|
||||
bb.explain(query.getExplain()) //
|
||||
.id(query.getId()); //
|
||||
if (query.getSource() != null) {
|
||||
bb.source(s -> s.scriptString(query.getSource()));
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(query.getParams())) {
|
||||
Map<String, JsonData> params = getTemplateParams(query.getParams().entrySet());
|
||||
@ -1292,11 +1291,8 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
.timeout(timeStringMs(query.getTimeout())) //
|
||||
;
|
||||
|
||||
if (query.getPageable().isPaged()) {
|
||||
bb //
|
||||
.from((int) query.getPageable().getOffset()) //
|
||||
.size(query.getPageable().getPageSize());
|
||||
}
|
||||
bb.from((int) (query.getPageable().isPaged() ? query.getPageable().getOffset() : 0))
|
||||
.size(query.getRequestSize());
|
||||
|
||||
if (!isEmpty(query.getFields())) {
|
||||
bb.fields(fb -> {
|
||||
@ -1309,10 +1305,6 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
bb.storedFields(query.getStoredFields());
|
||||
}
|
||||
|
||||
if (query.isLimiting()) {
|
||||
bb.size(query.getMaxResults());
|
||||
}
|
||||
|
||||
if (query.getMinScore() > 0) {
|
||||
bb.minScore((double) query.getMinScore());
|
||||
}
|
||||
@ -1347,7 +1339,9 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
|
||||
if (script != null) {
|
||||
rfb.script(s -> {
|
||||
s.source(script);
|
||||
if (script != null) {
|
||||
s.source(so -> so.scriptString(script));
|
||||
}
|
||||
|
||||
if (runtimeField.getParams() != null) {
|
||||
s.params(TypeUtils.paramsMap(runtimeField.getParams()));
|
||||
@ -1364,9 +1358,14 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getIndicesBoost())) {
|
||||
bb.indicesBoost(query.getIndicesBoost().stream()
|
||||
.map(indexBoost -> Map.of(indexBoost.getIndexName(), (double) indexBoost.getBoost()))
|
||||
.collect(Collectors.toList()));
|
||||
Stream<NamedValue<Double>> namedValueStream = query.getIndicesBoost().stream()
|
||||
.map(indexBoost -> {
|
||||
var namedValue = new NamedValue(indexBoost.getIndexName(),
|
||||
Float.valueOf(indexBoost.getBoost()).doubleValue());
|
||||
return namedValue;
|
||||
});
|
||||
List<NamedValue<Double>> namedValueList = namedValueStream.collect(Collectors.toList());
|
||||
bb.indicesBoost(namedValueList);
|
||||
}
|
||||
|
||||
query.getScriptedFields().forEach(scriptedField -> bb.scriptFields(scriptedField.getFieldName(),
|
||||
@ -1465,13 +1464,8 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
builder.seqNoPrimaryTerm(true);
|
||||
}
|
||||
|
||||
if (query.getPageable().isPaged()) {
|
||||
builder //
|
||||
.from((int) query.getPageable().getOffset()) //
|
||||
.size(query.getPageable().getPageSize());
|
||||
} else {
|
||||
builder.from(0).size(INDEX_MAX_RESULT_WINDOW);
|
||||
}
|
||||
builder.from((int) (query.getPageable().isPaged() ? query.getPageable().getOffset() : 0))
|
||||
.size(query.getRequestSize());
|
||||
|
||||
if (!isEmpty(query.getFields())) {
|
||||
var fieldAndFormats = query.getFields().stream().map(field -> FieldAndFormat.of(b -> b.field(field))).toList();
|
||||
@ -1486,10 +1480,6 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
addIndicesOptions(builder, query.getIndicesOptions());
|
||||
}
|
||||
|
||||
if (query.isLimiting()) {
|
||||
builder.size(query.getMaxResults());
|
||||
}
|
||||
|
||||
if (query.getMinScore() > 0) {
|
||||
builder.minScore((double) query.getMinScore());
|
||||
}
|
||||
@ -1547,7 +1537,9 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
String script = runtimeField.getScript();
|
||||
if (script != null) {
|
||||
rfb.script(s -> {
|
||||
s.source(script);
|
||||
if (script != null) {
|
||||
s.source(so -> so.scriptString(script));
|
||||
}
|
||||
|
||||
if (runtimeField.getParams() != null) {
|
||||
s.params(TypeUtils.paramsMap(runtimeField.getParams()));
|
||||
@ -1575,9 +1567,14 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getIndicesBoost())) {
|
||||
builder.indicesBoost(query.getIndicesBoost().stream()
|
||||
.map(indexBoost -> Map.of(indexBoost.getIndexName(), (double) indexBoost.getBoost()))
|
||||
.collect(Collectors.toList()));
|
||||
Stream<NamedValue<Double>> namedValueStream = query.getIndicesBoost().stream()
|
||||
.map(indexBoost -> {
|
||||
var namedValue = new NamedValue(indexBoost.getIndexName(),
|
||||
Float.valueOf(indexBoost.getBoost()).doubleValue());
|
||||
return namedValue;
|
||||
});
|
||||
List<NamedValue<Double>> namedValueList = namedValueStream.collect(Collectors.toList());
|
||||
builder.indicesBoost(namedValueList);
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getDocValueFields())) {
|
||||
@ -1639,7 +1636,7 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
builder.highlight(highlight);
|
||||
}
|
||||
|
||||
private void addHighlight(Query query, MultisearchBody.Builder builder) {
|
||||
private void addHighlight(Query query, SearchRequestBody.Builder builder) {
|
||||
|
||||
Highlight highlight = query.getHighlightQuery()
|
||||
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext(), this)
|
||||
@ -1763,7 +1760,7 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private void prepareNativeSearch(NativeQuery query, MultisearchBody.Builder builder) {
|
||||
private void prepareNativeSearch(NativeQuery query, SearchRequestBody.Builder builder) {
|
||||
|
||||
builder //
|
||||
.suggest(query.getSuggester()) //
|
||||
@ -1882,10 +1879,11 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
.id(query.getId()) //
|
||||
.index(Arrays.asList(index.getIndexNames())) //
|
||||
.preference(query.getPreference()) //
|
||||
.searchType(searchType(query.getSearchType())) //
|
||||
.source(query.getSource()) //
|
||||
;
|
||||
.searchType(searchType(query.getSearchType())); //
|
||||
|
||||
if (query.getSource() != null) {
|
||||
builder.source(so -> so.scriptString(query.getSource()));
|
||||
}
|
||||
if (query.getRoute() != null) {
|
||||
builder.routing(query.getRoute());
|
||||
} else if (StringUtils.hasText(routing)) {
|
||||
@ -1928,7 +1926,7 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
.id(script.id()) //
|
||||
.script(sb -> sb //
|
||||
.lang(script.language()) //
|
||||
.source(script.source())));
|
||||
.source(s -> s.scriptString(script.source()))));
|
||||
}
|
||||
|
||||
public GetScriptRequest scriptGet(String name) {
|
||||
|
@ -92,7 +92,7 @@ class ResponseConverter {
|
||||
return ClusterHealth.builder() //
|
||||
.withActivePrimaryShards(healthResponse.activePrimaryShards()) //
|
||||
.withActiveShards(healthResponse.activeShards()) //
|
||||
.withActiveShardsPercent(Double.parseDouble(healthResponse.activeShardsPercentAsNumber()))//
|
||||
.withActiveShardsPercent(healthResponse.activeShardsPercentAsNumber())//
|
||||
.withClusterName(healthResponse.clusterName()) //
|
||||
.withDelayedUnassignedShards(healthResponse.delayedUnassignedShards()) //
|
||||
.withInitializingShards(healthResponse.initializingShards()) //
|
||||
@ -191,7 +191,7 @@ class ResponseConverter {
|
||||
Assert.notNull(getMappingResponse, "getMappingResponse must not be null");
|
||||
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
|
||||
|
||||
Map<String, IndexMappingRecord> mappings = getMappingResponse.result();
|
||||
Map<String, IndexMappingRecord> mappings = getMappingResponse.mappings();
|
||||
|
||||
if (mappings == null || mappings.isEmpty()) {
|
||||
return Document.create();
|
||||
@ -219,7 +219,7 @@ class ResponseConverter {
|
||||
|
||||
List<IndexInformation> indexInformationList = new ArrayList<>();
|
||||
|
||||
getIndexResponse.result().forEach((indexName, indexState) -> {
|
||||
getIndexResponse.indices().forEach((indexName, indexState) -> {
|
||||
Settings settings = indexState.settings() != null ? Settings.parse(toJson(indexState.settings(), jsonpMapper))
|
||||
: new Settings();
|
||||
Document mappings = indexState.mappings() != null ? Document.parse(toJson(indexState.mappings(), jsonpMapper))
|
||||
@ -239,7 +239,7 @@ class ResponseConverter {
|
||||
Assert.notNull(getAliasResponse, "getAliasResponse must not be null");
|
||||
|
||||
Map<String, Set<AliasData>> aliasDataMap = new HashMap<>();
|
||||
getAliasResponse.result().forEach((indexName, alias) -> {
|
||||
getAliasResponse.aliases().forEach((indexName, alias) -> {
|
||||
Set<AliasData> aliasDataSet = new HashSet<>();
|
||||
alias.aliases()
|
||||
.forEach((aliasName, aliasDefinition) -> aliasDataSet.add(indicesGetAliasData(aliasName, aliasDefinition)));
|
||||
@ -497,6 +497,10 @@ class ResponseConverter {
|
||||
builder.withDeleted(response.deleted());
|
||||
}
|
||||
|
||||
if(response.updated() != null) {
|
||||
builder.withUpdated(response.updated());
|
||||
}
|
||||
|
||||
if (response.batches() != null) {
|
||||
builder.withBatches(Math.toIntExact(response.batches()));
|
||||
}
|
||||
@ -531,7 +535,7 @@ class ResponseConverter {
|
||||
? Script.builder() //
|
||||
.withId(response.id()) //
|
||||
.withLanguage(response.script().lang()) //
|
||||
.withSource(response.script().source()).build() //
|
||||
.withSource(response.script().source().scriptString()).build() //
|
||||
: null;
|
||||
}
|
||||
// endregion
|
||||
|
@ -0,0 +1,308 @@
|
||||
package org.springframework.data.elasticsearch.client.elc.rest5_client;
|
||||
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.TransportUtils;
|
||||
import co.elastic.clients.transport.rest5_client.Rest5ClientOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.RequestOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.Rest5ClientBuilder;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.hc.client5.http.config.ConnectionConfig;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
|
||||
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
|
||||
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility class containing the functions to create the Elasticsearch Rest5Client used from Elasticsearch 9 on.
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class Rest5Clients {
|
||||
|
||||
// values copied from Rest5ClientBuilder
|
||||
public static final int DEFAULT_SOCKET_TIMEOUT_MILLIS = 30000;
|
||||
public static final int DEFAULT_RESPONSE_TIMEOUT_MILLIS = 0; // meaning infinite
|
||||
|
||||
private Rest5Clients() {}
|
||||
|
||||
/**
|
||||
* Creates a low level {@link Rest5Client} for the given configuration.
|
||||
*
|
||||
* @param clientConfiguration must not be {@literal null}
|
||||
* @return the {@link Rest5Client}
|
||||
*/
|
||||
public static Rest5Client getRest5Client(ClientConfiguration clientConfiguration) {
|
||||
return getRest5ClientBuilder(clientConfiguration).build();
|
||||
}
|
||||
|
||||
private static Rest5ClientBuilder getRest5ClientBuilder(ClientConfiguration clientConfiguration) {
|
||||
|
||||
HttpHost[] httpHosts = getHttpHosts(clientConfiguration);
|
||||
Rest5ClientBuilder builder = Rest5Client.builder(httpHosts);
|
||||
|
||||
if (clientConfiguration.getPathPrefix() != null) {
|
||||
builder.setPathPrefix(clientConfiguration.getPathPrefix());
|
||||
}
|
||||
|
||||
HttpHeaders headers = clientConfiguration.getDefaultHeaders();
|
||||
|
||||
if (!headers.isEmpty()) {
|
||||
builder.setDefaultHeaders(toHeaderArray(headers));
|
||||
}
|
||||
|
||||
// RestClientBuilder configuration callbacks from the consumer
|
||||
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurationCallback : clientConfiguration
|
||||
.getClientConfigurers()) {
|
||||
if (clientConfigurationCallback instanceof ElasticsearchRest5ClientConfigurationCallback configurationCallback) {
|
||||
builder = configurationCallback.configure(builder);
|
||||
}
|
||||
}
|
||||
|
||||
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||
Duration socketTimeout = clientConfiguration.getSocketTimeout();
|
||||
|
||||
builder.setHttpClientConfigCallback(httpAsyncClientBuilder -> {
|
||||
|
||||
if (clientConfiguration.getProxy().isPresent()) {
|
||||
var proxy = clientConfiguration.getProxy().get();
|
||||
try {
|
||||
var proxyRoutePlanner = new DefaultProxyRoutePlanner(HttpHost.create(proxy));
|
||||
httpAsyncClientBuilder.setRoutePlanner(proxyRoutePlanner);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
httpAsyncClientBuilder.addRequestInterceptorFirst((request, entity, context) -> {
|
||||
clientConfiguration.getHeadersSupplier().get().forEach((header, values) -> {
|
||||
// The accept and content-type headers are already put on the request, despite this being the first
|
||||
// interceptor.
|
||||
if ("Accept".equalsIgnoreCase(header) || " Content-Type".equalsIgnoreCase(header)) {
|
||||
request.removeHeaders(header);
|
||||
}
|
||||
values.forEach(value -> request.addHeader(header, value));
|
||||
});
|
||||
});
|
||||
|
||||
// add httpclient configurator callbacks provided by the configuration
|
||||
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
|
||||
.getClientConfigurers()) {
|
||||
if (clientConfigurer instanceof ElasticsearchHttpClientConfigurationCallback httpClientConfigurer) {
|
||||
httpAsyncClientBuilder = httpClientConfigurer.configure(httpAsyncClientBuilder);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
builder.setConnectionConfigCallback(connectionConfigBuilder -> {
|
||||
|
||||
if (!connectTimeout.isNegative()) {
|
||||
connectionConfigBuilder.setConnectTimeout(
|
||||
Timeout.of(Math.toIntExact(connectTimeout.toMillis()), TimeUnit.MILLISECONDS));
|
||||
}
|
||||
if (!socketTimeout.isNegative()) {
|
||||
var soTimeout = Timeout.of(Math.toIntExact(socketTimeout.toMillis()), TimeUnit.MILLISECONDS);
|
||||
connectionConfigBuilder.setSocketTimeout(soTimeout);
|
||||
} else {
|
||||
connectionConfigBuilder.setSocketTimeout(Timeout.of(DEFAULT_SOCKET_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
// add connectionConfig configurator callbacks provided by the configuration
|
||||
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
|
||||
.getClientConfigurers()) {
|
||||
if (clientConfigurer instanceof ElasticsearchConnectionConfigurationCallback connectionConfigurationCallback) {
|
||||
connectionConfigBuilder = connectionConfigurationCallback.configure(connectionConfigBuilder);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
builder.setConnectionManagerCallback(poolingAsyncClientConnectionManagerBuilder -> {
|
||||
|
||||
SSLContext sslContext = null;
|
||||
try {
|
||||
sslContext = clientConfiguration.getCaFingerprint().isPresent()
|
||||
? TransportUtils.sslContextFromCaFingerprint(clientConfiguration.getCaFingerprint().get())
|
||||
: (clientConfiguration.getSslContext().isPresent()
|
||||
? clientConfiguration.getSslContext().get()
|
||||
: SSLContext.getDefault());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("could not create the default ssl context", e);
|
||||
}
|
||||
poolingAsyncClientConnectionManagerBuilder.setTlsStrategy(new BasicClientTlsStrategy(sslContext));
|
||||
|
||||
// add connectionManager configurator callbacks provided by the configuration
|
||||
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
|
||||
.getClientConfigurers()) {
|
||||
if (clientConfigurer instanceof ElasticsearchConnectionManagerCallback connectionManagerCallback) {
|
||||
poolingAsyncClientConnectionManagerBuilder = connectionManagerCallback
|
||||
.configure(poolingAsyncClientConnectionManagerBuilder);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
builder.setRequestConfigCallback(requestConfigBuilder -> {
|
||||
|
||||
if (!socketTimeout.isNegative()) {
|
||||
var soTimeout = Timeout.of(Math.toIntExact(socketTimeout.toMillis()), TimeUnit.MILLISECONDS);
|
||||
requestConfigBuilder.setConnectionRequestTimeout(soTimeout);
|
||||
} else {
|
||||
requestConfigBuilder
|
||||
.setConnectionRequestTimeout(Timeout.of(DEFAULT_RESPONSE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
// add connectionConfig configurator callbacks provided by the configuration
|
||||
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
|
||||
.getClientConfigurers()) {
|
||||
if (clientConfigurer instanceof ElasticsearchRequestConfigCallback requestConfigCallback) {
|
||||
requestConfigBuilder = requestConfigCallback.configure(requestConfigBuilder);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static HttpHost @NonNull [] getHttpHosts(ClientConfiguration clientConfiguration) {
|
||||
List<InetSocketAddress> hosts = clientConfiguration.getEndpoints();
|
||||
boolean useSsl = clientConfiguration.useSsl();
|
||||
return hosts.stream()
|
||||
.map(it -> (useSsl ? "https" : "http") + "://" + it.getHostString() + ':' + it.getPort())
|
||||
.map(URI::create)
|
||||
.map(HttpHost::create)
|
||||
.toArray(HttpHost[]::new);
|
||||
}
|
||||
|
||||
private static Header[] toHeaderArray(HttpHeaders headers) {
|
||||
return headers.entrySet().stream() //
|
||||
.flatMap(entry -> entry.getValue().stream() //
|
||||
.map(value -> new BasicHeader(entry.getKey(), value))) //
|
||||
.toList().toArray(new Header[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ClientConfiguration.ClientConfigurationCallback} to configure the Rest5Client client with a
|
||||
* {@link Rest5ClientBuilder}
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface ElasticsearchRest5ClientConfigurationCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<Rest5ClientBuilder> {
|
||||
|
||||
static ElasticsearchRest5ClientConfigurationCallback from(
|
||||
Function<Rest5ClientBuilder, Rest5ClientBuilder> rest5ClientBuilderCallback) {
|
||||
|
||||
Assert.notNull(rest5ClientBuilderCallback, "rest5ClientBuilderCallback must not be null");
|
||||
|
||||
return rest5ClientBuilderCallback::apply;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||
* the Elasticsearch Rest5Client's Http client with a {@link HttpAsyncClientBuilder}
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface ElasticsearchHttpClientConfigurationCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
|
||||
|
||||
static Rest5Clients.ElasticsearchHttpClientConfigurationCallback from(
|
||||
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> httpClientBuilderCallback) {
|
||||
|
||||
Assert.notNull(httpClientBuilderCallback, "httpClientBuilderCallback must not be null");
|
||||
|
||||
return httpClientBuilderCallback::apply;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||
* the Elasticsearch Rest5Client's connection with a {@link ConnectionConfig.Builder}
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface ElasticsearchConnectionConfigurationCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<ConnectionConfig.Builder> {
|
||||
|
||||
static ElasticsearchConnectionConfigurationCallback from(
|
||||
Function<ConnectionConfig.Builder, ConnectionConfig.Builder> connectionConfigBuilderCallback) {
|
||||
|
||||
Assert.notNull(connectionConfigBuilderCallback, "connectionConfigBuilderCallback must not be null");
|
||||
|
||||
return connectionConfigBuilderCallback::apply;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||
* the Elasticsearch Rest5Client's connection manager with a {@link PoolingAsyncClientConnectionManagerBuilder}
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface ElasticsearchConnectionManagerCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<PoolingAsyncClientConnectionManagerBuilder> {
|
||||
|
||||
static ElasticsearchConnectionManagerCallback from(
|
||||
Function<PoolingAsyncClientConnectionManagerBuilder, PoolingAsyncClientConnectionManagerBuilder> connectionManagerBuilderCallback) {
|
||||
|
||||
Assert.notNull(connectionManagerBuilderCallback, "connectionManagerBuilderCallback must not be null");
|
||||
|
||||
return connectionManagerBuilderCallback::apply;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||
* the Elasticsearch Rest5Client's connection manager with a {@link RequestConfig.Builder}
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface ElasticsearchRequestConfigCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<RequestConfig.Builder> {
|
||||
|
||||
static ElasticsearchRequestConfigCallback from(
|
||||
Function<RequestConfig.Builder, RequestConfig.Builder> requestConfigBuilderCallback) {
|
||||
|
||||
Assert.notNull(requestConfigBuilderCallback, "requestConfigBuilderCallback must not be null");
|
||||
|
||||
return requestConfigBuilderCallback::apply;
|
||||
}
|
||||
}
|
||||
|
||||
public static Rest5ClientOptions.Builder getRest5ClientOptionsBuilder(@Nullable TransportOptions transportOptions) {
|
||||
|
||||
if (transportOptions instanceof Rest5ClientOptions rest5ClientOptions) {
|
||||
return rest5ClientOptions.toBuilder();
|
||||
}
|
||||
|
||||
var builder = new Rest5ClientOptions.Builder(RequestOptions.DEFAULT.toBuilder());
|
||||
|
||||
if (transportOptions != null) {
|
||||
transportOptions.headers().forEach(header -> builder.addHeader(header.getKey(), header.getValue()));
|
||||
transportOptions.queryParameters().forEach(builder::setParameter);
|
||||
builder.onWarnings(transportOptions.onWarnings());
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package contains related to the new (from Elasticsearch 9 on) Rest5Client. There are also classes copied over from Elasticsearch in order to have a Rest5ClientBuilder that allows to configure the http client.
|
||||
*/
|
||||
@org.jspecify.annotations.NullMarked
|
||||
package org.springframework.data.elasticsearch.client.elc.rest5_client;
|
@ -0,0 +1,195 @@
|
||||
package org.springframework.data.elasticsearch.client.elc.rest_client;
|
||||
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.TransportUtils;
|
||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpRequestInterceptor;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.RestClientBuilder;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility class containing the functions to create the Elasticsearch RestClient used up to Elasticsearch 9.
|
||||
*
|
||||
* @since 6.0
|
||||
* @deprecated since 6.0, use the new Rest5Client the code for that is in the package ../rest_client.
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public final class RestClients {
|
||||
|
||||
/**
|
||||
* Creates a low level {@link RestClient} for the given configuration.
|
||||
*
|
||||
* @param clientConfiguration must not be {@literal null}
|
||||
* @return the {@link RestClient}
|
||||
*/
|
||||
public static RestClient getRestClient(ClientConfiguration clientConfiguration) {
|
||||
return getRestClientBuilder(clientConfiguration).build();
|
||||
}
|
||||
|
||||
private static RestClientBuilder getRestClientBuilder(ClientConfiguration clientConfiguration) {
|
||||
HttpHost[] httpHosts = getHttpHosts(clientConfiguration);
|
||||
RestClientBuilder builder = RestClient.builder(httpHosts);
|
||||
|
||||
if (clientConfiguration.getPathPrefix() != null) {
|
||||
builder.setPathPrefix(clientConfiguration.getPathPrefix());
|
||||
}
|
||||
|
||||
HttpHeaders headers = clientConfiguration.getDefaultHeaders();
|
||||
|
||||
if (!headers.isEmpty()) {
|
||||
builder.setDefaultHeaders(toHeaderArray(headers));
|
||||
}
|
||||
|
||||
builder.setHttpClientConfigCallback(clientBuilder -> {
|
||||
if (clientConfiguration.getCaFingerprint().isPresent()) {
|
||||
clientBuilder
|
||||
.setSSLContext(TransportUtils.sslContextFromCaFingerprint(clientConfiguration.getCaFingerprint().get()));
|
||||
}
|
||||
clientConfiguration.getSslContext().ifPresent(clientBuilder::setSSLContext);
|
||||
clientConfiguration.getHostNameVerifier().ifPresent(clientBuilder::setSSLHostnameVerifier);
|
||||
clientBuilder.addInterceptorLast(new CustomHeaderInjector(clientConfiguration.getHeadersSupplier()));
|
||||
|
||||
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
|
||||
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||
|
||||
if (!connectTimeout.isNegative()) {
|
||||
requestConfigBuilder.setConnectTimeout(Math.toIntExact(connectTimeout.toMillis()));
|
||||
}
|
||||
|
||||
Duration socketTimeout = clientConfiguration.getSocketTimeout();
|
||||
|
||||
if (!socketTimeout.isNegative()) {
|
||||
requestConfigBuilder.setSocketTimeout(Math.toIntExact(socketTimeout.toMillis()));
|
||||
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(socketTimeout.toMillis()));
|
||||
}
|
||||
|
||||
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
|
||||
|
||||
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
|
||||
|
||||
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
|
||||
.getClientConfigurers()) {
|
||||
if (clientConfigurer instanceof RestClients.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;
|
||||
}
|
||||
|
||||
private static HttpHost @NonNull [] getHttpHosts(ClientConfiguration clientConfiguration) {
|
||||
List<InetSocketAddress> hosts = clientConfiguration.getEndpoints();
|
||||
boolean useSsl = clientConfiguration.useSsl();
|
||||
return hosts.stream()
|
||||
.map(it -> (useSsl ? "https" : "http") + "://" + it.getHostString() + ':' + it.getPort())
|
||||
.map(HttpHost::create).toArray(HttpHost[]::new);
|
||||
}
|
||||
|
||||
private static org.apache.http.Header[] toHeaderArray(HttpHeaders headers) {
|
||||
return headers.entrySet().stream() //
|
||||
.flatMap(entry -> entry.getValue().stream() //
|
||||
.map(value -> new BasicHeader(entry.getKey(), value))) //
|
||||
.toArray(org.apache.http.Header[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interceptor to inject custom supplied headers.
|
||||
*
|
||||
* @since 4.4
|
||||
*/
|
||||
record CustomHeaderInjector(Supplier<HttpHeaders> headersSupplier) implements HttpRequestInterceptor {
|
||||
|
||||
@Override
|
||||
public void process(HttpRequest request, HttpContext context) {
|
||||
HttpHeaders httpHeaders = headersSupplier.get();
|
||||
|
||||
if (httpHeaders != null && !httpHeaders.isEmpty()) {
|
||||
Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||
* the Elasticsearch RestClient's Http client with a {@link HttpAsyncClientBuilder}
|
||||
*
|
||||
* @since 4.4
|
||||
*/
|
||||
public interface ElasticsearchHttpClientConfigurationCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
|
||||
|
||||
static RestClients.ElasticsearchHttpClientConfigurationCallback from(
|
||||
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> httpClientBuilderCallback) {
|
||||
|
||||
Assert.notNull(httpClientBuilderCallback, "httpClientBuilderCallback must not be null");
|
||||
|
||||
return httpClientBuilderCallback::apply;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||
* the RestClient client with a {@link RestClientBuilder}
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface ElasticsearchRestClientConfigurationCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<RestClientBuilder> {
|
||||
|
||||
static ElasticsearchRestClientConfigurationCallback from(
|
||||
Function<RestClientBuilder, RestClientBuilder> restClientBuilderCallback) {
|
||||
|
||||
Assert.notNull(restClientBuilderCallback, "restClientBuilderCallback must not be null");
|
||||
|
||||
return restClientBuilderCallback::apply;
|
||||
}
|
||||
}
|
||||
|
||||
public static RestClientOptions.Builder getRestClientOptionsBuilder(@Nullable TransportOptions transportOptions) {
|
||||
|
||||
if (transportOptions instanceof RestClientOptions restClientOptions) {
|
||||
return restClientOptions.toBuilder();
|
||||
}
|
||||
|
||||
var builder = new RestClientOptions.Builder(RequestOptions.DEFAULT.toBuilder());
|
||||
|
||||
if (transportOptions != null) {
|
||||
transportOptions.headers().forEach(header -> builder.addHeader(header.getKey(), header.getValue()));
|
||||
transportOptions.queryParameters().forEach(builder::setParameter);
|
||||
builder.onWarnings(transportOptions.onWarnings());
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021-2025 the original author or authors.
|
||||
* Copyright 2022-2025 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,15 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
/**
|
||||
* Define script types for update queries.
|
||||
*
|
||||
* @author Farid Faoudi
|
||||
* @since 4.2
|
||||
* This package contains related to the old (up to Elasticsearch 9) RestClient.
|
||||
*/
|
||||
|
||||
public enum ScriptType {
|
||||
INLINE, STORED
|
||||
}
|
||||
@Deprecated(since = "6.0", forRemoval=true)
|
||||
@org.jspecify.annotations.NullMarked
|
||||
package org.springframework.data.elasticsearch.client.elc.rest_client;
|
@ -233,6 +233,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
.subscribe(new Subscriber<>() {
|
||||
@Nullable private Subscription subscription = null;
|
||||
private final AtomicBoolean upstreamComplete = new AtomicBoolean(false);
|
||||
private final AtomicBoolean onNextHasBeenCalled = new AtomicBoolean(false);
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Subscription subscription) {
|
||||
@ -242,6 +243,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
|
||||
@Override
|
||||
public void onNext(List<T> entityList) {
|
||||
onNextHasBeenCalled.set(true);
|
||||
saveAll(entityList, index)
|
||||
.map(sink::tryEmitNext)
|
||||
.doOnComplete(() -> {
|
||||
@ -267,6 +269,10 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
@Override
|
||||
public void onComplete() {
|
||||
upstreamComplete.set(true);
|
||||
if (!onNextHasBeenCalled.get()) {
|
||||
// this happens when an empty flux is saved
|
||||
sink.tryEmitComplete();
|
||||
}
|
||||
}
|
||||
});
|
||||
return sink.asFlux();
|
||||
|
@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
@ -369,7 +370,7 @@ public class EntityOperations {
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#initializeVersionProperty()
|
||||
*/
|
||||
@Override
|
||||
public T initializeVersionProperty() {
|
||||
public @NonNull T initializeVersionProperty() {
|
||||
return map;
|
||||
}
|
||||
|
||||
@ -389,7 +390,7 @@ public class EntityOperations {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeqNoPrimaryTerm getSeqNoPrimaryTerm() {
|
||||
public @Nullable SeqNoPrimaryTerm getSeqNoPrimaryTerm() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -398,7 +399,7 @@ public class EntityOperations {
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#incrementVersion()
|
||||
*/
|
||||
@Override
|
||||
public T incrementVersion() {
|
||||
public @NonNull T incrementVersion() {
|
||||
return map;
|
||||
}
|
||||
|
||||
@ -407,7 +408,7 @@ public class EntityOperations {
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.Entity#getBean()
|
||||
*/
|
||||
@Override
|
||||
public T getBean() {
|
||||
public @NonNull T getBean() {
|
||||
return map;
|
||||
}
|
||||
|
||||
@ -425,12 +426,12 @@ public class EntityOperations {
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.Entity#getPersistentEntity()
|
||||
*/
|
||||
@Override
|
||||
public ElasticsearchPersistentEntity<?> getPersistentEntity() {
|
||||
public @Nullable ElasticsearchPersistentEntity<?> getPersistentEntity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRouting() {
|
||||
public @Nullable String getRouting() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -650,7 +651,7 @@ public class EntityOperations {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRouting() {
|
||||
public @Nullable String getRouting() {
|
||||
|
||||
String routing = routingResolver.getRouting(propertyAccessor.getBean());
|
||||
|
||||
|
@ -48,12 +48,12 @@ public class SearchHit<T> {
|
||||
@Nullable private final NestedMetaData nestedMetaData;
|
||||
@Nullable private final String routing;
|
||||
@Nullable private final Explanation explanation;
|
||||
private final List<String> matchedQueries = new ArrayList<>();
|
||||
private final Map<String, Double> matchedQueries = new LinkedHashMap<>();
|
||||
|
||||
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,
|
||||
@Nullable Explanation explanation, @Nullable List<String> matchedQueries, T content) {
|
||||
@Nullable Explanation explanation, @Nullable Map<String, Double> matchedQueries, T content) {
|
||||
this.index = index;
|
||||
this.id = id;
|
||||
this.routing = routing;
|
||||
@ -73,7 +73,7 @@ public class SearchHit<T> {
|
||||
this.content = content;
|
||||
|
||||
if (matchedQueries != null) {
|
||||
this.matchedQueries.addAll(matchedQueries);
|
||||
this.matchedQueries.putAll(matchedQueries);
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,7 +194,7 @@ public class SearchHit<T> {
|
||||
* @return the matched queries for this SearchHit.
|
||||
*/
|
||||
@Nullable
|
||||
public List<String> getMatchedQueries() {
|
||||
public Map<String, Double> getMatchedQueries() {
|
||||
return matchedQueries;
|
||||
}
|
||||
}
|
||||
|
@ -714,7 +714,7 @@ public class MappingElasticsearchConverter
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getPropertyValue(ElasticsearchPersistentProperty property) {
|
||||
public <T> @Nullable T getPropertyValue(ElasticsearchPersistentProperty property) {
|
||||
|
||||
String expression = property.getSpelExpression();
|
||||
Object value = expression != null ? evaluator.evaluate(expression) : accessor.get(property);
|
||||
|
@ -125,5 +125,5 @@ public interface SearchDocument extends Document {
|
||||
* @return the matched queries for the SearchHit.
|
||||
*/
|
||||
@Nullable
|
||||
List<String> getMatchedQueries();
|
||||
Map<String, Double> getMatchedQueries();
|
||||
}
|
||||
|
@ -41,12 +41,12 @@ public class SearchDocumentAdapter implements SearchDocument {
|
||||
private final Map<String, SearchDocumentResponse> innerHits = new HashMap<>();
|
||||
@Nullable private final NestedMetaData nestedMetaData;
|
||||
@Nullable private final Explanation explanation;
|
||||
@Nullable private final List<String> matchedQueries;
|
||||
@Nullable private final Map<String, Double> matchedQueries;
|
||||
@Nullable private final String routing;
|
||||
|
||||
public SearchDocumentAdapter(Document delegate, float score, Object[] sortValues, Map<String, List<Object>> fields,
|
||||
Map<String, List<String>> highlightFields, Map<String, SearchDocumentResponse> innerHits,
|
||||
@Nullable NestedMetaData nestedMetaData, @Nullable Explanation explanation, @Nullable List<String> matchedQueries,
|
||||
@Nullable NestedMetaData nestedMetaData, @Nullable Explanation explanation, @Nullable Map<String, Double> matchedQueries,
|
||||
@Nullable String routing) {
|
||||
|
||||
this.delegate = delegate;
|
||||
@ -249,7 +249,7 @@ public class SearchDocumentAdapter implements SearchDocument {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public List<String> getMatchedQueries() {
|
||||
public Map<String, Double> getMatchedQueries() {
|
||||
return matchedQueries;
|
||||
}
|
||||
|
||||
|
@ -66,8 +66,7 @@ public class AliasActionParameters {
|
||||
return indices;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String[] getAliases() {
|
||||
public String@Nullable[] getAliases() {
|
||||
return aliases;
|
||||
}
|
||||
|
||||
@ -107,8 +106,8 @@ public class AliasActionParameters {
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
@Nullable private String[] indices;
|
||||
@Nullable private String[] aliases;
|
||||
private String @Nullable [] indices;
|
||||
private String @Nullable [] aliases;
|
||||
@Nullable private Query filterQuery;
|
||||
@Nullable private Class<?> filterQueryClass;
|
||||
@Nullable private Boolean isHidden;
|
||||
|
@ -55,6 +55,7 @@ public class AliasActions {
|
||||
public AliasActions add(@Nullable AliasAction... actions) {
|
||||
|
||||
if (actions != null) {
|
||||
// noinspection NullableProblems
|
||||
this.actions.addAll(Arrays.asList(actions));
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
@ -273,7 +274,7 @@ public class MappingBuilder {
|
||||
writeTypeHintMapping(propertiesNode);
|
||||
|
||||
if (entity != null) {
|
||||
entity.doWithProperties((PropertyHandler<ElasticsearchPersistentProperty>) property -> {
|
||||
entity.doWithProperties((PropertyHandler<@NonNull ElasticsearchPersistentProperty>) property -> {
|
||||
try {
|
||||
if (property.isAnnotationPresent(Transient.class) || isInIgnoreFields(property, parentFieldAnnotation)) {
|
||||
return;
|
||||
|
@ -35,7 +35,7 @@ public record PutIndexTemplateRequest(String name, String[] indexPatterns, @Null
|
||||
public static class Builder {
|
||||
|
||||
@Nullable private String name;
|
||||
@Nullable private String[] indexPatterns;
|
||||
private String @Nullable [] indexPatterns;
|
||||
@Nullable private Settings settings;
|
||||
@Nullable private Document mapping;
|
||||
@Nullable AliasActions aliasActions;
|
||||
|
@ -81,7 +81,7 @@ public class TemplateData {
|
||||
@Nullable Document mapping;
|
||||
int order;
|
||||
@Nullable Integer version;
|
||||
@Nullable private String[] indexPatterns;
|
||||
@Nullable private String@Nullable [] indexPatterns;
|
||||
@Nullable private Map<String, AliasData> aliases;
|
||||
|
||||
private TemplateDataBuilder() {}
|
||||
|
@ -65,7 +65,7 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
|
||||
String getIndexStoreType();
|
||||
|
||||
@Override
|
||||
ElasticsearchPersistentProperty getVersionProperty();
|
||||
@Nullable ElasticsearchPersistentProperty getVersionProperty();
|
||||
|
||||
@Nullable
|
||||
String settingPath();
|
||||
|
@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
@ -298,7 +299,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
|
||||
return fieldNamePropertyCache.computeIfAbsent(fieldName, key -> {
|
||||
AtomicReference<ElasticsearchPersistentProperty> propertyRef = new AtomicReference<>();
|
||||
doWithProperties((PropertyHandler<ElasticsearchPersistentProperty>) property -> {
|
||||
doWithProperties((PropertyHandler<@NonNull ElasticsearchPersistentProperty>) property -> {
|
||||
if (key.equals(property.getFieldName())) {
|
||||
propertyRef.set(property);
|
||||
}
|
||||
@ -552,10 +553,10 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
short replicas;
|
||||
@Nullable String refreshIntervall;
|
||||
@Nullable String indexStoreType;
|
||||
@Nullable private String[] sortFields;
|
||||
private Setting.@Nullable SortOrder[] sortOrders;
|
||||
private Setting.@Nullable SortMode[] sortModes;
|
||||
private Setting.@Nullable SortMissing[] sortMissingValues;
|
||||
private String @Nullable [] sortFields;
|
||||
private Setting.@Nullable SortOrder @Nullable [] sortOrders;
|
||||
private Setting.@Nullable SortMode @Nullable [] sortModes;
|
||||
private Setting.@Nullable SortMissing @Nullable [] sortMissingValues;
|
||||
|
||||
Settings toSettings() {
|
||||
|
||||
|
@ -28,6 +28,7 @@ import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.util.Assert;
|
||||
@ -47,10 +48,15 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class BaseQuery implements Query {
|
||||
|
||||
public static final int INDEX_MAX_RESULT_WINDOW = 10_000;
|
||||
|
||||
private static final int DEFAULT_REACTIVE_BATCH_SIZE = 500;
|
||||
// the instance to mark the query pageable initial status, needed to distinguish between the initial
|
||||
// value and a user-set unpaged value; values don't matter, the RequestConverter compares to the isntance.
|
||||
private static final Pageable UNSET_PAGE = PageRequest.of(0, 1);
|
||||
|
||||
@Nullable protected Sort sort;
|
||||
protected Pageable pageable = DEFAULT_PAGE;
|
||||
protected Pageable pageable = UNSET_PAGE;
|
||||
protected List<String> fields = new ArrayList<>();
|
||||
@Nullable protected List<String> storedFields;
|
||||
@Nullable protected SourceFilter sourceFilter;
|
||||
@ -78,7 +84,7 @@ public class BaseQuery implements Query {
|
||||
private boolean queryIsUpdatedByConverter = false;
|
||||
@Nullable private Integer reactiveBatchSize = null;
|
||||
@Nullable private Boolean allowNoIndices = null;
|
||||
private EnumSet<IndicesOptions.WildcardStates> expandWildcards;
|
||||
private EnumSet<IndicesOptions.WildcardStates> expandWildcards = EnumSet.noneOf(IndicesOptions.WildcardStates.class);
|
||||
private List<DocValueField> docValueFields = new ArrayList<>();
|
||||
private List<ScriptedField> scriptedFields = new ArrayList<>();
|
||||
|
||||
@ -87,7 +93,7 @@ public class BaseQuery implements Query {
|
||||
public <Q extends BaseQuery, B extends BaseQueryBuilder<Q, B>> BaseQuery(BaseQueryBuilder<Q, B> builder) {
|
||||
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.setPageable(builder.getPageable() != null ? builder.getPageable() : UNSET_PAGE);
|
||||
this.fields = builder.getFields();
|
||||
this.storedFields = builder.getStoredFields();
|
||||
this.sourceFilter = builder.getSourceFilter();
|
||||
@ -203,7 +209,7 @@ public class BaseQuery implements Query {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public final <T extends Query> T addSort(@Nullable Sort sort) {
|
||||
if (sort == null) {
|
||||
if (sort == null || sort.isUnsorted()) {
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@ -561,4 +567,52 @@ public class BaseQuery implements Query {
|
||||
public List<ScriptedField> getScriptedFields() {
|
||||
return scriptedFields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getRequestSize() {
|
||||
|
||||
var pageable = getPageable();
|
||||
Integer requestSize = null;
|
||||
|
||||
if (pageable.isPaged() && pageable != UNSET_PAGE) {
|
||||
// pagesize defined by the user
|
||||
if (!isLimiting()) {
|
||||
// no maxResults
|
||||
requestSize = pageable.getPageSize();
|
||||
} else {
|
||||
// if we have both a page size and a max results, we take the min, this is necessary for
|
||||
// searchForStream to work correctly (#3098) as there the page size defines what is
|
||||
// returned in a single request, and the max result determines the total number of
|
||||
// documents returned.
|
||||
requestSize = Math.min(pageable.getPageSize(), getMaxResults());
|
||||
}
|
||||
} else if (pageable == UNSET_PAGE) {
|
||||
// no user defined pageable
|
||||
if (isLimiting()) {
|
||||
// maxResults
|
||||
requestSize = getMaxResults();
|
||||
} else {
|
||||
requestSize = DEFAULT_PAGE_SIZE;
|
||||
}
|
||||
} else {
|
||||
// explicitly set unpaged
|
||||
if (!isLimiting()) {
|
||||
// no maxResults
|
||||
requestSize = INDEX_MAX_RESULT_WINDOW;
|
||||
} else {
|
||||
// if we have both a implicit page size and a max results, we take the min, this is necessary for
|
||||
// searchForStream to work correctly (#3098) as there the page size defines what is
|
||||
// returned in a single request, and the max result determines the total number of
|
||||
// documents returned.
|
||||
requestSize = Math.min(INDEX_MAX_RESULT_WINDOW, getMaxResults());
|
||||
}
|
||||
}
|
||||
|
||||
if (requestSize == null) {
|
||||
// this should not happen
|
||||
requestSize = DEFAULT_PAGE_SIZE;
|
||||
}
|
||||
|
||||
return requestSize;
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
public Integer getReactiveBatchSize() {
|
||||
public @Nullable Integer getReactiveBatchSize() {
|
||||
return reactiveBatchSize;
|
||||
}
|
||||
|
||||
|
@ -344,8 +344,8 @@ public class ByQueryResponse {
|
||||
*
|
||||
* @return a new {@link SearchFailureBuilder} to build {@link SearchFailure}
|
||||
*/
|
||||
public static SearchFailureBuilder builder() {
|
||||
return new SearchFailureBuilder();
|
||||
public static SearchFailureBuilder builder(Throwable reason) {
|
||||
return new SearchFailureBuilder(reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -358,7 +358,9 @@ public class ByQueryResponse {
|
||||
@Nullable private Integer shardId;
|
||||
@Nullable private String nodeId;
|
||||
|
||||
private SearchFailureBuilder() {}
|
||||
private SearchFailureBuilder(Throwable reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public SearchFailureBuilder withReason(Throwable reason) {
|
||||
this.reason = reason;
|
||||
|
@ -63,12 +63,12 @@ public class FetchSourceFilter implements SourceFilter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getIncludes() {
|
||||
public @Nullable String[] getIncludes() {
|
||||
return includes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getExcludes() {
|
||||
public @Nullable String[] getExcludes() {
|
||||
return excludes;
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ import org.jspecify.annotations.Nullable;
|
||||
public class FetchSourceFilterBuilder {
|
||||
|
||||
@Nullable private Boolean fetchSource;
|
||||
@Nullable private String[] includes;
|
||||
@Nullable private String[] excludes;
|
||||
private String @Nullable [] includes;
|
||||
private String @Nullable [] excludes;
|
||||
|
||||
public FetchSourceFilterBuilder withIncludes(String... includes) {
|
||||
this.includes = includes;
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
|
||||
|
||||
@ -92,7 +93,7 @@ public class GeoDistanceOrder extends Order {
|
||||
getIgnoreUnmapped());
|
||||
}
|
||||
|
||||
public GeoDistanceOrder with(Mode mode) {
|
||||
public GeoDistanceOrder with(@Nullable Mode mode) {
|
||||
return new GeoDistanceOrder(getProperty(), getGeoPoint(), getDirection(), getDistanceType(), mode, getUnit(),
|
||||
getIgnoreUnmapped());
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ public class HasChildQuery {
|
||||
|
||||
public static final class Builder {
|
||||
private final String type;
|
||||
private Query query;
|
||||
@Nullable private Query query;
|
||||
|
||||
@Nullable private Boolean ignoreUnmapped;
|
||||
|
||||
|
@ -92,7 +92,7 @@ public class HasParentQuery {
|
||||
|
||||
public static class Builder {
|
||||
private final String parentType;
|
||||
private Query query;
|
||||
@Nullable private Query query;
|
||||
|
||||
@Nullable private Boolean score;
|
||||
@Nullable private Boolean ignoreUnmapped;
|
||||
|
@ -484,6 +484,13 @@ public interface Query {
|
||||
*/
|
||||
List<ScriptedField> getScriptedFields();
|
||||
|
||||
/**
|
||||
* @return the number of documents that should be requested from Elasticsearch in this query. Depends wether a
|
||||
* Pageable and/or maxResult size is set on the query.
|
||||
* @since 5.4.8 5.5.2
|
||||
*/
|
||||
public Integer getRequestSize();
|
||||
|
||||
/**
|
||||
* @since 4.3
|
||||
*/
|
||||
|
@ -15,100 +15,110 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* value class combining script information.
|
||||
* <p>
|
||||
* A script is either an inline script, then the script parameters must be set
|
||||
* or it refers to a stored script, then the name parameter is required.
|
||||
*
|
||||
* @param language the language when the script is passed in the script parameter
|
||||
* @param script the script to use as inline script
|
||||
* @param scriptName the name when using a stored script
|
||||
* @param params the script parameters
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
public record ScriptData(ScriptType type, @Nullable String language, @Nullable String script,
|
||||
@Nullable String scriptName, @Nullable Map<String, Object> params) {
|
||||
public record ScriptData(@Nullable String language, @Nullable String script,
|
||||
@Nullable String scriptName, @Nullable Map<String, Object> params) {
|
||||
|
||||
public ScriptData(ScriptType type, @Nullable String language, @Nullable String script, @Nullable String scriptName,
|
||||
@Nullable Map<String, Object> params) {
|
||||
/*
|
||||
* constructor overload to check the parameters
|
||||
*/
|
||||
public ScriptData(@Nullable String language, @Nullable String script, @Nullable String scriptName,
|
||||
@Nullable Map<String, Object> params) {
|
||||
|
||||
Assert.notNull(type, "type must not be null");
|
||||
Assert.isTrue(script != null || scriptName != null, "script or scriptName is required");
|
||||
|
||||
this.type = type;
|
||||
this.language = language;
|
||||
this.script = script;
|
||||
this.scriptName = scriptName;
|
||||
this.params = params;
|
||||
}
|
||||
this.language = language;
|
||||
this.script = script;
|
||||
this.scriptName = scriptName;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public static ScriptData of(ScriptType type, @Nullable String language, @Nullable String script,
|
||||
@Nullable String scriptName, @Nullable Map<String, Object> params) {
|
||||
return new ScriptData(type, language, script, scriptName, params);
|
||||
}
|
||||
/**
|
||||
* factory method to create a ScriptData object.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
public static ScriptData of(@Nullable String language, @Nullable String script,
|
||||
@Nullable String scriptName, @Nullable Map<String, Object> params) {
|
||||
return new ScriptData(language, script, scriptName, params);
|
||||
}
|
||||
|
||||
public static ScriptData of(Function<Builder, Builder> builderFunction) {
|
||||
/**
|
||||
* factory method to create a ScriptData object using a ScriptBuilder callback.
|
||||
*
|
||||
* @param builderFunction function called to populate the builder
|
||||
* @return
|
||||
*/
|
||||
public static ScriptData of(Function<Builder, Builder> builderFunction) {
|
||||
|
||||
Assert.notNull(builderFunction, "f must not be null");
|
||||
Assert.notNull(builderFunction, "builderFunction must not be null");
|
||||
|
||||
return builderFunction.apply(new Builder()).build();
|
||||
}
|
||||
return builderFunction.apply(new Builder()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public static final class Builder {
|
||||
@Nullable private ScriptType type;
|
||||
@Nullable private String language;
|
||||
@Nullable private String script;
|
||||
@Nullable private String scriptName;
|
||||
@Nullable private Map<String, Object> params;
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public static final class Builder {
|
||||
@Nullable
|
||||
private String language;
|
||||
@Nullable
|
||||
private String script;
|
||||
@Nullable
|
||||
private String scriptName;
|
||||
@Nullable
|
||||
private Map<String, Object> params;
|
||||
|
||||
private Builder() {}
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder withType(ScriptType type) {
|
||||
public Builder withLanguage(@Nullable String language) {
|
||||
this.language = language;
|
||||
return this;
|
||||
}
|
||||
|
||||
Assert.notNull(type, "type must not be null");
|
||||
public Builder withScript(@Nullable String script) {
|
||||
this.script = script;
|
||||
return this;
|
||||
}
|
||||
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
public Builder withScriptName(@Nullable String scriptName) {
|
||||
this.scriptName = scriptName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLanguage(@Nullable String language) {
|
||||
this.language = language;
|
||||
return this;
|
||||
}
|
||||
public Builder withParams(@Nullable Map<String, Object> params) {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withScript(@Nullable String script) {
|
||||
this.script = script;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withScriptName(@Nullable String scriptName) {
|
||||
this.scriptName = scriptName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withParams(@Nullable Map<String, Object> params) {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScriptData build() {
|
||||
|
||||
Assert.notNull(type, "type must be set");
|
||||
|
||||
return new ScriptData(type, language, script, scriptName, params);
|
||||
}
|
||||
}
|
||||
public ScriptData build() {
|
||||
return new ScriptData(language, script, scriptName, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,14 +32,14 @@ public interface SourceFilter {
|
||||
/**
|
||||
* @return the name of the fields to include in a response.
|
||||
*/
|
||||
@Nullable
|
||||
String[] getIncludes();
|
||||
|
||||
String@Nullable[] getIncludes();
|
||||
|
||||
/**
|
||||
* @return the names of the fields to exclude from a response.
|
||||
*/
|
||||
@Nullable
|
||||
String[] getExcludes();
|
||||
|
||||
String@Nullable[] getExcludes();
|
||||
|
||||
/**
|
||||
* Flag to set the _source parameter in a query to true or false. If this is not null, the values returned from
|
||||
|
@ -42,8 +42,8 @@ public class UpdateQuery {
|
||||
@Nullable private final Boolean fetchSource;
|
||||
@Nullable private final List<String> fetchSourceIncludes;
|
||||
@Nullable private final List<String> fetchSourceExcludes;
|
||||
@Nullable private final Integer ifSeqNo;
|
||||
@Nullable private final Integer ifPrimaryTerm;
|
||||
@Nullable private final Long ifSeqNo;
|
||||
@Nullable private final Long ifPrimaryTerm;
|
||||
@Nullable private final RefreshPolicy refreshPolicy;
|
||||
@Nullable private final Integer retryOnConflict;
|
||||
@Nullable private final String timeout;
|
||||
@ -71,12 +71,12 @@ public class UpdateQuery {
|
||||
private UpdateQuery(String id, @Nullable String script, @Nullable Map<String, Object> params,
|
||||
@Nullable Document document, @Nullable Document upsert, @Nullable String lang, @Nullable String routing,
|
||||
@Nullable Boolean scriptedUpsert, @Nullable Boolean docAsUpsert, @Nullable Boolean fetchSource,
|
||||
@Nullable List<String> fetchSourceIncludes, @Nullable List<String> fetchSourceExcludes, @Nullable Integer ifSeqNo,
|
||||
@Nullable Integer ifPrimaryTerm, @Nullable RefreshPolicy refreshPolicy, @Nullable Integer retryOnConflict,
|
||||
@Nullable List<String> fetchSourceIncludes, @Nullable List<String> fetchSourceExcludes, @Nullable Long ifSeqNo,
|
||||
@Nullable Long ifPrimaryTerm, @Nullable RefreshPolicy refreshPolicy, @Nullable Integer retryOnConflict,
|
||||
@Nullable String timeout, @Nullable String waitForActiveShards, @Nullable Query query,
|
||||
@Nullable Boolean abortOnVersionConflict, @Nullable Integer batchSize, @Nullable Integer maxDocs,
|
||||
@Nullable Integer maxRetries, @Nullable String pipeline, @Nullable Float requestsPerSecond,
|
||||
@Nullable Boolean shouldStoreResult, @Nullable Integer slices, @Nullable ScriptType scriptType,
|
||||
@Nullable Boolean shouldStoreResult, @Nullable Integer slices,
|
||||
@Nullable String scriptName, @Nullable String indexName) {
|
||||
|
||||
this.id = id;
|
||||
@ -105,8 +105,8 @@ public class UpdateQuery {
|
||||
this.slices = slices;
|
||||
this.indexName = indexName;
|
||||
|
||||
if (scriptType != null || lang != null || script != null || scriptName != null || params != null) {
|
||||
this.scriptData = new ScriptData(scriptType, lang, script, scriptName, params);
|
||||
if (lang != null || script != null || scriptName != null || params != null) {
|
||||
this.scriptData = new ScriptData(lang, script, scriptName, params);
|
||||
} else {
|
||||
this.scriptData = null;
|
||||
}
|
||||
@ -172,12 +172,12 @@ public class UpdateQuery {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getIfSeqNo() {
|
||||
public Long getIfSeqNo() {
|
||||
return ifSeqNo;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getIfPrimaryTerm() {
|
||||
public Long getIfPrimaryTerm() {
|
||||
return ifPrimaryTerm;
|
||||
}
|
||||
|
||||
@ -246,11 +246,6 @@ public class UpdateQuery {
|
||||
return slices;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ScriptType getScriptType() {
|
||||
return scriptData != null ? scriptData.type() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getScriptName() {
|
||||
return scriptData != null ? scriptData.scriptName() : null;
|
||||
@ -273,7 +268,7 @@ public class UpdateQuery {
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private String id;
|
||||
private String id = "";
|
||||
@Nullable private String script = null;
|
||||
@Nullable private Map<String, Object> params;
|
||||
@Nullable private Document document = null;
|
||||
@ -283,8 +278,8 @@ public class UpdateQuery {
|
||||
@Nullable private Boolean scriptedUpsert;
|
||||
@Nullable private Boolean docAsUpsert;
|
||||
@Nullable private Boolean fetchSource;
|
||||
@Nullable private Integer ifSeqNo;
|
||||
@Nullable private Integer ifPrimaryTerm;
|
||||
@Nullable private Long ifSeqNo;
|
||||
@Nullable private Long ifPrimaryTerm;
|
||||
@Nullable private RefreshPolicy refreshPolicy;
|
||||
@Nullable private Integer retryOnConflict;
|
||||
@Nullable private String timeout;
|
||||
@ -300,7 +295,6 @@ public class UpdateQuery {
|
||||
@Nullable private Float requestsPerSecond;
|
||||
@Nullable private Boolean shouldStoreResult;
|
||||
@Nullable private Integer slices;
|
||||
@Nullable private ScriptType scriptType;
|
||||
@Nullable private String scriptName;
|
||||
@Nullable private String indexName;
|
||||
|
||||
@ -357,12 +351,12 @@ public class UpdateQuery {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withIfSeqNo(Integer ifSeqNo) {
|
||||
public Builder withIfSeqNo(Long ifSeqNo) {
|
||||
this.ifSeqNo = ifSeqNo;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withIfPrimaryTerm(Integer ifPrimaryTerm) {
|
||||
public Builder withIfPrimaryTerm(Long ifPrimaryTerm) {
|
||||
this.ifPrimaryTerm = ifPrimaryTerm;
|
||||
return this;
|
||||
}
|
||||
@ -437,11 +431,6 @@ public class UpdateQuery {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withScriptType(ScriptType scriptType) {
|
||||
this.scriptType = scriptType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withScriptName(String scriptName) {
|
||||
this.scriptName = scriptName;
|
||||
return this;
|
||||
@ -456,7 +445,7 @@ public class UpdateQuery {
|
||||
return new UpdateQuery(id, script, params, document, upsert, lang, routing, scriptedUpsert, docAsUpsert,
|
||||
fetchSource, fetchSourceIncludes, fetchSourceExcludes, ifSeqNo, ifPrimaryTerm, refreshPolicy, retryOnConflict,
|
||||
timeout, waitForActiveShards, query, abortOnVersionConflict, batchSize, maxDocs, maxRetries, pipeline,
|
||||
requestsPerSecond, shouldStoreResult, slices, scriptType, scriptName, indexName);
|
||||
requestsPerSecond, shouldStoreResult, slices, scriptName, indexName);
|
||||
}
|
||||
|
||||
public Builder withIndex(@Nullable String indexName) {
|
||||
|
@ -249,16 +249,15 @@ public class ReindexRequest {
|
||||
}
|
||||
|
||||
public static class Script {
|
||||
private final String source;
|
||||
@Nullable private final String source;
|
||||
@Nullable private final String lang;
|
||||
|
||||
private Script(String source, @Nullable String lang) {
|
||||
Assert.notNull(source, "source must not be null");
|
||||
|
||||
private Script(@Nullable String source, @Nullable String lang) {
|
||||
this.source = source;
|
||||
this.lang = lang;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ public record Script(String id, String language, String source) {
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(language, "language must not be null");
|
||||
Assert.notNull(source, "source must not be null");
|
||||
|
||||
}
|
||||
|
||||
public static ScriptBuilder builder() {
|
||||
|
@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.repository.query;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
|
||||
@ -33,7 +34,7 @@ import org.springframework.util.Assert;
|
||||
public class ReactiveRepositorySearchTemplateQuery extends AbstractReactiveElasticsearchRepositoryQuery {
|
||||
|
||||
private String id;
|
||||
private Map<String, Object> params;
|
||||
private Map<String, Object> params = Map.of();
|
||||
|
||||
public ReactiveRepositorySearchTemplateQuery(ReactiveElasticsearchQueryMethod queryMethod,
|
||||
ReactiveElasticsearchOperations elasticsearchOperations,
|
||||
|
@ -33,7 +33,7 @@ import org.springframework.util.Assert;
|
||||
public class RepositorySearchTemplateQuery extends AbstractElasticsearchRepositoryQuery {
|
||||
|
||||
private String id;
|
||||
private Map<String, Object> params;
|
||||
private Map<String, Object> params = Map.of();
|
||||
|
||||
public RepositorySearchTemplateQuery(ElasticsearchQueryMethod queryMethod,
|
||||
ElasticsearchOperations elasticsearchOperations, ValueExpressionDelegate valueExpressionDelegate,
|
||||
|
@ -82,7 +82,7 @@ class ExampleCriteriaMapper {
|
||||
|
||||
Object propertyValue = propertyAccessor.getProperty(property);
|
||||
if (property.isMap() && propertyValue != null) {
|
||||
for (Map.Entry<String, Object> entry : ((Map<String, Object>) propertyValue).entrySet()) {
|
||||
for (Map.Entry<String, @Nullable Object> entry : ((Map<String, @Nullable Object>) propertyValue).entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
criteria = applyPropertySpec(propertyPath + "." + key, value, exampleSpecAccessor, property, matchMode,
|
||||
@ -96,7 +96,7 @@ class ExampleCriteriaMapper {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
private Criteria applyPropertySpec(String path, Object propertyValue, ExampleMatcherAccessor exampleSpecAccessor,
|
||||
private Criteria applyPropertySpec(String path, @Nullable Object propertyValue, ExampleMatcherAccessor exampleSpecAccessor,
|
||||
ElasticsearchPersistentProperty property, ExampleMatcher.MatchMode matchMode, Criteria criteria) {
|
||||
|
||||
if (exampleSpecAccessor.isIgnoreCaseForPath(path)) {
|
||||
|
@ -47,13 +47,14 @@ public class QueryByExampleElasticsearchExecutor<T> implements QueryByExampleExe
|
||||
|
||||
@Override
|
||||
public <S extends T> Optional<S> findOne(Example<S> example) {
|
||||
CriteriaQuery criteriaQuery = CriteriaQuery.builder(exampleCriteriaMapper.criteria(example)).withMaxResults(2).build();
|
||||
CriteriaQuery criteriaQuery = CriteriaQuery.builder(exampleCriteriaMapper.criteria(example)).withMaxResults(2)
|
||||
.build();
|
||||
SearchHits<S> searchHits = operations.search(criteriaQuery, example.getProbeType(),
|
||||
operations.getIndexCoordinatesFor(example.getProbeType()));
|
||||
if (searchHits.getTotalHits() > 1) {
|
||||
throw new org.springframework.dao.IncorrectResultSizeDataAccessException(1);
|
||||
}
|
||||
return Optional.ofNullable(searchHits).filter(SearchHits::hasSearchHits)
|
||||
return Optional.of(searchHits).filter(SearchHits::hasSearchHits)
|
||||
.map(result -> (List<S>) SearchHitSupport.unwrapSearchHits(result)).map(s -> s.get(0));
|
||||
}
|
||||
|
||||
|
@ -39,11 +39,11 @@ import org.springframework.util.MultiValueMapAdapter;
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.0
|
||||
*/
|
||||
public class HttpHeaders implements MultiValueMap<String, String> {
|
||||
public class HttpHeaders implements MultiValueMap<String, @Nullable String> {
|
||||
|
||||
public static final String AUTHORIZATION = "Authorization";
|
||||
|
||||
private final MultiValueMap<String, String> delegate;
|
||||
private final MultiValueMap<String, @Nullable String> delegate;
|
||||
|
||||
public HttpHeaders() {
|
||||
this.delegate = new MultiValueMapAdapter<>(new LinkedCaseInsensitiveMap<>(Locale.ENGLISH));
|
||||
@ -62,12 +62,12 @@ public class HttpHeaders implements MultiValueMap<String, String> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(String key, List<? extends String> values) {
|
||||
public void addAll(String key, List<? extends @Nullable String> values) {
|
||||
delegate.addAll(key, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(MultiValueMap<String, String> values) {
|
||||
public void addAll(MultiValueMap<String, @Nullable String> values) {
|
||||
delegate.addAll(values);
|
||||
}
|
||||
|
||||
@ -77,12 +77,12 @@ public class HttpHeaders implements MultiValueMap<String, String> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAll(Map<String, String> values) {
|
||||
public void setAll(Map<String, @Nullable String> values) {
|
||||
delegate.setAll(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> toSingleValueMap() {
|
||||
public Map<String, @Nullable String> toSingleValueMap() {
|
||||
return delegate.toSingleValueMap();
|
||||
}
|
||||
// endregion
|
||||
@ -110,18 +110,18 @@ public class HttpHeaders implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public List<String> get(Object key) {
|
||||
public List<@Nullable String> get(Object key) {
|
||||
return delegate.get(key);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> put(String key, List<String> value) {
|
||||
public List<@Nullable String> put(String key, List<@Nullable String> value) {
|
||||
return delegate.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> remove(Object key) {
|
||||
public List<@Nullable String> remove(Object key) {
|
||||
return delegate.remove(key);
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,17 @@ public final class VersionInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a String to use in a header, containing the versions of Spring Data Elasticsearch and the Elasticsearch
|
||||
* library
|
||||
* @since 6.0
|
||||
*/
|
||||
public static String clientVersions() {
|
||||
return String.format("spring-data-elasticsearch %s / elasticsearch client %s",
|
||||
versionProperties.getProperty(VERSION_SPRING_DATA_ELASTICSEARCH),
|
||||
versionProperties.getProperty(VERSION_ELASTICSEARCH_CLIENT));
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the version properties from the classpath resource.
|
||||
*
|
||||
|
@ -24,4 +24,4 @@ import org.springframework.data.repository.kotlin.CoroutineSortingRepository
|
||||
* @since 5.2
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
interface CoroutineElasticsearchRepository<T, ID> : CoroutineCrudRepository<T, ID>, CoroutineSortingRepository<T, ID>
|
||||
interface CoroutineElasticsearchRepository<T : Any, ID : Any> : CoroutineCrudRepository<T, ID>, CoroutineSortingRepository<T, ID>
|
||||
|
@ -1,4 +1,4 @@
|
||||
Spring Data Elasticsearch 6.0 M2 (2025.1.0)
|
||||
Spring Data Elasticsearch 6.0 M4 (2025.1.0)
|
||||
Copyright (c) [2013-2022] Pivotal Software, Inc.
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||
@ -9,3 +9,5 @@ separate copyright notices and license terms. Your use of the source
|
||||
code for the these subcomponents is subject to the terms and
|
||||
conditions of the subcomponent's license, as noted in the LICENSE file.
|
||||
|
||||
|
||||
|
||||
|
@ -1,21 +1,22 @@
|
||||
/*
|
||||
* Copyright 2019-2025 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.
|
||||
*/
|
||||
* Copyright 2019-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import static io.specto.hoverfly.junit.dsl.HoverflyDsl.*;
|
||||
import static io.specto.hoverfly.junit.verification.HoverflyVerifications.*;
|
||||
@ -40,6 +41,8 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients;
|
||||
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest5_client.Rest5Clients;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest_client.RestClients;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
@ -100,8 +103,11 @@ public class RestClientsTest {
|
||||
defaultHeaders.add("def2", "def2-1");
|
||||
|
||||
AtomicInteger supplierCount = new AtomicInteger(1);
|
||||
AtomicInteger httpClientConfigurerCount = new AtomicInteger(0);
|
||||
AtomicInteger restClientConfigurerCount = new AtomicInteger(0);
|
||||
AtomicInteger httpClientConfigurerCount = new AtomicInteger(0);
|
||||
AtomicInteger connectionConfigurerCount = new AtomicInteger(0);
|
||||
AtomicInteger connectionManagerConfigurerCount = new AtomicInteger(0);
|
||||
AtomicInteger requestConfigurerCount = new AtomicInteger(0);
|
||||
|
||||
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
|
||||
configurationBuilder //
|
||||
@ -115,25 +121,52 @@ public class RestClientsTest {
|
||||
return httpHeaders;
|
||||
});
|
||||
|
||||
if (clientUnderTestFactory instanceof ELCUnderTestFactory) {
|
||||
if (clientUnderTestFactory instanceof ELCRest5ClientUnderTestFactory) {
|
||||
configurationBuilder.withClientConfigurer(
|
||||
ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpClientBuilder -> {
|
||||
Rest5Clients.ElasticsearchRest5ClientConfigurationCallback.from(rest5ClientBuilder -> {
|
||||
restClientConfigurerCount.incrementAndGet();
|
||||
return rest5ClientBuilder;
|
||||
}));
|
||||
configurationBuilder.withClientConfigurer(
|
||||
Rest5Clients.ElasticsearchHttpClientConfigurationCallback.from(httpClientBuilder -> {
|
||||
httpClientConfigurerCount.incrementAndGet();
|
||||
return httpClientBuilder;
|
||||
}));
|
||||
configurationBuilder.withClientConfigurer(
|
||||
ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||
Rest5Clients.ElasticsearchConnectionConfigurationCallback.from(connectionConfigBuilder -> {
|
||||
connectionConfigurerCount.incrementAndGet();
|
||||
return connectionConfigBuilder;
|
||||
}));
|
||||
configurationBuilder.withClientConfigurer(
|
||||
Rest5Clients.ElasticsearchConnectionManagerCallback.from(connectionManagerBuilder -> {
|
||||
connectionManagerConfigurerCount.incrementAndGet();
|
||||
return connectionManagerBuilder;
|
||||
}));
|
||||
configurationBuilder.withClientConfigurer(
|
||||
Rest5Clients.ElasticsearchRequestConfigCallback.from(requestConfigBuilder -> {
|
||||
requestConfigurerCount.incrementAndGet();
|
||||
return requestConfigBuilder;
|
||||
}));
|
||||
} else if (clientUnderTestFactory instanceof ELCRestClientUnderTestFactory) {
|
||||
configurationBuilder.withClientConfigurer(
|
||||
RestClients.ElasticsearchHttpClientConfigurationCallback.from(httpClientBuilder -> {
|
||||
httpClientConfigurerCount.incrementAndGet();
|
||||
return httpClientBuilder;
|
||||
}));
|
||||
configurationBuilder.withClientConfigurer(
|
||||
RestClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||
restClientConfigurerCount.incrementAndGet();
|
||||
return restClientBuilder;
|
||||
}));
|
||||
|
||||
} else if (clientUnderTestFactory instanceof ReactiveELCUnderTestFactory) {
|
||||
configurationBuilder
|
||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(webClient -> {
|
||||
.withClientConfigurer(RestClients.ElasticsearchHttpClientConfigurationCallback.from(webClient -> {
|
||||
httpClientConfigurerCount.incrementAndGet();
|
||||
return webClient;
|
||||
}));
|
||||
configurationBuilder.withClientConfigurer(
|
||||
ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||
RestClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||
restClientConfigurerCount.incrementAndGet();
|
||||
return restClientBuilder;
|
||||
}));
|
||||
@ -155,19 +188,24 @@ public class RestClientsTest {
|
||||
.withHeader("def2", new EqualToPattern("def2-1")) //
|
||||
.withHeader("supplied", new EqualToPattern("val0")) //
|
||||
// on the first call Elasticsearch does the version check and thus already increments the counter
|
||||
.withHeader("supplied", new EqualToPattern("val" + (i))) //
|
||||
);
|
||||
.withHeader("supplied", new EqualToPattern("val" + i)) //
|
||||
.withHeader("supplied", including("val0", "val" + i)));
|
||||
;
|
||||
}
|
||||
|
||||
assertThat(restClientConfigurerCount).hasValue(clientUnderTestFactory.getExpectedRestClientConfigurerCalls());
|
||||
assertThat(httpClientConfigurerCount).hasValue(1);
|
||||
assertThat(restClientConfigurerCount).hasValue(clientUnderTestFactory.getExpectedRestClientConfigCalls());
|
||||
assertThat(connectionConfigurerCount).hasValue(clientUnderTestFactory.getExpectedConnectionConfigurerCalls());
|
||||
assertThat(connectionManagerConfigurerCount)
|
||||
.hasValue(clientUnderTestFactory.getExpectedConnectionManagerConfigurerCalls());
|
||||
assertThat(requestConfigurerCount).hasValue(clientUnderTestFactory.getExpectedRequestConfigurerCalls());
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest // #2088
|
||||
@MethodSource("clientUnderTestFactorySource")
|
||||
@DisplayName("should set compatibility headers")
|
||||
void shouldSetCompatibilityHeaders(ClientUnderTestFactory clientUnderTestFactory) {
|
||||
@DisplayName("should set explicit compatibility headers")
|
||||
void shouldSetExplicitCompatibilityHeaders(ClientUnderTestFactory clientUnderTestFactory) {
|
||||
|
||||
wireMockServer(server -> {
|
||||
|
||||
@ -190,7 +228,8 @@ public class RestClientsTest {
|
||||
}
|
||||
""" //
|
||||
, 201) //
|
||||
.withHeader("Content-Type", "application/vnd.elasticsearch+json;compatible-with=7") //
|
||||
.withHeader("Content-Type",
|
||||
"application/vnd.elasticsearch+json;compatible-with=7") //
|
||||
.withHeader("X-Elastic-Product", "Elasticsearch")));
|
||||
|
||||
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
|
||||
@ -198,8 +237,8 @@ public class RestClientsTest {
|
||||
.connectedTo("localhost:" + server.port()) //
|
||||
.withHeaders(() -> {
|
||||
HttpHeaders defaultCompatibilityHeaders = new HttpHeaders();
|
||||
defaultCompatibilityHeaders.add("Accept", "application/vnd.elasticsearch+json;compatible-with=7");
|
||||
defaultCompatibilityHeaders.add("Content-Type", "application/vnd.elasticsearch+json;compatible-with=7");
|
||||
defaultCompatibilityHeaders.add("Accept", "application/vnd.elasticsearch+json; compatible-with=7");
|
||||
defaultCompatibilityHeaders.add("Content-Type", "application/vnd.elasticsearch+json; compatible-with=7");
|
||||
return defaultCompatibilityHeaders;
|
||||
});
|
||||
|
||||
@ -214,11 +253,66 @@ public class RestClientsTest {
|
||||
}
|
||||
}
|
||||
|
||||
clientUnderTest.index(new Foo("42"));
|
||||
clientUnderTest.index(new Foo("42"));
|
||||
|
||||
verify(putRequestedFor(urlMatching(urlPattern)) //
|
||||
.withHeader("Accept", new EqualToPattern("application/vnd.elasticsearch+json;compatible-with=7")) //
|
||||
.withHeader("Content-Type", new EqualToPattern("application/vnd.elasticsearch+json;compatible-with=7")) //
|
||||
.withHeader("Accept", new EqualToPattern("application/vnd.elasticsearch+json; compatible-with=7"))
|
||||
.withHeader("Content-Type", new EqualToPattern("application/vnd.elasticsearch+json; compatible-with=7")));
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("clientUnderTestFactorySource")
|
||||
@DisplayName("should set implicit compatibility headers")
|
||||
void shouldSetImplicitCompatibilityHeaders(ClientUnderTestFactory clientUnderTestFactory) {
|
||||
|
||||
wireMockServer(server -> {
|
||||
|
||||
String urlPattern = "^/index/_doc/42(\\?.*)?$";
|
||||
var elasticsearchMajorVersion = clientUnderTestFactory.getElasticsearchMajorVersion();
|
||||
stubFor(put(urlMatching(urlPattern)) //
|
||||
.willReturn(jsonResponse("""
|
||||
{
|
||||
"_id": "42",
|
||||
"_index": "test",
|
||||
"_primary_term": 1,
|
||||
"_seq_no": 0,
|
||||
"_shards": {
|
||||
"failed": 0,
|
||||
"successful": 1,
|
||||
"total": 2
|
||||
},
|
||||
"_type": "_doc",
|
||||
"_version": 1,
|
||||
"result": "created"
|
||||
}
|
||||
""" //
|
||||
, 201) //
|
||||
.withHeader("Content-Type",
|
||||
"application/vnd.elasticsearch+json; compatible-with=" + elasticsearchMajorVersion) //
|
||||
.withHeader("X-Elastic-Product", "Elasticsearch")));
|
||||
|
||||
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
|
||||
configurationBuilder.connectedTo("localhost:" + server.port());
|
||||
|
||||
ClientConfiguration clientConfiguration = configurationBuilder.build();
|
||||
ClientUnderTest clientUnderTest = clientUnderTestFactory.create(clientConfiguration);
|
||||
|
||||
class Foo {
|
||||
public final String id;
|
||||
|
||||
Foo(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
clientUnderTest.index(new Foo("42"));
|
||||
|
||||
verify(putRequestedFor(urlMatching(urlPattern)) //
|
||||
.withHeader("Accept",
|
||||
new EqualToPattern("application/vnd.elasticsearch+json; compatible-with=" + elasticsearchMajorVersion)) //
|
||||
.withHeader("Content-Type",
|
||||
new EqualToPattern("application/vnd.elasticsearch+json; compatible-with=" + elasticsearchMajorVersion)) //
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -280,6 +374,7 @@ public class RestClientsTest {
|
||||
private void wireMockServer(WiremockConsumer consumer) {
|
||||
WireMockServer wireMockServer = new WireMockServer(options() //
|
||||
.dynamicPort() //
|
||||
// .notifier(new ConsoleNotifier(true)) // for debugging output
|
||||
.usingFilesUnderDirectory("src/test/resources/wiremock-mappings")); // needed, otherwise Wiremock goes to
|
||||
// test/resources/mappings
|
||||
try {
|
||||
@ -295,7 +390,7 @@ public class RestClientsTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* The client to be tested. Abstraction to be able to test reactive and non-reactive clients.
|
||||
* The client to be tested. Abstraction to be able to test reactive and non-reactive clients in different versions.
|
||||
*/
|
||||
interface ClientUnderTest {
|
||||
/**
|
||||
@ -329,26 +424,61 @@ public class RestClientsTest {
|
||||
|
||||
protected abstract String getDisplayName();
|
||||
|
||||
protected Integer getExpectedRestClientConfigCalls() {
|
||||
protected Integer getExpectedRestClientConfigurerCalls() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected abstract int getElasticsearchMajorVersion();
|
||||
|
||||
public Integer getExpectedConnectionConfigurerCalls() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public Integer getExpectedConnectionManagerConfigurerCalls() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public Integer getExpectedRequestConfigurerCalls() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ClientUnderTestFactory} implementation for the {@link co.elastic.clients.elasticsearch.ElasticsearchClient}.
|
||||
* {@link ClientUnderTestFactory} implementation for the {@link co.elastic.clients.elasticsearch.ElasticsearchClient}
|
||||
* using the Rest5_Client.
|
||||
*/
|
||||
static class ELCUnderTestFactory extends ClientUnderTestFactory {
|
||||
static class ELCRest5ClientUnderTestFactory extends ClientUnderTestFactory {
|
||||
|
||||
@Override
|
||||
protected String getDisplayName() {
|
||||
return "ElasticsearchClient";
|
||||
return "ElasticsearchRest5Client";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer getExpectedRestClientConfigCalls() {
|
||||
protected Integer getExpectedRestClientConfigurerCalls() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getExpectedConnectionConfigurerCalls() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getExpectedConnectionManagerConfigurerCalls() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getExpectedRequestConfigurerCalls() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getElasticsearchMajorVersion() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
||||
|
||||
@ -372,6 +502,51 @@ public class RestClientsTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ClientUnderTestFactory} implementation for the {@link co.elastic.clients.elasticsearch.ElasticsearchClient}
|
||||
* using the old Rest_Client.
|
||||
*/
|
||||
static class ELCRestClientUnderTestFactory extends ClientUnderTestFactory {
|
||||
|
||||
@Override
|
||||
protected String getDisplayName() {
|
||||
return "ElasticsearchRestClient";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer getExpectedRestClientConfigurerCalls() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getElasticsearchMajorVersion() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
||||
|
||||
var restClient = RestClients.getRestClient(clientConfiguration);
|
||||
ElasticsearchClient client = ElasticsearchClients.createImperative(restClient);
|
||||
return new ClientUnderTest() {
|
||||
@Override
|
||||
public boolean ping() throws Exception {
|
||||
return client.ping().value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usesInitialRequest() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void index(T entity) throws IOException {
|
||||
client.index(ir -> ir.index("index").id("42").document(entity));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ClientUnderTestFactory} implementation for the {@link ReactiveElasticsearchClient}.
|
||||
*/
|
||||
@ -383,10 +558,15 @@ public class RestClientsTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer getExpectedRestClientConfigCalls() {
|
||||
protected Integer getExpectedRestClientConfigurerCalls() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getElasticsearchMajorVersion() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
||||
|
||||
@ -416,8 +596,9 @@ public class RestClientsTest {
|
||||
* @return stream of factories
|
||||
*/
|
||||
static Stream<ClientUnderTestFactory> clientUnderTestFactorySource() {
|
||||
return Stream.of( //
|
||||
new ELCUnderTestFactory(), //
|
||||
return Stream.of(
|
||||
new ELCRestClientUnderTestFactory(),
|
||||
new ELCRest5ClientUnderTestFactory(),
|
||||
new ReactiveELCUnderTestFactory());
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,9 @@ import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest5_client.Rest5Clients;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest_client.RestClients;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -85,7 +88,7 @@ public class DevTests {
|
||||
private final ReactiveElasticsearchClient reactiveElasticsearchClient = ElasticsearchClients
|
||||
.createReactive(clientConfiguration(), transportOptions);
|
||||
private final ElasticsearchClient imperativeElasticsearchClient = ElasticsearchClients
|
||||
.createImperative(ElasticsearchClients.getRestClient(clientConfiguration()), transportOptions, jsonpMapper);
|
||||
.createImperative(Rest5Clients.getRest5Client(clientConfiguration()), transportOptions, jsonpMapper);
|
||||
|
||||
@Test
|
||||
void someTest() throws IOException {
|
||||
|
@ -23,6 +23,7 @@ import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.assertj.core.data.Offset;
|
||||
@ -144,17 +145,17 @@ class DocumentAdaptersUnitTests {
|
||||
Hit<EntityAsMap> searchHit = new Hit.Builder<EntityAsMap>() //
|
||||
.index("index") //
|
||||
.id("42") //
|
||||
.matchedQueries("query1", "query2") //
|
||||
.matchedQueries("query1", 1D) //
|
||||
.build();
|
||||
|
||||
SearchDocument searchDocument = DocumentAdapters.from(searchHit, jsonpMapper);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
List<String> matchedQueries = searchDocument.getMatchedQueries();
|
||||
Map<String, Double> matchedQueries = searchDocument.getMatchedQueries();
|
||||
softly.assertThat(matchedQueries).isNotNull();
|
||||
softly.assertThat(matchedQueries).hasSize(2);
|
||||
softly.assertThat(matchedQueries).isEqualTo(Arrays.asList("query1", "query2"));
|
||||
softly.assertThat(matchedQueries).hasSize(1);
|
||||
softly.assertThat(matchedQueries).isEqualTo(Map.of("query1",1D));
|
||||
softly.assertAll();
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
package org.springframework.data.elasticsearch.client.elc.rest5_client;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*;
|
||||
@ -29,6 +29,7 @@ import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@ -41,7 +42,7 @@ import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
|
||||
*/
|
||||
@SuppressWarnings("UastIncorrectHttpHeaderInspection")
|
||||
@ExtendWith(SpringExtension.class)
|
||||
public class ELCWiremockTests {
|
||||
public class ELCRest5ClientWiremockTests {
|
||||
|
||||
@RegisterExtension static WireMockExtension wireMock = WireMockExtension.newInstance()
|
||||
.options(wireMockConfig()
|
||||
@ -69,7 +70,7 @@ public class ELCWiremockTests {
|
||||
wireMock.stubFor(put(urlPathEqualTo("/null-fields/_doc/42"))
|
||||
.withRequestBody(equalToJson("""
|
||||
{
|
||||
"_class": "org.springframework.data.elasticsearch.client.elc.ELCWiremockTests$EntityWithNullFields",
|
||||
"_class": "org.springframework.data.elasticsearch.client.elc.rest5_client.ELCRest5ClientWiremockTests$EntityWithNullFields",
|
||||
"id": "42",
|
||||
"field1": null
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2024-2025 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.rest_client;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchLegacyRestClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
|
||||
|
||||
/**
|
||||
* Tests that need to check the data produced by the Elasticsearch client
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SuppressWarnings("UastIncorrectHttpHeaderInspection")
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
public class ELCRestClientWiremockTests {
|
||||
|
||||
@RegisterExtension static WireMockExtension wireMock = WireMockExtension.newInstance()
|
||||
.options(wireMockConfig()
|
||||
.dynamicPort()
|
||||
// needed, otherwise Wiremock goes to test/resources/mappings
|
||||
.usingFilesUnderDirectory("src/test/resources/wiremock-mappings"))
|
||||
.build();
|
||||
|
||||
@Configuration
|
||||
static class Config extends ElasticsearchLegacyRestClientConfiguration {
|
||||
@Override
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder()
|
||||
.connectedTo("localhost:" + wireMock.getPort())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired ElasticsearchOperations operations;
|
||||
|
||||
@Test // #2839
|
||||
@DisplayName("should store null values if configured")
|
||||
void shouldStoreNullValuesIfConfigured() {
|
||||
|
||||
wireMock.stubFor(put(urlPathEqualTo("/null-fields/_doc/42"))
|
||||
.withRequestBody(equalToJson("""
|
||||
{
|
||||
"_class": "org.springframework.data.elasticsearch.client.elc.rest_client.ELCRestClientWiremockTests$EntityWithNullFields",
|
||||
"id": "42",
|
||||
"field1": null
|
||||
}
|
||||
"""))
|
||||
.willReturn(
|
||||
aResponse()
|
||||
.withStatus(200)
|
||||
.withHeader("X-elastic-product", "Elasticsearch")
|
||||
.withHeader("content-type", "application/vnd.elasticsearch+json;compatible-with=8")
|
||||
.withBody("""
|
||||
{
|
||||
"_index": "null-fields",
|
||||
"_id": "42",
|
||||
"_version": 1,
|
||||
"result": "created",
|
||||
"forced_refresh": true,
|
||||
"_shards": {
|
||||
"total": 2,
|
||||
"successful": 1,
|
||||
"failed": 0
|
||||
},
|
||||
"_seq_no": 1,
|
||||
"_primary_term": 1
|
||||
}
|
||||
""")));
|
||||
|
||||
var entity = new EntityWithNullFields();
|
||||
entity.setId("42");
|
||||
|
||||
operations.save(entity);
|
||||
// no need to assert anything, if the field1:null is not sent, we run into a 404 error
|
||||
}
|
||||
|
||||
@Document(indexName = "null-fields")
|
||||
static class EntityWithNullFields {
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
@Nullable
|
||||
@Field(storeNullValue = true) private String field1;
|
||||
@Nullable
|
||||
@Field private String field2;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getField1() {
|
||||
return field1;
|
||||
}
|
||||
|
||||
public void setField1(@Nullable String field1) {
|
||||
this.field1 = field1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getField2() {
|
||||
return field2;
|
||||
}
|
||||
|
||||
public void setField2(@Nullable String field2) {
|
||||
this.field2 = field2;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
@Deprecated(since = "6.0", forRemoval=true)
|
||||
package org.springframework.data.elasticsearch.client.elc.rest_client;
|
@ -19,7 +19,8 @@ import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@ -50,7 +51,7 @@ public class ElasticsearchConfigurationELCTests {
|
||||
considerNestedRepositories = true)
|
||||
static class Config extends ElasticsearchConfiguration {
|
||||
@Override
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
public @NonNull ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() //
|
||||
.connectedTo("localhost:9200") //
|
||||
.build();
|
||||
@ -61,7 +62,7 @@ public class ElasticsearchConfigurationELCTests {
|
||||
* using a repository with an entity that is set to createIndex = false as we have no elastic running for this test
|
||||
* and just check that all the necessary beans are created.
|
||||
*/
|
||||
@Autowired private RestClient restClient;
|
||||
@Autowired private Rest5Client rest5Client;
|
||||
@Autowired private ElasticsearchClient elasticsearchClient;
|
||||
@Autowired private ElasticsearchOperations elasticsearchOperations;
|
||||
|
||||
@ -69,7 +70,7 @@ public class ElasticsearchConfigurationELCTests {
|
||||
|
||||
@Test
|
||||
public void providesRequiredBeans() {
|
||||
assertThat(restClient).isNotNull();
|
||||
assertThat(rest5Client).isNotNull();
|
||||
assertThat(elasticsearchClient).isNotNull();
|
||||
assertThat(elasticsearchOperations).isNotNull();
|
||||
assertThat(repository).isNotNull();
|
||||
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2021-2025 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.config.configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchLegacyRestClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
/**
|
||||
* Tests for {@link ElasticsearchConfiguration}.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
public class ElasticsearchLegacyRestClientConfigurationELCTests {
|
||||
|
||||
@Configuration
|
||||
@EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.config.configuration" },
|
||||
considerNestedRepositories = true)
|
||||
static class Config extends ElasticsearchLegacyRestClientConfiguration {
|
||||
@Override
|
||||
public @NonNull ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() //
|
||||
.connectedTo("localhost:9200") //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* using a repository with an entity that is set to createIndex = false as we have no elastic running for this test
|
||||
* and just check that all the necessary beans are created.
|
||||
*/
|
||||
@Autowired private RestClient restClient;
|
||||
@Autowired private ElasticsearchClient elasticsearchClient;
|
||||
@Autowired private ElasticsearchOperations elasticsearchOperations;
|
||||
|
||||
@Autowired private CreateIndexFalseRepository repository;
|
||||
|
||||
@Test
|
||||
public void providesRequiredBeans() {
|
||||
assertThat(restClient).isNotNull();
|
||||
assertThat(elasticsearchClient).isNotNull();
|
||||
assertThat(elasticsearchOperations).isNotNull();
|
||||
assertThat(repository).isNotNull();
|
||||
}
|
||||
|
||||
@Document(indexName = "test-index-config-configuration", createIndex = false)
|
||||
static class CreateIndexFalseEntity {
|
||||
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
}
|
||||
|
||||
interface CreateIndexFalseRepository extends ElasticsearchRepository<CreateIndexFalseEntity, String> {}
|
||||
}
|
@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.config.configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@ -49,7 +50,7 @@ public class ReactiveElasticsearchConfigurationELCTests {
|
||||
static class Config extends ReactiveElasticsearchConfiguration {
|
||||
|
||||
@Override
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
public @NonNull ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() //
|
||||
.connectedTo("localhost:9200") //
|
||||
.build();
|
||||
@ -66,7 +67,6 @@ public class ReactiveElasticsearchConfigurationELCTests {
|
||||
|
||||
@Test
|
||||
public void providesRequiredBeans() {
|
||||
// assertThat(webClient).isNotNull();
|
||||
assertThat(reactiveElasticsearchClient).isNotNull();
|
||||
assertThat(reactiveElasticsearchOperations).isNotNull();
|
||||
assertThat(repository).isNotNull();
|
||||
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2019-2025 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.config.configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
|
||||
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchLegacyRestClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
public class ReactiveElasticsearchLegacyRestClientConfigurationELCTests {
|
||||
|
||||
@Configuration
|
||||
@EnableReactiveElasticsearchRepositories(
|
||||
basePackages = { "org.springframework.data.elasticsearch.config.configuration" },
|
||||
considerNestedRepositories = true)
|
||||
static class Config extends ReactiveElasticsearchLegacyRestClientConfiguration {
|
||||
|
||||
@Override
|
||||
public @NonNull ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() //
|
||||
.connectedTo("localhost:9200") //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* using a repository with an entity that is set to createIndex = false as we have no elastic running for this test
|
||||
* and just check that all the necessary beans are created.
|
||||
*/
|
||||
@Autowired private ReactiveElasticsearchClient reactiveElasticsearchClient;
|
||||
@Autowired private ReactiveElasticsearchOperations reactiveElasticsearchOperations;
|
||||
@Autowired private CreateIndexFalseRepository repository;
|
||||
|
||||
@Test
|
||||
public void providesRequiredBeans() {
|
||||
// assertThat(webClient).isNotNull();
|
||||
assertThat(reactiveElasticsearchClient).isNotNull();
|
||||
assertThat(reactiveElasticsearchOperations).isNotNull();
|
||||
assertThat(repository).isNotNull();
|
||||
}
|
||||
|
||||
@Document(indexName = "test-index-config-configuration", createIndex = false)
|
||||
static class CreateIndexFalseEntity {
|
||||
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
}
|
||||
|
||||
interface CreateIndexFalseRepository extends ReactiveElasticsearchRepository<CreateIndexFalseEntity, String> {}
|
||||
}
|
@ -71,10 +71,10 @@ public abstract class EnableNestedRepositoriesIntegrationTests {
|
||||
@Field(type = Text, store = true, fielddata = true) private String type;
|
||||
@Nullable
|
||||
@Field(type = Text, store = true, fielddata = true) private String message;
|
||||
@Nullable private int rate;
|
||||
private int rate;
|
||||
@Nullable
|
||||
@ScriptedField private Double scriptedRate;
|
||||
@Nullable private boolean available;
|
||||
private boolean available;
|
||||
@Nullable private String highlightedMessage;
|
||||
@Nullable private GeoPoint location;
|
||||
@Nullable
|
||||
|
@ -114,10 +114,10 @@ public abstract class EnableRepositoriesIntegrationTests implements ApplicationC
|
||||
@Field(type = Text, store = true, fielddata = true) private String type;
|
||||
@Nullable
|
||||
@Field(type = Text, store = true, fielddata = true) private String message;
|
||||
@Nullable private int rate;
|
||||
private int rate;
|
||||
@Nullable
|
||||
@ScriptedField private Double scriptedRate;
|
||||
@Nullable private boolean available;
|
||||
private boolean available;
|
||||
@Nullable private String highlightedMessage;
|
||||
@Nullable private GeoPoint location;
|
||||
@Nullable
|
||||
@ -208,10 +208,10 @@ public abstract class EnableRepositoriesIntegrationTests implements ApplicationC
|
||||
@Nullable private String type;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text, fielddata = true) private String message;
|
||||
@Nullable private int rate;
|
||||
private int rate;
|
||||
@Nullable
|
||||
@ScriptedField private Long scriptedRate;
|
||||
@Nullable private boolean available;
|
||||
private boolean available;
|
||||
@Nullable private String highlightedMessage;
|
||||
@Nullable private GeoPoint location;
|
||||
@Nullable
|
||||
|
@ -46,7 +46,6 @@ import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptData;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptType;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptedField;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
@ -183,7 +182,7 @@ public class ElasticsearchELCIntegrationTests extends ElasticsearchIntegrationTe
|
||||
|
||||
return nativeQueryBuilder.withScriptedField(new ScriptedField( //
|
||||
fieldName, //
|
||||
new ScriptData(ScriptType.INLINE, "expression", script, null, params))) //
|
||||
new ScriptData( "expression", script, null, params))) //
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -104,8 +104,6 @@ import org.springframework.data.util.StreamUtils;
|
||||
@SpringIntegrationTest
|
||||
public abstract class ElasticsearchIntegrationTests {
|
||||
|
||||
static final Integer INDEX_MAX_RESULT_WINDOW = 10_000;
|
||||
|
||||
private static final String MULTI_INDEX_PREFIX = "test-index";
|
||||
private static final String MULTI_INDEX_ALL = MULTI_INDEX_PREFIX + "*";
|
||||
private static final String MULTI_INDEX_1_NAME = MULTI_INDEX_PREFIX + "-1";
|
||||
@ -1630,12 +1628,13 @@ public abstract class ElasticsearchIntegrationTests {
|
||||
final Query query = operations.matchAllQuery();
|
||||
|
||||
final UpdateQuery updateQuery = UpdateQuery.builder(query)
|
||||
.withScriptType(ScriptType.INLINE)
|
||||
.withScript("ctx._source['message'] = params['newMessage']").withLang("painless")
|
||||
.withParams(Collections.singletonMap("newMessage", messageAfterUpdate)).withAbortOnVersionConflict(true)
|
||||
.build();
|
||||
|
||||
operations.updateByQuery(updateQuery, IndexCoordinates.of(indexNameProvider.indexName()));
|
||||
var byQueryResponse = operations.updateByQuery(updateQuery, IndexCoordinates.of(indexNameProvider.indexName()));
|
||||
|
||||
assertThat(byQueryResponse.getUpdated()).isEqualTo(1);
|
||||
|
||||
SampleEntity indexedEntity = operations.get(documentId, SampleEntity.class,
|
||||
IndexCoordinates.of(indexNameProvider.indexName()));
|
||||
@ -3901,10 +3900,10 @@ public abstract class ElasticsearchIntegrationTests {
|
||||
@Field(type = Text, store = true, fielddata = true) private String type;
|
||||
@Nullable
|
||||
@Field(type = Text, store = true, fielddata = true) private String message;
|
||||
@Nullable private int rate;
|
||||
private int rate;
|
||||
@Nullable
|
||||
@ScriptedField private Double scriptedRate;
|
||||
@Nullable private boolean available;
|
||||
private boolean available;
|
||||
@Nullable private GeoPoint location;
|
||||
@Nullable
|
||||
@Version private Long version;
|
||||
@ -3919,7 +3918,7 @@ public abstract class ElasticsearchIntegrationTests {
|
||||
@Nullable private String type;
|
||||
@Nullable private String message;
|
||||
@Nullable private Long version;
|
||||
@Nullable private int rate;
|
||||
private int rate;
|
||||
@Nullable private GeoPoint location;
|
||||
|
||||
public Builder id(String id) {
|
||||
@ -4116,10 +4115,10 @@ public abstract class ElasticsearchIntegrationTests {
|
||||
@Nullable private String type;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text, fielddata = true) private String message;
|
||||
@Nullable private int rate;
|
||||
private int rate;
|
||||
@Nullable
|
||||
@ScriptedField private Long scriptedRate;
|
||||
@Nullable private boolean available;
|
||||
private boolean available;
|
||||
@Nullable private GeoPoint location;
|
||||
@Nullable
|
||||
@Version private Long version;
|
||||
@ -4807,8 +4806,7 @@ public abstract class ElasticsearchIntegrationTests {
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-immutable-scripted")
|
||||
public static final class ImmutableWithScriptedEntity {
|
||||
@Id private final String id;
|
||||
@Field(type = Integer)
|
||||
@Nullable private final int rate;
|
||||
@Field(type = Integer) private final int rate;
|
||||
@Nullable
|
||||
@ScriptedField private final Double scriptedRate;
|
||||
|
||||
@ -4864,9 +4862,11 @@ public abstract class ElasticsearchIntegrationTests {
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-readonly-id")
|
||||
static class ReadonlyIdEntity {
|
||||
@Field(type = FieldType.Keyword) private String part1;
|
||||
@Field(type = FieldType.Keyword)
|
||||
@Nullable private String part1;
|
||||
|
||||
@Field(type = FieldType.Keyword) private String part2;
|
||||
@Field(type = FieldType.Keyword)
|
||||
@Nullable private String part2;
|
||||
|
||||
@Id
|
||||
@WriteOnlyProperty
|
||||
@ -4875,7 +4875,7 @@ public abstract class ElasticsearchIntegrationTests {
|
||||
return part1 + '-' + part2;
|
||||
}
|
||||
|
||||
public String getPart1() {
|
||||
public @Nullable String getPart1() {
|
||||
return part1;
|
||||
}
|
||||
|
||||
@ -4883,7 +4883,7 @@ public abstract class ElasticsearchIntegrationTests {
|
||||
this.part1 = part1;
|
||||
}
|
||||
|
||||
public String getPart2() {
|
||||
public @Nullable String getPart2() {
|
||||
return part2;
|
||||
}
|
||||
|
||||
@ -5007,9 +5007,11 @@ public abstract class ElasticsearchIntegrationTests {
|
||||
private static class RootEntity {
|
||||
@Id private String id;
|
||||
|
||||
@Field(type = FieldType.Object) private Child child;
|
||||
@Field(type = FieldType.Object)
|
||||
@Nullable private Child child;
|
||||
|
||||
@Field(type = FieldType.Object) private Parent parent;
|
||||
@Field(type = FieldType.Object)
|
||||
@Nullable private Parent parent;
|
||||
|
||||
@JoinTypeRelations(relations = {
|
||||
@JoinTypeRelation(parent = "parent", children = { "child" })
|
||||
@ -5031,7 +5033,7 @@ public abstract class ElasticsearchIntegrationTests {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Child getChild() {
|
||||
public @Nullable Child getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
@ -5039,7 +5041,7 @@ public abstract class ElasticsearchIntegrationTests {
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
public Parent getParent() {
|
||||
public @Nullable Parent getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
|
@ -245,7 +245,7 @@ class EntityOperationsUnitTests {
|
||||
@IndexedIndexName
|
||||
@Nullable private String indexName;
|
||||
|
||||
public String getId() {
|
||||
public @Nullable String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ public abstract class LogEntityIntegrationTests {
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
@Nullable private String action;
|
||||
@Nullable private long sequenceCode;
|
||||
private long sequenceCode;
|
||||
@Nullable
|
||||
@Field(type = Ip) private String ip;
|
||||
@Field(type = Date, format = DateFormat.date_time) private java.util.@Nullable Date date;
|
||||
@ -149,7 +149,7 @@ public abstract class LogEntityIntegrationTests {
|
||||
return format;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
public @Nullable String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@ -157,7 +157,7 @@ public abstract class LogEntityIntegrationTests {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
public @Nullable String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
@ -173,7 +173,7 @@ public abstract class LogEntityIntegrationTests {
|
||||
this.sequenceCode = sequenceCode;
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
public @Nullable String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
@ -181,7 +181,7 @@ public abstract class LogEntityIntegrationTests {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public java.util.Date getDate() {
|
||||
public java.util.@Nullable Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
|
@ -1298,7 +1298,7 @@ public abstract class ReactiveElasticsearchIntegrationTests {
|
||||
@Id private String id;
|
||||
@Nullable
|
||||
@Field(type = Text, store = true, fielddata = true) private String message;
|
||||
@Nullable private int rate;
|
||||
private int rate;
|
||||
@Nullable
|
||||
@Version private Long version;
|
||||
|
||||
@ -1568,9 +1568,9 @@ public abstract class ReactiveElasticsearchIntegrationTests {
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-readonly-id")
|
||||
static class ReadonlyIdEntity {
|
||||
@Field(type = FieldType.Keyword) private String part1;
|
||||
@Field(type = FieldType.Keyword) private @Nullable String part1;
|
||||
|
||||
@Field(type = FieldType.Keyword) private String part2;
|
||||
@Field(type = FieldType.Keyword) private @Nullable String part2;
|
||||
|
||||
@Id
|
||||
@WriteOnlyProperty
|
||||
@ -1579,7 +1579,7 @@ public abstract class ReactiveElasticsearchIntegrationTests {
|
||||
return part1 + '-' + part2;
|
||||
}
|
||||
|
||||
public String getPart1() {
|
||||
public @Nullable String getPart1() {
|
||||
return part1;
|
||||
}
|
||||
|
||||
@ -1587,7 +1587,7 @@ public abstract class ReactiveElasticsearchIntegrationTests {
|
||||
this.part1 = part1;
|
||||
}
|
||||
|
||||
public String getPart2() {
|
||||
public @Nullable String getPart2() {
|
||||
return part2;
|
||||
}
|
||||
|
||||
|
@ -164,7 +164,7 @@ public abstract class AggregationIntegrationTests {
|
||||
@Nullable
|
||||
@Field(type = Integer, store = true) private List<Integer> publishedYears = new ArrayList<>();
|
||||
|
||||
@Nullable private int score;
|
||||
private int score;
|
||||
|
||||
public ArticleEntity(@Nullable String id) {
|
||||
this.id = id;
|
||||
|
@ -25,7 +25,15 @@ import java.time.LocalTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
@ -128,7 +136,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
public void init() {
|
||||
|
||||
SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext();
|
||||
mappingContext.setInitialEntitySet(Collections.singleton(Rifle.class));
|
||||
mappingContext.setInitialEntitySet(singleton(Rifle.class));
|
||||
mappingContext.afterPropertiesSet();
|
||||
|
||||
mappingElasticsearchConverter = new MappingElasticsearchConverter(mappingContext, new GenericConversionService());
|
||||
@ -389,7 +397,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
@Test // DATAES-530
|
||||
public void readListOfConcreteTypesCorrectly() {
|
||||
|
||||
sarahAsMap.put("coWorkers", Collections.singletonList(kyleAsMap));
|
||||
sarahAsMap.put("coWorkers", singletonList(kyleAsMap));
|
||||
|
||||
Person target = mappingElasticsearchConverter.read(Person.class, sarahAsMap);
|
||||
|
||||
@ -414,7 +422,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
|
||||
Map<String, Object> target = writeToMap(sarahConnor);
|
||||
assertThat(target.get("shippingAddresses")).isInstanceOf(Map.class);
|
||||
assertThat(target.get("shippingAddresses")).isEqualTo(Collections.singletonMap("home", gratiotAveAsMap));
|
||||
assertThat(target.get("shippingAddresses")).isEqualTo(singletonMap("home", gratiotAveAsMap));
|
||||
}
|
||||
|
||||
@Test // DATAES-530
|
||||
@ -433,7 +441,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
@Test // DATAES-530
|
||||
public void readConcreteMapCorrectly() {
|
||||
|
||||
sarahAsMap.put("shippingAddresses", Collections.singletonMap("home", gratiotAveAsMap));
|
||||
sarahAsMap.put("shippingAddresses", singletonMap("home", gratiotAveAsMap));
|
||||
|
||||
Person target = mappingElasticsearchConverter.read(Person.class, sarahAsMap);
|
||||
|
||||
@ -443,7 +451,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
@Test // DATAES-530
|
||||
public void readInterfaceMapCorrectly() {
|
||||
|
||||
sarahAsMap.put("inventoryMap", Collections.singletonMap("glock19", gunAsMap));
|
||||
sarahAsMap.put("inventoryMap", singletonMap("glock19", gunAsMap));
|
||||
|
||||
Person target = mappingElasticsearchConverter.read(Person.class, sarahAsMap);
|
||||
|
||||
@ -490,7 +498,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
public void readGenericListList() {
|
||||
|
||||
Document source = Document.create();
|
||||
source.put("objectList", Collections.singletonList(Arrays.asList(t800AsMap, gunAsMap)));
|
||||
source.put("objectList", singletonList(Arrays.asList(t800AsMap, gunAsMap)));
|
||||
|
||||
Skynet target = mappingElasticsearchConverter.read(Skynet.class, source);
|
||||
|
||||
@ -515,7 +523,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
public void readGenericMap() {
|
||||
|
||||
Document source = Document.create();
|
||||
source.put("objectMap", Collections.singletonMap("glock19", gunAsMap));
|
||||
source.put("objectMap", singletonMap("glock19", gunAsMap));
|
||||
|
||||
Skynet target = mappingElasticsearchConverter.read(Skynet.class, source);
|
||||
|
||||
@ -527,23 +535,23 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
|
||||
Skynet skynet = new Skynet();
|
||||
skynet.objectMap = new LinkedHashMap<>();
|
||||
skynet.objectMap.put("inventory", Collections.singletonMap("glock19", gun));
|
||||
skynet.objectMap.put("inventory", singletonMap("glock19", gun));
|
||||
|
||||
Map<String, Object> target = writeToMap(skynet);
|
||||
|
||||
assertThat((Map<String, Object>) target.get("objectMap")).containsEntry("inventory",
|
||||
Collections.singletonMap("glock19", gunAsMap));
|
||||
singletonMap("glock19", gunAsMap));
|
||||
}
|
||||
|
||||
@Test // DATAES-530
|
||||
public void readGenericMapMap() {
|
||||
|
||||
Document source = Document.create();
|
||||
source.put("objectMap", Collections.singletonMap("inventory", Collections.singletonMap("glock19", gunAsMap)));
|
||||
source.put("objectMap", singletonMap("inventory", singletonMap("glock19", gunAsMap)));
|
||||
|
||||
Skynet target = mappingElasticsearchConverter.read(Skynet.class, source);
|
||||
|
||||
assertThat(target.getObjectMap()).containsEntry("inventory", Collections.singletonMap("glock19", gun));
|
||||
assertThat(target.getObjectMap()).containsEntry("inventory", singletonMap("glock19", gun));
|
||||
}
|
||||
|
||||
@Test // DATAES-530
|
||||
@ -575,7 +583,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
@Test // DATAES-530
|
||||
public void writesNestedAliased() {
|
||||
|
||||
t800.inventoryList = Collections.singletonList(rifle);
|
||||
t800.inventoryList = singletonList(rifle);
|
||||
Map<String, Object> target = writeToMap(t800);
|
||||
|
||||
assertThat((List<Document>) target.get("inventoryList")).contains(rifleAsMap);
|
||||
@ -589,7 +597,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
@Test // DATAES-530
|
||||
public void readsNestedAliased() {
|
||||
|
||||
t800AsMap.put("inventoryList", Collections.singletonList(rifleAsMap));
|
||||
t800AsMap.put("inventoryList", singletonList(rifleAsMap));
|
||||
|
||||
assertThat(mappingElasticsearchConverter.read(Person.class, t800AsMap).getInventoryList()).containsExactly(rifle);
|
||||
}
|
||||
@ -911,12 +919,12 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
void shouldWriteMapContainingCollectionContainingMap() throws JSONException {
|
||||
|
||||
class EntityWithMapCollectionMap {
|
||||
Map<String, Object> map;
|
||||
Map<String, Object> map = Map.of();
|
||||
}
|
||||
class InnerEntity {
|
||||
String prop1;
|
||||
@Nullable String prop1;
|
||||
|
||||
String prop2;
|
||||
@Nullable String prop2;
|
||||
|
||||
public InnerEntity() {}
|
||||
|
||||
@ -928,8 +936,8 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
}
|
||||
|
||||
var entity = new EntityWithMapCollectionMap();
|
||||
entity.map = Collections.singletonMap("collection",
|
||||
Collections.singletonList(Collections.singletonMap("destination", new InnerEntity("prop1", "prop2"))));
|
||||
entity.map = singletonMap("collection",
|
||||
singletonList(singletonMap("destination", new InnerEntity("prop1", "prop2"))));
|
||||
|
||||
var expected = """
|
||||
{
|
||||
@ -1069,67 +1077,79 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
class RangeEntity {
|
||||
|
||||
@Id private String id;
|
||||
@Field(type = FieldType.Integer_Range) private Range<Integer> integerRange;
|
||||
@Field(type = FieldType.Float_Range) private Range<Float> floatRange;
|
||||
@Field(type = FieldType.Long_Range) private Range<Long> longRange;
|
||||
@Field(type = FieldType.Double_Range) private Range<Double> doubleRange;
|
||||
@Field(type = FieldType.Date_Range) private Range<Date> dateRange;
|
||||
@Field(type = FieldType.Date_Range, format = DateFormat.year_month_day) private Range<LocalDate> localDateRange;
|
||||
@Field(type = FieldType.Integer_Range)
|
||||
@Nullable private Range<Integer> integerRange;
|
||||
@Field(type = FieldType.Float_Range)
|
||||
@Nullable private Range<Float> floatRange;
|
||||
@Field(type = FieldType.Long_Range)
|
||||
@Nullable private Range<Long> longRange;
|
||||
@Field(type = FieldType.Double_Range)
|
||||
@Nullable private Range<Double> doubleRange;
|
||||
@Field(type = FieldType.Date_Range)
|
||||
@Nullable private Range<Date> dateRange;
|
||||
@Field(type = FieldType.Date_Range, format = DateFormat.year_month_day)
|
||||
@Nullable private Range<LocalDate> localDateRange;
|
||||
@Field(type = FieldType.Date_Range,
|
||||
format = DateFormat.hour_minute_second_millis) private Range<LocalTime> localTimeRange;
|
||||
format = DateFormat.hour_minute_second_millis)
|
||||
@Nullable private Range<LocalTime> localTimeRange;
|
||||
@Field(type = FieldType.Date_Range,
|
||||
format = DateFormat.date_hour_minute_second_millis) private Range<LocalDateTime> localDateTimeRange;
|
||||
@Field(type = FieldType.Date_Range, format = DateFormat.time) private Range<OffsetTime> offsetTimeRange;
|
||||
@Field(type = FieldType.Date_Range) private Range<ZonedDateTime> zonedDateTimeRange;
|
||||
@Field(type = FieldType.Date_Range, storeNullValue = true) private Range<ZonedDateTime> nullRange;
|
||||
format = DateFormat.date_hour_minute_second_millis)
|
||||
@Nullable private Range<LocalDateTime> localDateTimeRange;
|
||||
@Field(type = FieldType.Date_Range, format = DateFormat.time)
|
||||
@Nullable private Range<OffsetTime> offsetTimeRange;
|
||||
@Field(type = FieldType.Date_Range)
|
||||
@Nullable private Range<ZonedDateTime> zonedDateTimeRange;
|
||||
@Field(type = FieldType.Date_Range, storeNullValue = true)
|
||||
@Nullable private Range<ZonedDateTime> nullRange;
|
||||
|
||||
@Field(type = FieldType.Integer_Range) private List<Range<Integer>> integerRangeList;
|
||||
@Field(type = FieldType.Integer_Range)
|
||||
@Nullable private List<Range<Integer>> integerRangeList;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Range<Integer> getIntegerRange() {
|
||||
public @Nullable Range<Integer> getIntegerRange() {
|
||||
return integerRange;
|
||||
}
|
||||
|
||||
public Range<Float> getFloatRange() {
|
||||
public @Nullable Range<Float> getFloatRange() {
|
||||
return floatRange;
|
||||
}
|
||||
|
||||
public Range<Long> getLongRange() {
|
||||
public @Nullable Range<Long> getLongRange() {
|
||||
return longRange;
|
||||
}
|
||||
|
||||
public Range<Double> getDoubleRange() {
|
||||
public @Nullable Range<Double> getDoubleRange() {
|
||||
return doubleRange;
|
||||
}
|
||||
|
||||
public Range<Date> getDateRange() {
|
||||
public @Nullable Range<Date> getDateRange() {
|
||||
return dateRange;
|
||||
}
|
||||
|
||||
public Range<LocalDate> getLocalDateRange() {
|
||||
public @Nullable Range<LocalDate> getLocalDateRange() {
|
||||
return localDateRange;
|
||||
}
|
||||
|
||||
public Range<LocalTime> getLocalTimeRange() {
|
||||
public @Nullable Range<LocalTime> getLocalTimeRange() {
|
||||
return localTimeRange;
|
||||
}
|
||||
|
||||
public Range<LocalDateTime> getLocalDateTimeRange() {
|
||||
public @Nullable Range<LocalDateTime> getLocalDateTimeRange() {
|
||||
return localDateTimeRange;
|
||||
}
|
||||
|
||||
public Range<OffsetTime> getOffsetTimeRange() {
|
||||
public @Nullable Range<OffsetTime> getOffsetTimeRange() {
|
||||
return offsetTimeRange;
|
||||
}
|
||||
|
||||
public Range<ZonedDateTime> getZonedDateTimeRange() {
|
||||
public @Nullable Range<ZonedDateTime> getZonedDateTimeRange() {
|
||||
return zonedDateTimeRange;
|
||||
}
|
||||
|
||||
public Range<ZonedDateTime> getNullRange() {
|
||||
public @Nullable Range<ZonedDateTime> getNullRange() {
|
||||
return nullRange;
|
||||
}
|
||||
|
||||
@ -1181,7 +1201,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
this.nullRange = nullRange;
|
||||
}
|
||||
|
||||
public List<Range<Integer>> getIntegerRangeList() {
|
||||
public @Nullable List<Range<Integer>> getIntegerRangeList() {
|
||||
return integerRangeList;
|
||||
}
|
||||
|
||||
@ -2639,8 +2659,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
@Nullable private GeoPoint pointB;
|
||||
@Nullable
|
||||
@GeoPointField private String pointC;
|
||||
@Nullable
|
||||
@GeoPointField private double[] pointD;
|
||||
@GeoPointField private double @Nullable [] pointD;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
@ -2705,12 +2724,11 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
this.pointC = pointC;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public double[] getPointD() {
|
||||
public double @Nullable [] getPointD() {
|
||||
return pointD;
|
||||
}
|
||||
|
||||
public void setPointD(@Nullable double[] pointD) {
|
||||
public void setPointD(double @Nullable [] pointD) {
|
||||
this.pointD = pointD;
|
||||
}
|
||||
}
|
||||
@ -3259,7 +3277,8 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
@Id
|
||||
@Nullable private String id;
|
||||
|
||||
@Field(type = FieldType.Nested, name = "level-one") private List<Level1> level1Entries;
|
||||
@Field(type = FieldType.Nested, name = "level-one")
|
||||
@Nullable private List<Level1> level1Entries;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
@ -3270,7 +3289,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public List<Level1> getLevel1Entries() {
|
||||
public @Nullable List<Level1> getLevel1Entries() {
|
||||
return level1Entries;
|
||||
}
|
||||
|
||||
@ -3279,9 +3298,10 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
}
|
||||
|
||||
static class Level1 {
|
||||
@Field(type = FieldType.Nested, name = "level-two") private List<Level2> level2Entries;
|
||||
@Field(type = FieldType.Nested, name = "level-two")
|
||||
@Nullable private List<Level2> level2Entries;
|
||||
|
||||
public List<Level2> getLevel2Entries() {
|
||||
public @Nullable List<Level2> getLevel2Entries() {
|
||||
return level2Entries;
|
||||
}
|
||||
|
||||
@ -3291,9 +3311,10 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
}
|
||||
|
||||
static class Level2 {
|
||||
@Field(type = FieldType.Keyword, name = "key-word") private String keyWord;
|
||||
@Field(type = FieldType.Keyword, name = "key-word")
|
||||
@Nullable private String keyWord;
|
||||
|
||||
public String getKeyWord() {
|
||||
public @Nullable String getKeyWord() {
|
||||
return keyWord;
|
||||
}
|
||||
|
||||
|
@ -424,12 +424,11 @@ public abstract class GeoIntegrationTests {
|
||||
@Nullable private String name;
|
||||
@Nullable
|
||||
@GeoPointField private String locationAsString;
|
||||
@Nullable
|
||||
@GeoPointField private double[] locationAsArray;
|
||||
@GeoPointField private double @Nullable [] locationAsArray;
|
||||
@Nullable
|
||||
@GeoPointField private String locationAsGeoHash;
|
||||
|
||||
public String getId() {
|
||||
public @Nullable String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@ -437,7 +436,7 @@ public abstract class GeoIntegrationTests {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
public @Nullable String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@ -445,7 +444,7 @@ public abstract class GeoIntegrationTests {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getLocationAsString() {
|
||||
public @Nullable String getLocationAsString() {
|
||||
return locationAsString;
|
||||
}
|
||||
|
||||
@ -461,7 +460,7 @@ public abstract class GeoIntegrationTests {
|
||||
this.locationAsArray = locationAsArray;
|
||||
}
|
||||
|
||||
public String getLocationAsGeoHash() {
|
||||
public @Nullable String getLocationAsGeoHash() {
|
||||
return locationAsGeoHash;
|
||||
}
|
||||
|
||||
|
@ -724,8 +724,7 @@ public abstract class MappingBuilderIntegrationTests extends MappingContextBaseT
|
||||
static class DenseVectorEntity {
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
@Nullable
|
||||
@Field(type = Dense_Vector, dims = 3) private float[] dense_vector;
|
||||
@Field(type = Dense_Vector, dims = 3) private float @Nullable [] dense_vector;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
@ -736,12 +735,11 @@ public abstract class MappingBuilderIntegrationTests extends MappingContextBaseT
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public float[] getDense_vector() {
|
||||
public float @Nullable [] getDense_vector() {
|
||||
return dense_vector;
|
||||
}
|
||||
|
||||
public void setDense_vector(@Nullable float[] dense_vector) {
|
||||
public void setDense_vector(float @Nullable [] dense_vector) {
|
||||
this.dense_vector = dense_vector;
|
||||
}
|
||||
}
|
||||
@ -915,7 +913,8 @@ public abstract class MappingBuilderIntegrationTests extends MappingContextBaseT
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
|
||||
@Field(type = FieldType.Dense_Vector, dims = 42, knnSimilarity = KnnSimilarity.COSINE) private double[] denseVector;
|
||||
@Field(type = FieldType.Dense_Vector, dims = 42,
|
||||
knnSimilarity = KnnSimilarity.COSINE) private double @Nullable [] denseVector;
|
||||
}
|
||||
|
||||
@Mapping(aliases = {
|
||||
|
@ -1398,7 +1398,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
|
||||
@Field("mapping-property")
|
||||
@Mapping(mappingPath = "/mappings/test-field-analyzed-mappings.json") //
|
||||
@Nullable private byte[] mappingProperty;
|
||||
private byte @Nullable [] mappingProperty;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ -1820,8 +1820,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
@Nullable private GeoPoint pointB;
|
||||
@Nullable
|
||||
@GeoPointField private String pointC;
|
||||
@Nullable
|
||||
@GeoPointField private double[] pointD;
|
||||
@GeoPointField private double @Nullable [] pointD;
|
||||
|
||||
@Nullable
|
||||
@GeoShapeField private String shape1;
|
||||
@ -1892,12 +1891,11 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
this.pointC = pointC;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public double[] getPointD() {
|
||||
public double @Nullable [] getPointD() {
|
||||
return pointD;
|
||||
}
|
||||
|
||||
public void setPointD(@Nullable double[] pointD) {
|
||||
public void setPointD(double @Nullable [] pointD) {
|
||||
this.pointD = pointD;
|
||||
}
|
||||
|
||||
@ -2171,8 +2169,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
static class DenseVectorEntity {
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Dense_Vector, dims = 16) private float[] my_vector;
|
||||
@Field(type = FieldType.Dense_Vector, dims = 16) private float @Nullable [] my_vector;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
@ -2183,12 +2180,11 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public float[] getMy_vector() {
|
||||
public float @Nullable [] getMy_vector() {
|
||||
return my_vector;
|
||||
}
|
||||
|
||||
public void setMy_vector(@Nullable float[] my_vector) {
|
||||
public void setMy_vector(float @Nullable [] my_vector) {
|
||||
this.my_vector = my_vector;
|
||||
}
|
||||
}
|
||||
@ -2198,10 +2194,9 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
|
||||
@Nullable
|
||||
@Field(type = FieldType.Dense_Vector, dims = 16, elementType = FieldElementType.FLOAT,
|
||||
knnIndexOptions = @KnnIndexOptions(type = KnnAlgorithmType.HNSW, m = 16, efConstruction = 100),
|
||||
knnSimilarity = KnnSimilarity.DOT_PRODUCT) private float[] my_vector;
|
||||
knnSimilarity = KnnSimilarity.DOT_PRODUCT) private float @Nullable [] my_vector;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
@ -2212,12 +2207,11 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public float[] getMy_vector() {
|
||||
public float @Nullable [] getMy_vector() {
|
||||
return my_vector;
|
||||
}
|
||||
|
||||
public void setMy_vector(@Nullable float[] my_vector) {
|
||||
public void setMy_vector(float @Nullable [] my_vector) {
|
||||
this.my_vector = my_vector;
|
||||
}
|
||||
}
|
||||
@ -2277,7 +2271,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
static class DenseVectorMisMatchConfidenceIntervalClass {
|
||||
@Field(type = Dense_Vector, dims = 16, elementType = FieldElementType.FLOAT,
|
||||
knnIndexOptions = @KnnIndexOptions(type = KnnAlgorithmType.HNSW, m = 16, confidenceInterval = 0.95F),
|
||||
knnSimilarity = KnnSimilarity.DOT_PRODUCT) private float[] dense_vector;
|
||||
knnSimilarity = KnnSimilarity.DOT_PRODUCT) private float @Nullable [] dense_vector;
|
||||
}
|
||||
|
||||
static class DisabledMappingProperty {
|
||||
|
@ -110,10 +110,10 @@ public class MappingParametersTest extends MappingContextBaseTests {
|
||||
}
|
||||
|
||||
static class DenseVectorInvalidDimsClass {
|
||||
@Field(type = Dense_Vector, dims = 4097) private float[] dense_vector;
|
||||
@Field(type = Dense_Vector, dims = 4097) private float @Nullable [] dense_vector;
|
||||
}
|
||||
|
||||
static class DenseVectorMissingDimsClass {
|
||||
@Field(type = Dense_Vector) private float[] dense_vector;
|
||||
@Field(type = Dense_Vector) private float @Nullable [] dense_vector;
|
||||
}
|
||||
}
|
||||
|
@ -444,7 +444,8 @@ public abstract class ReactiveIndexOperationsIntegrationTests {
|
||||
})
|
||||
private static class EntityWithAliases {
|
||||
@Nullable private @Id String id;
|
||||
@Field(type = Text) private String type;
|
||||
@Field(type = Text)
|
||||
@Nullable private String type;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
@ -455,7 +456,7 @@ public abstract class ReactiveIndexOperationsIntegrationTests {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
public @Nullable String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
@ -325,7 +325,7 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
|
||||
|
||||
static class FieldNamingStrategyEntity {
|
||||
@Nullable private String withoutCustomFieldName;
|
||||
@Field(name = "CUStomFIEldnAME") private String withCustomFieldName;
|
||||
@Field(name = "CUStomFIEldnAME") @Nullable private String withCustomFieldName;
|
||||
|
||||
@Nullable
|
||||
public String getWithoutCustomFieldName() {
|
||||
@ -336,7 +336,7 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
|
||||
this.withoutCustomFieldName = withoutCustomFieldName;
|
||||
}
|
||||
|
||||
public String getWithCustomFieldName() {
|
||||
public @Nullable String getWithCustomFieldName() {
|
||||
return withCustomFieldName;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2025 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.core.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.elasticsearch.core.query.BaseQuery.*;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
class BaseQueryTests {
|
||||
|
||||
private static final String MATCH_ALL_QUERY = "{\"match_all\":{}}";
|
||||
|
||||
@Test // #3127
|
||||
@DisplayName("query with no Pageable and no maxResults requests 10 docs from 0")
|
||||
void queryWithNoPageableAndNoMaxResultsRequests10DocsFrom0() {
|
||||
|
||||
var query = StringQuery.builder(MATCH_ALL_QUERY)
|
||||
.build();
|
||||
|
||||
var requestSize = query.getRequestSize();
|
||||
|
||||
assertThat(requestSize).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test // #3127
|
||||
@DisplayName("query with a Pageable and no MaxResults request with values from Pageable")
|
||||
void queryWithAPageableAndNoMaxResultsRequestWithValuesFromPageable() {
|
||||
var query = StringQuery.builder(MATCH_ALL_QUERY)
|
||||
.withPageable(Pageable.ofSize(42))
|
||||
.build();
|
||||
|
||||
var requestSize = query.getRequestSize();
|
||||
|
||||
assertThat(requestSize).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test // #3127
|
||||
@DisplayName("query with no Pageable and maxResults requests maxResults")
|
||||
void queryWithNoPageableAndMaxResultsRequestsMaxResults() {
|
||||
|
||||
var query = StringQuery.builder(MATCH_ALL_QUERY)
|
||||
.withMaxResults(12_345)
|
||||
.build();
|
||||
|
||||
var requestSize = query.getRequestSize();
|
||||
|
||||
assertThat(requestSize).isEqualTo(12_345);
|
||||
}
|
||||
|
||||
@Test // #3127
|
||||
@DisplayName("query with Pageable and maxResults requests with values from Pageable if Pageable is less than maxResults")
|
||||
void queryWithPageableAndMaxResultsRequestsWithValuesFromPageableIfPageableIsLessThanMaxResults() {
|
||||
|
||||
var query = StringQuery.builder(MATCH_ALL_QUERY)
|
||||
.withPageable(Pageable.ofSize(42))
|
||||
.withMaxResults(123)
|
||||
.build();
|
||||
|
||||
var requestSize = query.getRequestSize();
|
||||
|
||||
assertThat(requestSize).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test // #3127
|
||||
@DisplayName("query with Pageable and maxResults requests with values from maxResults if Pageable is more than maxResults")
|
||||
void queryWithPageableAndMaxResultsRequestsWithValuesFromMaxResultsIfPageableIsMoreThanMaxResults() {
|
||||
|
||||
var query = StringQuery.builder(MATCH_ALL_QUERY)
|
||||
.withPageable(Pageable.ofSize(420))
|
||||
.withMaxResults(123)
|
||||
.build();
|
||||
|
||||
var requestSize = query.getRequestSize();
|
||||
|
||||
assertThat(requestSize).isEqualTo(123);
|
||||
}
|
||||
|
||||
@Test // #3127
|
||||
@DisplayName("query with explicit unpaged request and no maxResults requests max request window size")
|
||||
void queryWithExplicitUnpagedRequestAndNoMaxResultsRequestsMaxRequestWindowSize() {
|
||||
|
||||
var query = StringQuery.builder(MATCH_ALL_QUERY)
|
||||
.withPageable(Pageable.unpaged())
|
||||
.build();
|
||||
|
||||
var requestSize = query.getRequestSize();
|
||||
|
||||
assertThat(requestSize).isEqualTo(INDEX_MAX_RESULT_WINDOW);
|
||||
}
|
||||
}
|
@ -715,7 +715,7 @@ public abstract class CriteriaQueryIntegrationTests {
|
||||
@Field(type = Text, store = true, fielddata = true) private String type;
|
||||
@Nullable
|
||||
@Field(type = Text, store = true, fielddata = true) private String message;
|
||||
@Nullable private int rate;
|
||||
private int rate;
|
||||
@Nullable
|
||||
@Version private Long version;
|
||||
|
||||
|
@ -48,7 +48,6 @@ import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilde
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.RuntimeField;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptData;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptType;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
@ -165,7 +164,6 @@ public abstract class ReactiveScriptedAndRuntimeFieldsIntegrationTests {
|
||||
return org.springframework.data.elasticsearch.core.query.ScriptedField.of(
|
||||
fieldName,
|
||||
ScriptData.of(b -> b
|
||||
.withType(ScriptType.INLINE)
|
||||
.withScript("doc['value'].size() > 0 ? doc['value'].value * params['factor'] : 0")
|
||||
.withParams(Map.of("factor", factor))));
|
||||
}
|
||||
|
@ -45,7 +45,6 @@ import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilde
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.RuntimeField;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptData;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptType;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
@ -314,7 +313,6 @@ public abstract class ScriptedAndRuntimeFieldsIntegrationTests {
|
||||
return org.springframework.data.elasticsearch.core.query.ScriptedField.of(
|
||||
fieldName,
|
||||
ScriptData.of(b -> b
|
||||
.withType(ScriptType.INLINE)
|
||||
.withScript("doc['value'].size() > 0 ? doc['value'].value * params['factor'] : 0")
|
||||
.withParams(Map.of("factor", factor))));
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ import java.util.Properties;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testcontainers.elasticsearch.ElasticsearchContainer;
|
||||
@ -41,7 +40,7 @@ import org.testcontainers.utility.DockerImageName;
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public class ClusterConnection implements ExtensionContext.Store.CloseableResource {
|
||||
public class ClusterConnection implements AutoCloseable {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(ClusterConnection.class);
|
||||
|
||||
@ -106,7 +105,7 @@ public class ClusterConnection implements ExtensionContext.Store.CloseableResour
|
||||
LOGGER.warn("DATAES_ELASTICSEARCH_PORT does not contain a number");
|
||||
}
|
||||
|
||||
return ClusterConnectionInfo.builder().withIntegrationtestEnvironment(IntegrationtestEnvironment.get())
|
||||
return ClusterConnectionInfo.builder(IntegrationtestEnvironment.get())
|
||||
.withHostAndPort(host, port).build();
|
||||
}
|
||||
|
||||
@ -138,11 +137,10 @@ public class ClusterConnection implements ExtensionContext.Store.CloseableResour
|
||||
.withEnv(testcontainersProperties).withStartupTimeout(Duration.ofMinutes(2)).withReuse(true);
|
||||
elasticsearchContainer.start();
|
||||
|
||||
return ClusterConnectionInfo.builder() //
|
||||
.withIntegrationtestEnvironment(integrationtestEnvironment)
|
||||
return ClusterConnectionInfo.builder(integrationtestEnvironment)
|
||||
.withHostAndPort(elasticsearchContainer.getHost(),
|
||||
elasticsearchContainer.getMappedPort(ELASTICSEARCH_DEFAULT_PORT)) //
|
||||
.withElasticsearchContainer(elasticsearchContainer) //
|
||||
elasticsearchContainer.getMappedPort(ELASTICSEARCH_DEFAULT_PORT))
|
||||
.withElasticsearchContainer(elasticsearchContainer)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Could not start Elasticsearch container", e);
|
||||
|
@ -24,7 +24,7 @@ import org.testcontainers.elasticsearch.ElasticsearchContainer;
|
||||
* The {@link #host}, {@link #httpPort} and {@link #useSsl} values specify the values needed to connect to the cluster
|
||||
* with a rest client for both a local started cluster and for one defined by the cluster URL when creating the
|
||||
* {@link ClusterConnection}.<br/>
|
||||
* The object must be created by using a {@link ClusterConnectionInfo.Builder}.
|
||||
* The object must be created by using a {@link Builder}.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@ -36,8 +36,8 @@ public final class ClusterConnectionInfo {
|
||||
private final String clusterName;
|
||||
@Nullable private final ElasticsearchContainer elasticsearchContainer;
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
public static Builder builder(IntegrationtestEnvironment integrationtestEnvironment) {
|
||||
return new Builder(integrationtestEnvironment);
|
||||
}
|
||||
|
||||
private ClusterConnectionInfo(IntegrationtestEnvironment integrationtestEnvironment, String host, int httpPort,
|
||||
@ -84,13 +84,12 @@ public final class ClusterConnectionInfo {
|
||||
public static class Builder {
|
||||
private IntegrationtestEnvironment integrationtestEnvironment;
|
||||
private boolean useSsl = false;
|
||||
private String host;
|
||||
private String host = "";
|
||||
private int httpPort;
|
||||
@Nullable private ElasticsearchContainer elasticsearchContainer;
|
||||
|
||||
public Builder withIntegrationtestEnvironment(IntegrationtestEnvironment integrationtestEnvironment) {
|
||||
public Builder(IntegrationtestEnvironment integrationtestEnvironment) {
|
||||
this.integrationtestEnvironment = integrationtestEnvironment;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withHostAndPort(String host, int httpPort) {
|
||||
|
@ -176,7 +176,7 @@ public class CdiRepositoryTests {
|
||||
@Nullable
|
||||
@Field(type = FieldType.Float) private Float price;
|
||||
@Nullable private Integer popularity;
|
||||
@Nullable private boolean available;
|
||||
private boolean available;
|
||||
@Nullable private String location;
|
||||
@Nullable private Date lastModified;
|
||||
|
||||
@ -293,11 +293,12 @@ public class CdiRepositoryTests {
|
||||
|
||||
@Id private String id;
|
||||
|
||||
private String name;
|
||||
@Nullable private String name;
|
||||
|
||||
@Field(type = FieldType.Nested) private List<Car> car;
|
||||
@Field(type = FieldType.Nested)
|
||||
@Nullable private List<Car> car;
|
||||
|
||||
@Field(type = FieldType.Nested, includeInParent = true) private List<Book> books;
|
||||
@Field(type = FieldType.Nested, includeInParent = true) private @Nullable List<Book> books;
|
||||
|
||||
}
|
||||
|
||||
|
@ -2443,8 +2443,8 @@ public abstract class CustomMethodRepositoryIntegrationTests {
|
||||
@Field(type = Text, store = true, fielddata = true) private String message;
|
||||
@Nullable
|
||||
@Field(type = Keyword) private String keyword;
|
||||
@Nullable private int rate;
|
||||
@Nullable private boolean available;
|
||||
private int rate;
|
||||
private boolean available;
|
||||
@Nullable private GeoPoint location;
|
||||
@Nullable
|
||||
@Version private Long version;
|
||||
|
@ -116,8 +116,7 @@ public abstract class GeoRepositoryIntegrationTests {
|
||||
@Nullable private GeoPoint pointB;
|
||||
@Nullable
|
||||
@GeoPointField private String pointC;
|
||||
@Nullable
|
||||
@GeoPointField private double[] pointD;
|
||||
@GeoPointField private double @Nullable [] pointD;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
@ -182,12 +181,11 @@ public abstract class GeoRepositoryIntegrationTests {
|
||||
this.pointC = pointC;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public double[] getPointD() {
|
||||
public double @Nullable [] getPointD() {
|
||||
return pointD;
|
||||
}
|
||||
|
||||
public void setPointD(@Nullable double[] pointD) {
|
||||
public void setPointD(double @Nullable [] pointD) {
|
||||
this.pointD = pointD;
|
||||
}
|
||||
}
|
||||
|
@ -165,12 +165,11 @@ public abstract class KnnSearchIntegrationTests {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public float[] getVector() {
|
||||
public float @Nullable [] getVector() {
|
||||
return vector;
|
||||
}
|
||||
|
||||
public void setVector(@Nullable float[] vector) {
|
||||
public void setVector(float @Nullable [] vector) {
|
||||
this.vector = vector;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user