mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-29 07:12:26 +00:00
Use the new Rest5Client as default, provide the old RestClient as optional.
Original Pull Request: #3125 Closes #3117 Signed-off-by: Peter-Josef Meisch <pj.meisch@sothawo.com>
This commit is contained in:
parent
6e49980c7c
commit
6324b72707
2
pom.xml
2
pom.xml
@ -132,6 +132,7 @@
|
|||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- the old RestCLient is an optional dependency for user that still want to use it-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.elasticsearch.client</groupId>
|
<groupId>org.elasticsearch.client</groupId>
|
||||||
<artifactId>elasticsearch-rest-client</artifactId>
|
<artifactId>elasticsearch-rest-client</artifactId>
|
||||||
@ -142,6 +143,7 @@
|
|||||||
<artifactId>commons-logging</artifactId>
|
<artifactId>commons-logging</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -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.
|
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].
|
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]]
|
[[elasticsearch.clients.rest5client]]
|
||||||
== Imperative Rest Client
|
== 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]
|
[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]
|
<.> 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:
|
The following beans can then be injected in other Spring components:
|
||||||
@ -46,7 +46,81 @@ ElasticsearchOperations operations; <.>
|
|||||||
ElasticsearchClient elasticsearchClient; <.>
|
ElasticsearchClient elasticsearchClient; <.>
|
||||||
|
|
||||||
@Autowired
|
@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
|
@Autowired
|
||||||
JsonpMapper jsonpMapper; <.>
|
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.
|
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.
|
When using repositories, this instance is used under the hood as well.
|
||||||
|
|
||||||
[[elasticsearch.clients.reactiverestclient]]
|
[[elasticsearch.clients.reactiverest5client]]
|
||||||
== Reactive Rest Client
|
== Reactive Rest5Client
|
||||||
|
|
||||||
When working with the reactive stack, the configuration must be derived from a different class:
|
When working with the reactive stack, the configuration must be derived from a different class:
|
||||||
|
|
||||||
@ -99,6 +173,65 @@ ReactiveElasticsearchOperations operations; <.>
|
|||||||
@Autowired
|
@Autowired
|
||||||
ReactiveElasticsearchClient elasticsearchClient; <.>
|
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
|
@Autowired
|
||||||
RestClient restClient; <.>
|
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:
|
The following callbacks are provided:
|
||||||
|
|
||||||
[[elasticsearch.clients.configuration.callbacks.rest]]
|
[[elasticsearch.clients.configuration.callbacks.rest5]]
|
||||||
==== Configuration of the low level Elasticsearch `RestClient`:
|
==== Configuration of the low level Elasticsearch `Rest5Client`:
|
||||||
|
|
||||||
This callback provides a `org.elasticsearch.client.RestClientBuilder` that can be used to configure the Elasticsearch
|
This callback provides a `org.elasticsearch.client.RestClientBuilder` that can be used to configure the Elasticsearch
|
||||||
`RestClient`:
|
`RestClient`:
|
||||||
@ -193,7 +326,24 @@ This callback provides a `org.elasticsearch.client.RestClientBuilder` that can b
|
|||||||
----
|
----
|
||||||
ClientConfiguration.builder()
|
ClientConfiguration.builder()
|
||||||
.connectedTo("localhost:9200", "localhost:9291")
|
.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
|
// configure the Elasticsearch RestClient
|
||||||
return restClientBuilder;
|
return restClientBuilder;
|
||||||
}))
|
}))
|
||||||
@ -201,10 +351,29 @@ ClientConfiguration.builder()
|
|||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
[[elasticsearch.clients.configurationcallbacks.httpasync]]
|
[[elasticsearch.clients.configurationcallbacks.httpasync5]]
|
||||||
==== Configuration of the HttpAsyncClient used by the low level Elasticsearch `RestClient`:
|
==== 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`.
|
used by the `RestClient`.
|
||||||
|
|
||||||
====
|
====
|
||||||
@ -212,7 +381,7 @@ used by the `RestClient`.
|
|||||||
----
|
----
|
||||||
ClientConfiguration.builder()
|
ClientConfiguration.builder()
|
||||||
.connectedTo("localhost:9200", "localhost:9291")
|
.connectedTo("localhost:9200", "localhost:9291")
|
||||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
|
.withClientConfigurer(RestClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
|
||||||
// configure the HttpAsyncClient
|
// configure the HttpAsyncClient
|
||||||
return httpAsyncClientBuilder;
|
return httpAsyncClientBuilder;
|
||||||
}))
|
}))
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
* Upgarde to Spring 7
|
* Upgarde to Spring 7
|
||||||
* Switch to jspecify nullability annotations
|
* Switch to jspecify nullability annotations
|
||||||
* Upgrade to Elasticsearch 9.0.3
|
* Upgrade to Elasticsearch 9.0.3
|
||||||
|
* Use the new Elasticsearch Rest5Client as default
|
||||||
|
|
||||||
|
|
||||||
[[new-features.5-5-0]]
|
[[new-features.5-5-0]]
|
||||||
|
@ -6,9 +6,13 @@ This section describes breaking changes from version 5.5.x to 6.0.x and how remo
|
|||||||
[[elasticsearch-migration-guide-5.5-6.0.breaking-changes]]
|
[[elasticsearch-migration-guide-5.5-6.0.breaking-changes]]
|
||||||
== 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.
|
||||||
|
|
||||||
[[elasticsearch-migration-guide-5.5-6.0.deprecations]]
|
[[elasticsearch-migration-guide-5.5-6.0.deprecations]]
|
||||||
== 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
|
=== Removals
|
||||||
|
|
||||||
|
@ -127,10 +127,16 @@ public interface ClientConfiguration {
|
|||||||
Optional<String> getCaFingerprint();
|
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.
|
* @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();
|
Optional<HostnameVerifier> getHostNameVerifier();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,44 +15,38 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.client.elc;
|
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.elasticsearch.ElasticsearchClient;
|
||||||
import co.elastic.clients.json.JsonpMapper;
|
import co.elastic.clients.json.JsonpMapper;
|
||||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||||
import co.elastic.clients.transport.TransportOptions;
|
import co.elastic.clients.transport.TransportOptions;
|
||||||
import co.elastic.clients.transport.TransportUtils;
|
|
||||||
import co.elastic.clients.transport.Version;
|
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.RestClientOptions;
|
||||||
import co.elastic.clients.transport.rest_client.RestClientTransport;
|
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.Consumer;
|
||||||
import java.util.function.Function;
|
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.entity.ContentType;
|
||||||
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
|
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
|
||||||
import org.apache.http.message.BasicHeader;
|
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
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.RestClient;
|
||||||
import org.elasticsearch.client.RestClientBuilder;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
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;
|
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
|
* @author Peter-Josef Meisch
|
||||||
* @since 4.4
|
* @since 4.4
|
||||||
@ -119,18 +113,32 @@ public final class ElasticsearchClients {
|
|||||||
*
|
*
|
||||||
* @param restClient the underlying {@link RestClient}
|
* @param restClient the underlying {@link RestClient}
|
||||||
* @return the {@link ReactiveElasticsearchClient}
|
* @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) {
|
public static ReactiveElasticsearchClient createReactive(RestClient restClient) {
|
||||||
return createReactive(restClient, null, DEFAULT_JSONP_MAPPER);
|
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}.
|
* Creates a new {@link ReactiveElasticsearchClient}.
|
||||||
*
|
*
|
||||||
* @param restClient the underlying {@link RestClient}
|
* @param restClient the underlying {@link RestClient}
|
||||||
* @param transportOptions options to be added to each request.
|
* @param transportOptions options to be added to each request.
|
||||||
* @return the {@link ReactiveElasticsearchClient}
|
* @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,
|
public static ReactiveElasticsearchClient createReactive(RestClient restClient,
|
||||||
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
|
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
|
||||||
|
|
||||||
@ -139,6 +147,21 @@ public final class ElasticsearchClients {
|
|||||||
var transport = getElasticsearchTransport(restClient, REACTIVE_CLIENT, transportOptions, jsonpMapper);
|
var transport = getElasticsearchTransport(restClient, REACTIVE_CLIENT, transportOptions, jsonpMapper);
|
||||||
return createReactive(transport);
|
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}.
|
* Creates a new {@link ReactiveElasticsearchClient} that uses the given {@link ElasticsearchTransport}.
|
||||||
@ -156,17 +179,21 @@ public final class ElasticsearchClients {
|
|||||||
|
|
||||||
// region imperative client
|
// 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}.
|
* @param clientConfiguration configuration options, must not be {@literal null}.
|
||||||
* @return the {@link ElasticsearchClient}
|
* @return the {@link ElasticsearchClient}
|
||||||
*/
|
*/
|
||||||
public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration) {
|
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 clientConfiguration configuration options, must not be {@literal null}.
|
||||||
* @param transportOptions options to be added to each request.
|
* @param transportOptions options to be added to each request.
|
||||||
@ -174,7 +201,7 @@ public final class ElasticsearchClients {
|
|||||||
*/
|
*/
|
||||||
public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration,
|
public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration,
|
||||||
TransportOptions transportOptions) {
|
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
|
* @param restClient the RestClient to use
|
||||||
* @return the {@link ElasticsearchClient}
|
* @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) {
|
public static ElasticsearchClient createImperative(RestClient restClient) {
|
||||||
return createImperative(restClient, null, DEFAULT_JSONP_MAPPER);
|
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}
|
* Creates a new imperative {@link ElasticsearchClient}
|
||||||
*
|
*
|
||||||
@ -194,7 +233,9 @@ public final class ElasticsearchClients {
|
|||||||
* @param transportOptions options to be added to each request.
|
* @param transportOptions options to be added to each request.
|
||||||
* @param jsonpMapper the mapper for the transport to use
|
* @param jsonpMapper the mapper for the transport to use
|
||||||
* @return the {@link ElasticsearchClient}
|
* @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,
|
public static ElasticsearchClient createImperative(RestClient restClient, @Nullable TransportOptions transportOptions,
|
||||||
JsonpMapper jsonpMapper) {
|
JsonpMapper jsonpMapper) {
|
||||||
|
|
||||||
@ -206,6 +247,27 @@ public final class ElasticsearchClients {
|
|||||||
return createImperative(transport);
|
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}.
|
* Creates a new {@link ElasticsearchClient} that uses the given {@link ElasticsearchTransport}.
|
||||||
*
|
*
|
||||||
@ -220,96 +282,6 @@ public final class ElasticsearchClients {
|
|||||||
}
|
}
|
||||||
// endregion
|
// 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
|
// region Elasticsearch transport
|
||||||
/**
|
/**
|
||||||
* Creates an {@link ElasticsearchTransport} that will use the given client that additionally is customized with a
|
* 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 transportOptions options for the transport
|
||||||
* @param jsonpMapper mapper for the transport
|
* @param jsonpMapper mapper for the transport
|
||||||
* @return ElasticsearchTransport
|
* @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,
|
public static ElasticsearchTransport getElasticsearchTransport(RestClient restClient, String clientType,
|
||||||
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
|
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
|
||||||
|
|
||||||
@ -329,7 +303,7 @@ public final class ElasticsearchClients {
|
|||||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||||
|
|
||||||
TransportOptions.Builder transportOptionsBuilder = transportOptions != null ? transportOptions.toBuilder()
|
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);
|
RestClientOptions.Builder restClientOptionsBuilder = getRestClientOptionsBuilder(transportOptions);
|
||||||
|
|
||||||
@ -353,70 +327,35 @@ public final class ElasticsearchClients {
|
|||||||
|
|
||||||
return new RestClientTransport(restClient, jsonpMapper, restClientOptionsBuilder.build());
|
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
|
// endregion
|
||||||
|
|
||||||
private static List<String> formattedHosts(List<InetSocketAddress> hosts, boolean useSsl) {
|
// todo #3117 remove and document that ElasticsearchHttpClientConfigurationCallback has been move to RestClients.
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,13 @@ import co.elastic.clients.json.JsonpMapper;
|
|||||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||||
import co.elastic.clients.transport.TransportOptions;
|
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.context.annotation.Bean;
|
||||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
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.config.ElasticsearchConfigurationSupport;
|
||||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
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
|
* 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
|
* 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
|
* @author Peter-Josef Meisch
|
||||||
* @since 4.4
|
* @since 4.4
|
||||||
@ -60,27 +63,27 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
|
|||||||
* @return RestClient
|
* @return RestClient
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
|
public Rest5Client elasticsearchRest5Client(ClientConfiguration clientConfiguration) {
|
||||||
|
|
||||||
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
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.
|
* the {@link JsonpMapper} bean provided in this class.
|
||||||
*
|
*
|
||||||
* @return the {@link ElasticsearchTransport}
|
* @return the {@link ElasticsearchTransport}
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
*/
|
*/
|
||||||
@Bean
|
@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");
|
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||||
|
|
||||||
return ElasticsearchClients.getElasticsearchTransport(restClient, ElasticsearchClients.IMPERATIVE_CLIENT,
|
return ElasticsearchClients.getElasticsearchTransport(rest5Client, ElasticsearchClients.IMPERATIVE_CLIENT,
|
||||||
transportOptions(), jsonpMapper);
|
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
|
* @return the {@link JsonpMapper} to use
|
||||||
* @since 5.2
|
* @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}
|
* @return the options that should be added to every request. Must not be {@literal null}
|
||||||
*/
|
*/
|
||||||
public TransportOptions transportOptions() {
|
public TransportOptions transportOptions() {
|
||||||
return new RestClientOptions(RequestOptions.DEFAULT, false);
|
return new Rest5ClientOptions(RequestOptions.DEFAULT, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,14 +119,19 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
|||||||
String message = null;
|
String message = null;
|
||||||
|
|
||||||
if (exception instanceof ResponseException responseException) {
|
if (exception instanceof ResponseException responseException) {
|
||||||
|
// this code is for the old RestClient
|
||||||
status = responseException.getResponse().getStatusLine().getStatusCode();
|
status = responseException.getResponse().getStatusLine().getStatusCode();
|
||||||
message = responseException.getMessage();
|
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) {
|
} else if (exception.getCause() != null) {
|
||||||
checkForConflictException(exception.getCause());
|
checkForConflictException(exception.getCause());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status != null && message != null) {
|
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")) {
|
if (message.contains("version conflict, required seqNo")) {
|
||||||
throw new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict",
|
throw new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict",
|
||||||
exception);
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -19,12 +19,18 @@ import co.elastic.clients.json.JsonpMapper;
|
|||||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||||
import co.elastic.clients.transport.TransportOptions;
|
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 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.elasticsearch.client.RestClient;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
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.config.ElasticsearchConfigurationSupport;
|
||||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
@ -55,11 +61,11 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
|
|||||||
* @return RestClient
|
* @return RestClient
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
|
public Rest5Client elasticsearchRestClient(ClientConfiguration clientConfiguration) {
|
||||||
|
|
||||||
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
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
|
* @since 5.2
|
||||||
*/
|
*/
|
||||||
@Bean
|
@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");
|
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||||
|
|
||||||
return ElasticsearchClients.getElasticsearchTransport(restClient, ElasticsearchClients.REACTIVE_CLIENT,
|
return ElasticsearchClients.getElasticsearchTransport(rest5Client, ElasticsearchClients.REACTIVE_CLIENT,
|
||||||
transportOptions(), jsonpMapper);
|
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.
|
* exposes it as a bean.
|
||||||
*
|
*
|
||||||
* @return the {@link JsonpMapper} to use
|
* @return the {@link JsonpMapper} to use
|
||||||
@ -118,13 +124,19 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public JsonpMapper jsonpMapper() {
|
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}
|
* @return the options that should be added to every request. Must not be {@literal null}
|
||||||
*/
|
*/
|
||||||
public TransportOptions transportOptions() {
|
public TransportOptions transportOptions() {
|
||||||
return new RestClientOptions(RequestOptions.DEFAULT, false).toBuilder().build();
|
return new Rest5ClientOptions(RequestOptions.DEFAULT, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,274 @@
|
|||||||
|
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.Locale;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
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.DefaultAuthenticationStrategy;
|
||||||
|
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
|
||||||
|
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
|
||||||
|
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
|
||||||
|
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.data.elasticsearch.support.VersionInfo;
|
||||||
|
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_CONNECT_TIMEOUT_MILLIS = 1000;
|
||||||
|
public static final int DEFAULT_SOCKET_TIMEOUT_MILLIS = 30000;
|
||||||
|
public static final int DEFAULT_RESPONSE_TIMEOUT_MILLIS = 0; // meaning infinite
|
||||||
|
public static final int DEFAULT_MAX_CONN_PER_ROUTE = 10;
|
||||||
|
public static final int DEFAULT_MAX_CONN_TOTAL = 30;
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to provide our own HttpClient, as the Rest5ClientBuilder
|
||||||
|
// does not provide a callback for configuration the http client as the old RestClientBuilder.
|
||||||
|
var httpClient = createHttpClient(clientConfiguration);
|
||||||
|
builder.setHttpClient(httpClient);
|
||||||
|
|
||||||
|
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurationCallback : clientConfiguration
|
||||||
|
.getClientConfigurers()) {
|
||||||
|
if (clientConfigurationCallback instanceof ElasticsearchRest5ClientConfigurationCallback 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(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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the basic logic to create the http client is copied from the Rest5ClientBuilder class, this is taken from the
|
||||||
|
// Elasticsearch code, as there is no public usable instance in that
|
||||||
|
private static CloseableHttpAsyncClient createHttpClient(ClientConfiguration clientConfiguration) {
|
||||||
|
|
||||||
|
var requestConfigBuilder = RequestConfig.custom();
|
||||||
|
var connectionConfigBuilder = ConnectionConfig.custom();
|
||||||
|
|
||||||
|
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||||
|
|
||||||
|
if (!connectTimeout.isNegative()) {
|
||||||
|
connectionConfigBuilder.setConnectTimeout(
|
||||||
|
Timeout.of(Math.toIntExact(connectTimeout.toMillis()), TimeUnit.MILLISECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
Duration socketTimeout = clientConfiguration.getSocketTimeout();
|
||||||
|
|
||||||
|
if (!socketTimeout.isNegative()) {
|
||||||
|
var soTimeout = Timeout.of(Math.toIntExact(socketTimeout.toMillis()), TimeUnit.MILLISECONDS);
|
||||||
|
connectionConfigBuilder.setSocketTimeout(soTimeout);
|
||||||
|
requestConfigBuilder.setConnectionRequestTimeout(soTimeout);
|
||||||
|
} else {
|
||||||
|
connectionConfigBuilder.setSocketTimeout(Timeout.of(DEFAULT_SOCKET_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
|
||||||
|
requestConfigBuilder
|
||||||
|
.setConnectionRequestTimeout(Timeout.of(DEFAULT_RESPONSE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SSLContext sslContext = clientConfiguration.getCaFingerprint().isPresent()
|
||||||
|
? TransportUtils.sslContextFromCaFingerprint(clientConfiguration.getCaFingerprint().get())
|
||||||
|
: (clientConfiguration.getSslContext().isPresent()
|
||||||
|
? clientConfiguration.getSslContext().get()
|
||||||
|
: SSLContext.getDefault());
|
||||||
|
|
||||||
|
ConnectionConfig connectionConfig = connectionConfigBuilder.build();
|
||||||
|
|
||||||
|
PoolingAsyncClientConnectionManager defaultConnectionManager = PoolingAsyncClientConnectionManagerBuilder.create()
|
||||||
|
.setDefaultConnectionConfig(connectionConfig)
|
||||||
|
.setMaxConnPerRoute(DEFAULT_MAX_CONN_PER_ROUTE)
|
||||||
|
.setMaxConnTotal(DEFAULT_MAX_CONN_TOTAL)
|
||||||
|
.setTlsStrategy(new BasicClientTlsStrategy(sslContext))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
var requestConfig = requestConfigBuilder.build();
|
||||||
|
|
||||||
|
var immutableRefToHttpClientBuilder = new Object() {
|
||||||
|
HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClientBuilder.create()
|
||||||
|
.setDefaultRequestConfig(requestConfig)
|
||||||
|
.setConnectionManager(defaultConnectionManager)
|
||||||
|
.setUserAgent(VersionInfo.clientVersions())
|
||||||
|
.setTargetAuthenticationStrategy(new DefaultAuthenticationStrategy())
|
||||||
|
.setThreadFactory(new RestClientThreadFactory());
|
||||||
|
};
|
||||||
|
|
||||||
|
clientConfiguration.getProxy().ifPresent(proxy -> {
|
||||||
|
try {
|
||||||
|
var proxyRoutePlanner = new DefaultProxyRoutePlanner(HttpHost.create(proxy));
|
||||||
|
immutableRefToHttpClientBuilder.httpClientBuilder.setRoutePlanner(proxyRoutePlanner);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
immutableRefToHttpClientBuilder.httpClientBuilder.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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
|
||||||
|
.getClientConfigurers()) {
|
||||||
|
if (clientConfigurer instanceof ElasticsearchHttpClientConfigurationCallback httpClientConfigurer) {
|
||||||
|
immutableRefToHttpClientBuilder.httpClientBuilder = httpClientConfigurer.configure(immutableRefToHttpClientBuilder.httpClientBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return immutableRefToHttpClientBuilder.httpClientBuilder.build();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException("could not create the default ssl context", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copied from the Elasticsearch code as this class is not public there.
|
||||||
|
*/
|
||||||
|
private static class RestClientThreadFactory implements ThreadFactory {
|
||||||
|
private static final AtomicLong CLIENT_THREAD_POOL_ID_GENERATOR = new AtomicLong();
|
||||||
|
private final long clientThreadPoolId;
|
||||||
|
private final AtomicLong clientThreadId;
|
||||||
|
|
||||||
|
private RestClientThreadFactory() {
|
||||||
|
this.clientThreadPoolId = CLIENT_THREAD_POOL_ID_GENERATOR.getAndIncrement();
|
||||||
|
this.clientThreadId = new AtomicLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread newThread(Runnable runnable) {
|
||||||
|
return new Thread(runnable, String.format(Locale.ROOT, "elasticsearch-rest-client-%d-thread-%d",
|
||||||
|
this.clientThreadPoolId, this.clientThreadId.incrementAndGet()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
* 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 old (up to Elasticsearch 9) RestClient.
|
||||||
|
*/
|
||||||
|
@Deprecated(since = "6.0", forRemoval=true)
|
||||||
|
@org.jspecify.annotations.NullMarked
|
||||||
|
package org.springframework.data.elasticsearch.client.elc.rest_client;
|
@ -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.
|
* gets the version properties from the classpath resource.
|
||||||
*
|
*
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019-2025 the original author or authors.
|
* Copyright 2019-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.client;
|
package org.springframework.data.elasticsearch.client;
|
||||||
|
|
||||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
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 com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||||
import static io.specto.hoverfly.junit.dsl.HoverflyDsl.*;
|
import static io.specto.hoverfly.junit.dsl.HoverflyDsl.*;
|
||||||
import static io.specto.hoverfly.junit.verification.HoverflyVerifications.*;
|
import static io.specto.hoverfly.junit.verification.HoverflyVerifications.*;
|
||||||
@ -40,10 +41,13 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients;
|
import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients;
|
||||||
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
|
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 org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||||
|
|
||||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||||
|
import com.github.tomakehurst.wiremock.common.ConsoleNotifier;
|
||||||
import com.github.tomakehurst.wiremock.matching.AnythingPattern;
|
import com.github.tomakehurst.wiremock.matching.AnythingPattern;
|
||||||
import com.github.tomakehurst.wiremock.matching.EqualToPattern;
|
import com.github.tomakehurst.wiremock.matching.EqualToPattern;
|
||||||
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
|
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
|
||||||
@ -115,25 +119,38 @@ public class RestClientsTest {
|
|||||||
return httpHeaders;
|
return httpHeaders;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (clientUnderTestFactory instanceof ELCUnderTestFactory) {
|
if (clientUnderTestFactory instanceof ELCRest5ClientUnderTestFactory) {
|
||||||
configurationBuilder.withClientConfigurer(
|
configurationBuilder.withClientConfigurer(
|
||||||
ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpClientBuilder -> {
|
Rest5Clients.ElasticsearchHttpClientConfigurationCallback.from(httpClientBuilder -> {
|
||||||
httpClientConfigurerCount.incrementAndGet();
|
httpClientConfigurerCount.incrementAndGet();
|
||||||
return httpClientBuilder;
|
return httpClientBuilder;
|
||||||
}));
|
}));
|
||||||
configurationBuilder.withClientConfigurer(
|
configurationBuilder.withClientConfigurer(
|
||||||
ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
Rest5Clients.ElasticsearchRest5ClientConfigurationCallback.from(rest5ClientBuilder -> {
|
||||||
|
restClientConfigurerCount.incrementAndGet();
|
||||||
|
return rest5ClientBuilder;
|
||||||
|
}));
|
||||||
|
|
||||||
|
} else if (clientUnderTestFactory instanceof ELCRestClientUnderTestFactory) {
|
||||||
|
configurationBuilder.withClientConfigurer(
|
||||||
|
RestClients.ElasticsearchHttpClientConfigurationCallback.from(httpClientBuilder -> {
|
||||||
|
httpClientConfigurerCount.incrementAndGet();
|
||||||
|
return httpClientBuilder;
|
||||||
|
}));
|
||||||
|
configurationBuilder.withClientConfigurer(
|
||||||
|
RestClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||||
restClientConfigurerCount.incrementAndGet();
|
restClientConfigurerCount.incrementAndGet();
|
||||||
return restClientBuilder;
|
return restClientBuilder;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
} else if (clientUnderTestFactory instanceof ReactiveELCUnderTestFactory) {
|
} else if (clientUnderTestFactory instanceof ReactiveELCUnderTestFactory) {
|
||||||
configurationBuilder
|
configurationBuilder
|
||||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(webClient -> {
|
.withClientConfigurer(RestClients.ElasticsearchHttpClientConfigurationCallback.from(webClient -> {
|
||||||
httpClientConfigurerCount.incrementAndGet();
|
httpClientConfigurerCount.incrementAndGet();
|
||||||
return webClient;
|
return webClient;
|
||||||
}));
|
}));
|
||||||
configurationBuilder.withClientConfigurer(
|
configurationBuilder.withClientConfigurer(
|
||||||
ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
RestClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||||
restClientConfigurerCount.incrementAndGet();
|
restClientConfigurerCount.incrementAndGet();
|
||||||
return restClientBuilder;
|
return restClientBuilder;
|
||||||
}));
|
}));
|
||||||
@ -155,8 +172,9 @@ public class RestClientsTest {
|
|||||||
.withHeader("def2", new EqualToPattern("def2-1")) //
|
.withHeader("def2", new EqualToPattern("def2-1")) //
|
||||||
.withHeader("supplied", new EqualToPattern("val0")) //
|
.withHeader("supplied", new EqualToPattern("val0")) //
|
||||||
// on the first call Elasticsearch does the version check and thus already increments the counter
|
// 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(httpClientConfigurerCount).hasValue(1);
|
assertThat(httpClientConfigurerCount).hasValue(1);
|
||||||
@ -166,8 +184,8 @@ public class RestClientsTest {
|
|||||||
|
|
||||||
@ParameterizedTest // #2088
|
@ParameterizedTest // #2088
|
||||||
@MethodSource("clientUnderTestFactorySource")
|
@MethodSource("clientUnderTestFactorySource")
|
||||||
@DisplayName("should set compatibility headers")
|
@DisplayName("should set explicit compatibility headers")
|
||||||
void shouldSetCompatibilityHeaders(ClientUnderTestFactory clientUnderTestFactory) {
|
void shouldSetExplicitCompatibilityHeaders(ClientUnderTestFactory clientUnderTestFactory) {
|
||||||
|
|
||||||
wireMockServer(server -> {
|
wireMockServer(server -> {
|
||||||
|
|
||||||
@ -190,7 +208,8 @@ public class RestClientsTest {
|
|||||||
}
|
}
|
||||||
""" //
|
""" //
|
||||||
, 201) //
|
, 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")));
|
.withHeader("X-Elastic-Product", "Elasticsearch")));
|
||||||
|
|
||||||
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
|
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
|
||||||
@ -198,8 +217,8 @@ public class RestClientsTest {
|
|||||||
.connectedTo("localhost:" + server.port()) //
|
.connectedTo("localhost:" + server.port()) //
|
||||||
.withHeaders(() -> {
|
.withHeaders(() -> {
|
||||||
HttpHeaders defaultCompatibilityHeaders = new HttpHeaders();
|
HttpHeaders defaultCompatibilityHeaders = new HttpHeaders();
|
||||||
defaultCompatibilityHeaders.add("Accept", "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");
|
defaultCompatibilityHeaders.add("Content-Type", "application/vnd.elasticsearch+json; compatible-with=7");
|
||||||
return defaultCompatibilityHeaders;
|
return defaultCompatibilityHeaders;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -214,11 +233,66 @@ public class RestClientsTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clientUnderTest.index(new Foo("42"));
|
clientUnderTest.index(new Foo("42"));
|
||||||
|
|
||||||
verify(putRequestedFor(urlMatching(urlPattern)) //
|
verify(putRequestedFor(urlMatching(urlPattern)) //
|
||||||
.withHeader("Accept", 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")) //
|
.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 +354,7 @@ public class RestClientsTest {
|
|||||||
private void wireMockServer(WiremockConsumer consumer) {
|
private void wireMockServer(WiremockConsumer consumer) {
|
||||||
WireMockServer wireMockServer = new WireMockServer(options() //
|
WireMockServer wireMockServer = new WireMockServer(options() //
|
||||||
.dynamicPort() //
|
.dynamicPort() //
|
||||||
|
// .notifier(new ConsoleNotifier(true)) // for debugging output
|
||||||
.usingFilesUnderDirectory("src/test/resources/wiremock-mappings")); // needed, otherwise Wiremock goes to
|
.usingFilesUnderDirectory("src/test/resources/wiremock-mappings")); // needed, otherwise Wiremock goes to
|
||||||
// test/resources/mappings
|
// test/resources/mappings
|
||||||
try {
|
try {
|
||||||
@ -295,7 +370,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 {
|
interface ClientUnderTest {
|
||||||
/**
|
/**
|
||||||
@ -332,16 +407,19 @@ public class RestClientsTest {
|
|||||||
protected Integer getExpectedRestClientConfigCalls() {
|
protected Integer getExpectedRestClientConfigCalls() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract int getElasticsearchMajorVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@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
|
@Override
|
||||||
protected String getDisplayName() {
|
protected String getDisplayName() {
|
||||||
return "ElasticsearchClient";
|
return "ElasticsearchRest5Client";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -349,6 +427,11 @@ public class RestClientsTest {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getElasticsearchMajorVersion() {
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
||||||
|
|
||||||
@ -372,6 +455,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 getExpectedRestClientConfigCalls() {
|
||||||
|
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}.
|
* {@link ClientUnderTestFactory} implementation for the {@link ReactiveElasticsearchClient}.
|
||||||
*/
|
*/
|
||||||
@ -387,6 +515,11 @@ public class RestClientsTest {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getElasticsearchMajorVersion() {
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
||||||
|
|
||||||
@ -416,8 +549,9 @@ public class RestClientsTest {
|
|||||||
* @return stream of factories
|
* @return stream of factories
|
||||||
*/
|
*/
|
||||||
static Stream<ClientUnderTestFactory> clientUnderTestFactorySource() {
|
static Stream<ClientUnderTestFactory> clientUnderTestFactorySource() {
|
||||||
return Stream.of( //
|
return Stream.of(
|
||||||
new ELCUnderTestFactory(), //
|
new ELCRestClientUnderTestFactory(),
|
||||||
|
new ELCRest5ClientUnderTestFactory(),
|
||||||
new ReactiveELCUnderTestFactory());
|
new ReactiveELCUnderTestFactory());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,9 @@ import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
|||||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||||
import co.elastic.clients.transport.TransportOptions;
|
import co.elastic.clients.transport.TransportOptions;
|
||||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
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 reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -85,7 +88,7 @@ public class DevTests {
|
|||||||
private final ReactiveElasticsearchClient reactiveElasticsearchClient = ElasticsearchClients
|
private final ReactiveElasticsearchClient reactiveElasticsearchClient = ElasticsearchClients
|
||||||
.createReactive(clientConfiguration(), transportOptions);
|
.createReactive(clientConfiguration(), transportOptions);
|
||||||
private final ElasticsearchClient imperativeElasticsearchClient = ElasticsearchClients
|
private final ElasticsearchClient imperativeElasticsearchClient = ElasticsearchClients
|
||||||
.createImperative(ElasticsearchClients.getRestClient(clientConfiguration()), transportOptions, jsonpMapper);
|
.createImperative(Rest5Clients.getRest5Client(clientConfiguration()), transportOptions, jsonpMapper);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void someTest() throws IOException {
|
void someTest() throws IOException {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.client.WireMock.*;
|
||||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*;
|
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.Document;
|
||||||
import org.springframework.data.elasticsearch.annotations.Field;
|
import org.springframework.data.elasticsearch.annotations.Field;
|
||||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
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.data.elasticsearch.core.ElasticsearchOperations;
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("UastIncorrectHttpHeaderInspection")
|
@SuppressWarnings("UastIncorrectHttpHeaderInspection")
|
||||||
@ExtendWith(SpringExtension.class)
|
@ExtendWith(SpringExtension.class)
|
||||||
public class ELCWiremockTests {
|
public class ELCRest5ClientWiremockTests {
|
||||||
|
|
||||||
@RegisterExtension static WireMockExtension wireMock = WireMockExtension.newInstance()
|
@RegisterExtension static WireMockExtension wireMock = WireMockExtension.newInstance()
|
||||||
.options(wireMockConfig()
|
.options(wireMockConfig()
|
||||||
@ -69,7 +70,7 @@ public class ELCWiremockTests {
|
|||||||
wireMock.stubFor(put(urlPathEqualTo("/null-fields/_doc/42"))
|
wireMock.stubFor(put(urlPathEqualTo("/null-fields/_doc/42"))
|
||||||
.withRequestBody(equalToJson("""
|
.withRequestBody(equalToJson("""
|
||||||
{
|
{
|
||||||
"_class": "org.springframework.data.elasticsearch.client.elc.ELCWiremockTests$EntityWithNullFields",
|
"_class": "org.springframework.data.elasticsearch.client.elc.rest5_client.ELCRest5ClientWiremockTests$EntityWithNullFields",
|
||||||
"id": "42",
|
"id": "42",
|
||||||
"field1": null
|
"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 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.jspecify.annotations.Nullable;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
@ -50,7 +51,7 @@ public class ElasticsearchConfigurationELCTests {
|
|||||||
considerNestedRepositories = true)
|
considerNestedRepositories = true)
|
||||||
static class Config extends ElasticsearchConfiguration {
|
static class Config extends ElasticsearchConfiguration {
|
||||||
@Override
|
@Override
|
||||||
public ClientConfiguration clientConfiguration() {
|
public @NonNull ClientConfiguration clientConfiguration() {
|
||||||
return ClientConfiguration.builder() //
|
return ClientConfiguration.builder() //
|
||||||
.connectedTo("localhost:9200") //
|
.connectedTo("localhost:9200") //
|
||||||
.build();
|
.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
|
* 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.
|
* and just check that all the necessary beans are created.
|
||||||
*/
|
*/
|
||||||
@Autowired private RestClient restClient;
|
@Autowired private Rest5Client rest5Client;
|
||||||
@Autowired private ElasticsearchClient elasticsearchClient;
|
@Autowired private ElasticsearchClient elasticsearchClient;
|
||||||
@Autowired private ElasticsearchOperations elasticsearchOperations;
|
@Autowired private ElasticsearchOperations elasticsearchOperations;
|
||||||
|
|
||||||
@ -69,7 +70,7 @@ public class ElasticsearchConfigurationELCTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void providesRequiredBeans() {
|
public void providesRequiredBeans() {
|
||||||
assertThat(restClient).isNotNull();
|
assertThat(rest5Client).isNotNull();
|
||||||
assertThat(elasticsearchClient).isNotNull();
|
assertThat(elasticsearchClient).isNotNull();
|
||||||
assertThat(elasticsearchOperations).isNotNull();
|
assertThat(elasticsearchOperations).isNotNull();
|
||||||
assertThat(repository).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> {}
|
||||||
|
}
|
@ -66,7 +66,6 @@ public class ReactiveElasticsearchConfigurationELCTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void providesRequiredBeans() {
|
public void providesRequiredBeans() {
|
||||||
// assertThat(webClient).isNotNull();
|
|
||||||
assertThat(reactiveElasticsearchClient).isNotNull();
|
assertThat(reactiveElasticsearchClient).isNotNull();
|
||||||
assertThat(reactiveElasticsearchOperations).isNotNull();
|
assertThat(reactiveElasticsearchOperations).isNotNull();
|
||||||
assertThat(repository).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> {}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user