mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-29 15:22:11 +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>
|
||||
</dependency>
|
||||
|
||||
<!-- the old RestCLient is an optional dependency for user that still want to use it-->
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>elasticsearch-rest-client</artifactId>
|
||||
@ -142,6 +143,7 @@
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<optional>true</optional>
|
||||
</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.
|
||||
Although the Elasticsearch Client can be used directly to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of xref:elasticsearch/template.adoc[Elasticsearch Operations] and xref:elasticsearch/repositories/elasticsearch-repositories.adoc[Elasticsearch Repositories].
|
||||
|
||||
[[elasticsearch.clients.restclient]]
|
||||
== Imperative Rest Client
|
||||
[[elasticsearch.clients.rest5client]]
|
||||
== Imperative Rest5Client
|
||||
|
||||
To use the imperative (non-reactive) client, a configuration bean must be configured like this:
|
||||
To use the imperative (non-reactive) Rest5Client, a configuration bean must be configured like this:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
@ -31,7 +31,81 @@ public class MyClientConfig extends ElasticsearchConfiguration {
|
||||
<.> for a detailed description of the builder methods see xref:elasticsearch/clients.adoc#elasticsearch.clients.configuration[Client Configuration]
|
||||
====
|
||||
|
||||
The javadoc:org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration[]] class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
|
||||
The javadoc:org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration[] class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
|
||||
|
||||
|
||||
The following beans can then be injected in other Spring components:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.beans.factory.annotation.Autowired;@Autowired
|
||||
ElasticsearchOperations operations; <.>
|
||||
|
||||
@Autowired
|
||||
ElasticsearchClient elasticsearchClient; <.>
|
||||
|
||||
@Autowired
|
||||
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:
|
||||
@ -61,8 +135,8 @@ JsonpMapper jsonpMapper; <.>
|
||||
Basically one should just use the javadoc:org.springframework.data.elasticsearch.core.ElasticsearchOperations[] to interact with the Elasticsearch cluster.
|
||||
When using repositories, this instance is used under the hood as well.
|
||||
|
||||
[[elasticsearch.clients.reactiverestclient]]
|
||||
== Reactive Rest Client
|
||||
[[elasticsearch.clients.reactiverest5client]]
|
||||
== Reactive Rest5Client
|
||||
|
||||
When working with the reactive stack, the configuration must be derived from a different class:
|
||||
|
||||
@ -99,6 +173,65 @@ ReactiveElasticsearchOperations operations; <.>
|
||||
@Autowired
|
||||
ReactiveElasticsearchClient elasticsearchClient; <.>
|
||||
|
||||
@Autowired
|
||||
Rest5Client rest5Client; <.>
|
||||
|
||||
@Autowired
|
||||
JsonpMapper jsonpMapper; <.>
|
||||
----
|
||||
|
||||
the following can be injected:
|
||||
|
||||
<.> an implementation of javadoc:org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations[]
|
||||
<.> the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient` that is used.
|
||||
This is a reactive implementation based on the Elasticsearch client implementation.
|
||||
<.> the low level `RestClient` from the Elasticsearch libraries
|
||||
<.> the `JsonpMapper` user by the Elasticsearch `Transport`
|
||||
====
|
||||
|
||||
Basically one should just use the javadoc:org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations[] to interact with the Elasticsearch cluster.
|
||||
When using repositories, this instance is used under the hood as well.
|
||||
|
||||
[[elasticsearch.clients.reactiverestclient]]
|
||||
== Deprecated Reactive RestClient
|
||||
|
||||
See the section above for the imperative code to use the deprecated RestClient for the necessary dependencies to include.
|
||||
|
||||
When working with the reactive stack, the configuration must be derived from a different class:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchLegacyRestClientConfiguration;
|
||||
|
||||
@Configuration
|
||||
public class MyClientConfig extends ReactiveElasticsearchLegacyRestClientConfiguration {
|
||||
|
||||
@Override
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() <.>
|
||||
.connectedTo("localhost:9200")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
<.> for a detailed description of the builder methods see xref:elasticsearch/clients.adoc#elasticsearch.clients.configuration[Client Configuration]
|
||||
====
|
||||
|
||||
The javadoc:org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchConfiguration[] class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
|
||||
|
||||
The following beans can then be injected in other Spring components:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Autowired
|
||||
ReactiveElasticsearchOperations operations; <.>
|
||||
|
||||
@Autowired
|
||||
ReactiveElasticsearchClient elasticsearchClient; <.>
|
||||
|
||||
@Autowired
|
||||
RestClient restClient; <.>
|
||||
|
||||
@ -183,8 +316,8 @@ In the case this is not enough, the user can add callback functions by using the
|
||||
|
||||
The following callbacks are provided:
|
||||
|
||||
[[elasticsearch.clients.configuration.callbacks.rest]]
|
||||
==== Configuration of the low level Elasticsearch `RestClient`:
|
||||
[[elasticsearch.clients.configuration.callbacks.rest5]]
|
||||
==== Configuration of the low level Elasticsearch `Rest5Client`:
|
||||
|
||||
This callback provides a `org.elasticsearch.client.RestClientBuilder` that can be used to configure the Elasticsearch
|
||||
`RestClient`:
|
||||
@ -193,7 +326,24 @@ This callback provides a `org.elasticsearch.client.RestClientBuilder` that can b
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||
.withClientConfigurer(Rest5Clients.ElasticsearchRest5ClientConfigurationCallback.from(restClientBuilder -> {
|
||||
// configure the Elasticsearch Rest5Client
|
||||
return restClientBuilder;
|
||||
}))
|
||||
.build();
|
||||
----
|
||||
====
|
||||
[[elasticsearch.clients.configuration.callbacks.rest]]
|
||||
==== Configuration of the deprecated low level Elasticsearch `RestClient`:
|
||||
|
||||
This callback provides a `org.elasticsearch.client.RestClientBuilder` that can be used to configure the Elasticsearch
|
||||
`RestClient`:
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withClientConfigurer(RestClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||
// configure the Elasticsearch RestClient
|
||||
return restClientBuilder;
|
||||
}))
|
||||
@ -201,10 +351,29 @@ ClientConfiguration.builder()
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.configurationcallbacks.httpasync]]
|
||||
==== Configuration of the HttpAsyncClient used by the low level Elasticsearch `RestClient`:
|
||||
[[elasticsearch.clients.configurationcallbacks.httpasync5]]
|
||||
==== Configuration of the HttpAsyncClient used by the low level Elasticsearch `Rest5Client`:
|
||||
|
||||
This callback provides a `org.apache.http.impl.nio.client.HttpAsyncClientBuilder` to configure the HttpCLient that is
|
||||
This callback provides a `org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder` to configure the HttpClient that is
|
||||
used by the `Rest5Client`.
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withClientConfigurer(Rest5Clients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
|
||||
// configure the HttpAsyncClient
|
||||
return httpAsyncClientBuilder;
|
||||
}))
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.configurationcallbacks.httpasync]]
|
||||
==== Configuration of the HttpAsyncClient used by the deprecated low level Elasticsearch `RestClient`:
|
||||
|
||||
This callback provides a `org.apache.http.impl.nio.client.HttpAsyncClientBuilder` to configure the HttpClient that is
|
||||
used by the `RestClient`.
|
||||
|
||||
====
|
||||
@ -212,7 +381,7 @@ used by the `RestClient`.
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
|
||||
.withClientConfigurer(RestClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
|
||||
// configure the HttpAsyncClient
|
||||
return httpAsyncClientBuilder;
|
||||
}))
|
||||
|
@ -7,6 +7,7 @@
|
||||
* Upgarde to Spring 7
|
||||
* Switch to jspecify nullability annotations
|
||||
* Upgrade to Elasticsearch 9.0.3
|
||||
* Use the new Elasticsearch Rest5Client as default
|
||||
|
||||
|
||||
[[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]]
|
||||
== 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]]
|
||||
== 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
|
||||
|
||||
|
@ -127,10 +127,16 @@ public interface ClientConfiguration {
|
||||
Optional<String> getCaFingerprint();
|
||||
|
||||
/**
|
||||
* Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured.
|
||||
* Returns the {@link HostnameVerifier} to use. Must be {@link Optional#empty()} if not configured.
|
||||
* Cannot be used with the Rest5Client used from Elasticsearch 9 on as the underlying Apache http components 5 does not offer a way
|
||||
* to set this. Users that need a hostname verifier must integrate this in a SSLContext.
|
||||
* Returning a value here is ignored in this case
|
||||
*
|
||||
* @return the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured.
|
||||
* @deprecated since 6.0
|
||||
*/
|
||||
// todo #3117 document this
|
||||
@Deprecated(since = "6.0", forRemoval=true)
|
||||
Optional<HostnameVerifier> getHostNameVerifier();
|
||||
|
||||
/**
|
||||
|
@ -15,44 +15,38 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import static org.springframework.data.elasticsearch.client.elc.rest5_client.Rest5Clients.*;
|
||||
import static org.springframework.data.elasticsearch.client.elc.rest_client.RestClients.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.TransportUtils;
|
||||
import co.elastic.clients.transport.Version;
|
||||
import co.elastic.clients.transport.rest5_client.Rest5ClientOptions;
|
||||
import co.elastic.clients.transport.rest5_client.Rest5ClientTransport;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.RequestOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
|
||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||
import co.elastic.clients.transport.rest_client.RestClientTransport;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpRequestInterceptor;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.RestClientBuilder;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
import org.springframework.data.elasticsearch.support.VersionInfo;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility class to create the different Elasticsearch clients
|
||||
* Utility class to create the different Elasticsearch clients. The RestClient class is the one used in Elasticsearch
|
||||
* until version 9, it is still available, but it's use is deprecated. The Rest5Client class is the one that should be
|
||||
* used from Elasticsearch 9 on.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
@ -119,18 +113,32 @@ public final class ElasticsearchClients {
|
||||
*
|
||||
* @param restClient the underlying {@link RestClient}
|
||||
* @return the {@link ReactiveElasticsearchClient}
|
||||
* @deprecated since 6.0, use the version with a Rest5Client.
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static ReactiveElasticsearchClient createReactive(RestClient restClient) {
|
||||
return createReactive(restClient, null, DEFAULT_JSONP_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveElasticsearchClient}.
|
||||
*
|
||||
* @param rest5Client the underlying {@link RestClient}
|
||||
* @return the {@link ReactiveElasticsearchClient}
|
||||
*/
|
||||
public static ReactiveElasticsearchClient createReactive(Rest5Client rest5Client) {
|
||||
return createReactive(rest5Client, null, DEFAULT_JSONP_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveElasticsearchClient}.
|
||||
*
|
||||
* @param restClient the underlying {@link RestClient}
|
||||
* @param transportOptions options to be added to each request.
|
||||
* @return the {@link ReactiveElasticsearchClient}
|
||||
* @deprecated since 6.0, use the version with a Rest5Client.
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static ReactiveElasticsearchClient createReactive(RestClient restClient,
|
||||
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
|
||||
|
||||
@ -139,6 +147,21 @@ public final class ElasticsearchClients {
|
||||
var transport = getElasticsearchTransport(restClient, REACTIVE_CLIENT, transportOptions, jsonpMapper);
|
||||
return createReactive(transport);
|
||||
}
|
||||
/**
|
||||
* Creates a new {@link ReactiveElasticsearchClient}.
|
||||
*
|
||||
* @param rest5Client the underlying {@link RestClient}
|
||||
* @param transportOptions options to be added to each request.
|
||||
* @return the {@link ReactiveElasticsearchClient}
|
||||
*/
|
||||
public static ReactiveElasticsearchClient createReactive(Rest5Client rest5Client,
|
||||
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(rest5Client, "restClient must not be null");
|
||||
|
||||
var transport = getElasticsearchTransport(rest5Client, REACTIVE_CLIENT, transportOptions, jsonpMapper);
|
||||
return createReactive(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveElasticsearchClient} that uses the given {@link ElasticsearchTransport}.
|
||||
@ -156,17 +179,21 @@ public final class ElasticsearchClients {
|
||||
|
||||
// region imperative client
|
||||
/**
|
||||
* Creates a new imperative {@link ElasticsearchClient}
|
||||
* Creates a new imperative {@link ElasticsearchClient}. This uses a RestClient, if the old RestClient is needed, this
|
||||
* must be created with the {@link org.springframework.data.elasticsearch.client.elc.rest_client.RestClients} class
|
||||
* and passed in as parameter.
|
||||
*
|
||||
* @param clientConfiguration configuration options, must not be {@literal null}.
|
||||
* @return the {@link ElasticsearchClient}
|
||||
*/
|
||||
public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration) {
|
||||
return createImperative(getRestClient(clientConfiguration), null, DEFAULT_JSONP_MAPPER);
|
||||
return createImperative(getRest5Client(clientConfiguration), null, DEFAULT_JSONP_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new imperative {@link ElasticsearchClient}
|
||||
* Creates a new imperative {@link ElasticsearchClient}. This uses a RestClient, if the old RestClient is needed, this
|
||||
* must be created with the {@link org.springframework.data.elasticsearch.client.elc.rest_client.RestClients} class
|
||||
* and passed in as parameter.
|
||||
*
|
||||
* @param clientConfiguration configuration options, must not be {@literal null}.
|
||||
* @param transportOptions options to be added to each request.
|
||||
@ -174,7 +201,7 @@ public final class ElasticsearchClients {
|
||||
*/
|
||||
public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration,
|
||||
TransportOptions transportOptions) {
|
||||
return createImperative(getRestClient(clientConfiguration), transportOptions, DEFAULT_JSONP_MAPPER);
|
||||
return createImperative(getRest5Client(clientConfiguration), transportOptions, DEFAULT_JSONP_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -182,11 +209,23 @@ public final class ElasticsearchClients {
|
||||
*
|
||||
* @param restClient the RestClient to use
|
||||
* @return the {@link ElasticsearchClient}
|
||||
* @deprecated since 6.0, use the version with a Rest5Client.
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static ElasticsearchClient createImperative(RestClient restClient) {
|
||||
return createImperative(restClient, null, DEFAULT_JSONP_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new imperative {@link ElasticsearchClient}
|
||||
*
|
||||
* @param rest5Client the Rest5Client to use
|
||||
* @return the {@link ElasticsearchClient}
|
||||
*/
|
||||
public static ElasticsearchClient createImperative(Rest5Client rest5Client) {
|
||||
return createImperative(rest5Client, null, DEFAULT_JSONP_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new imperative {@link ElasticsearchClient}
|
||||
*
|
||||
@ -194,7 +233,9 @@ public final class ElasticsearchClients {
|
||||
* @param transportOptions options to be added to each request.
|
||||
* @param jsonpMapper the mapper for the transport to use
|
||||
* @return the {@link ElasticsearchClient}
|
||||
* @deprecated since 6.0, use the version with a Rest5Client.
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static ElasticsearchClient createImperative(RestClient restClient, @Nullable TransportOptions transportOptions,
|
||||
JsonpMapper jsonpMapper) {
|
||||
|
||||
@ -206,6 +247,27 @@ public final class ElasticsearchClients {
|
||||
return createImperative(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new imperative {@link ElasticsearchClient}
|
||||
*
|
||||
* @param rest5Client the Rest5Client to use
|
||||
* @param transportOptions options to be added to each request.
|
||||
* @param jsonpMapper the mapper for the transport to use
|
||||
* @return the {@link ElasticsearchClient}
|
||||
* @since 6.0
|
||||
*/
|
||||
public static ElasticsearchClient createImperative(Rest5Client rest5Client,
|
||||
@Nullable TransportOptions transportOptions,
|
||||
JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(rest5Client, "restClient must not be null");
|
||||
|
||||
ElasticsearchTransport transport = getElasticsearchTransport(rest5Client, IMPERATIVE_CLIENT, transportOptions,
|
||||
jsonpMapper);
|
||||
|
||||
return createImperative(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ElasticsearchClient} that uses the given {@link ElasticsearchTransport}.
|
||||
*
|
||||
@ -220,96 +282,6 @@ public final class ElasticsearchClients {
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region low level RestClient
|
||||
private static RestClientOptions.Builder getRestClientOptionsBuilder(@Nullable TransportOptions transportOptions) {
|
||||
|
||||
if (transportOptions instanceof RestClientOptions restClientOptions) {
|
||||
return restClientOptions.toBuilder();
|
||||
}
|
||||
|
||||
var builder = new RestClientOptions.Builder(RequestOptions.DEFAULT.toBuilder());
|
||||
|
||||
if (transportOptions != null) {
|
||||
transportOptions.headers().forEach(header -> builder.addHeader(header.getKey(), header.getValue()));
|
||||
transportOptions.queryParameters().forEach(builder::setParameter);
|
||||
builder.onWarnings(transportOptions.onWarnings());
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a low level {@link RestClient} for the given configuration.
|
||||
*
|
||||
* @param clientConfiguration must not be {@literal null}
|
||||
* @return the {@link RestClient}
|
||||
*/
|
||||
public static RestClient getRestClient(ClientConfiguration clientConfiguration) {
|
||||
return getRestClientBuilder(clientConfiguration).build();
|
||||
}
|
||||
|
||||
private static RestClientBuilder getRestClientBuilder(ClientConfiguration clientConfiguration) {
|
||||
HttpHost[] httpHosts = formattedHosts(clientConfiguration.getEndpoints(), clientConfiguration.useSsl()).stream()
|
||||
.map(HttpHost::create).toArray(HttpHost[]::new);
|
||||
RestClientBuilder builder = RestClient.builder(httpHosts);
|
||||
|
||||
if (clientConfiguration.getPathPrefix() != null) {
|
||||
builder.setPathPrefix(clientConfiguration.getPathPrefix());
|
||||
}
|
||||
|
||||
HttpHeaders headers = clientConfiguration.getDefaultHeaders();
|
||||
|
||||
if (!headers.isEmpty()) {
|
||||
builder.setDefaultHeaders(toHeaderArray(headers));
|
||||
}
|
||||
|
||||
builder.setHttpClientConfigCallback(clientBuilder -> {
|
||||
if (clientConfiguration.getCaFingerprint().isPresent()) {
|
||||
clientBuilder
|
||||
.setSSLContext(TransportUtils.sslContextFromCaFingerprint(clientConfiguration.getCaFingerprint().get()));
|
||||
}
|
||||
clientConfiguration.getSslContext().ifPresent(clientBuilder::setSSLContext);
|
||||
clientConfiguration.getHostNameVerifier().ifPresent(clientBuilder::setSSLHostnameVerifier);
|
||||
clientBuilder.addInterceptorLast(new CustomHeaderInjector(clientConfiguration.getHeadersSupplier()));
|
||||
|
||||
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
|
||||
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||
|
||||
if (!connectTimeout.isNegative()) {
|
||||
requestConfigBuilder.setConnectTimeout(Math.toIntExact(connectTimeout.toMillis()));
|
||||
}
|
||||
|
||||
Duration socketTimeout = clientConfiguration.getSocketTimeout();
|
||||
|
||||
if (!socketTimeout.isNegative()) {
|
||||
requestConfigBuilder.setSocketTimeout(Math.toIntExact(socketTimeout.toMillis()));
|
||||
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(socketTimeout.toMillis()));
|
||||
}
|
||||
|
||||
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
|
||||
|
||||
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
|
||||
|
||||
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
|
||||
.getClientConfigurers()) {
|
||||
if (clientConfigurer instanceof ElasticsearchHttpClientConfigurationCallback restClientConfigurationCallback) {
|
||||
clientBuilder = restClientConfigurationCallback.configure(clientBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
return clientBuilder;
|
||||
});
|
||||
|
||||
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurationCallback : clientConfiguration
|
||||
.getClientConfigurers()) {
|
||||
if (clientConfigurationCallback instanceof ElasticsearchRestClientConfigurationCallback configurationCallback) {
|
||||
builder = configurationCallback.configure(builder);
|
||||
}
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Elasticsearch transport
|
||||
/**
|
||||
* Creates an {@link ElasticsearchTransport} that will use the given client that additionally is customized with a
|
||||
@ -320,7 +292,9 @@ public final class ElasticsearchClients {
|
||||
* @param transportOptions options for the transport
|
||||
* @param jsonpMapper mapper for the transport
|
||||
* @return ElasticsearchTransport
|
||||
* @deprecated since 6.0, use the version taking a Rest5Client
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
public static ElasticsearchTransport getElasticsearchTransport(RestClient restClient, String clientType,
|
||||
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
|
||||
|
||||
@ -329,7 +303,7 @@ public final class ElasticsearchClients {
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
TransportOptions.Builder transportOptionsBuilder = transportOptions != null ? transportOptions.toBuilder()
|
||||
: new RestClientOptions(RequestOptions.DEFAULT, false).toBuilder();
|
||||
: new RestClientOptions(org.elasticsearch.client.RequestOptions.DEFAULT, false).toBuilder();
|
||||
|
||||
RestClientOptions.Builder restClientOptionsBuilder = getRestClientOptionsBuilder(transportOptions);
|
||||
|
||||
@ -353,70 +327,35 @@ public final class ElasticsearchClients {
|
||||
|
||||
return new RestClientTransport(restClient, jsonpMapper, restClientOptionsBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link ElasticsearchTransport} that will use the given client that additionally is customized with a
|
||||
* header to contain the clientType
|
||||
*
|
||||
* @param rest5Client the client to use
|
||||
* @param clientType the client type to pass in each request as header
|
||||
* @param transportOptions options for the transport
|
||||
* @param jsonpMapper mapper for the transport
|
||||
* @return ElasticsearchTransport
|
||||
*/
|
||||
public static ElasticsearchTransport getElasticsearchTransport(Rest5Client rest5Client, String clientType,
|
||||
@Nullable TransportOptions transportOptions, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(rest5Client, "restClient must not be null");
|
||||
Assert.notNull(clientType, "clientType must not be null");
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
TransportOptions.Builder transportOptionsBuilder = transportOptions != null ? transportOptions.toBuilder()
|
||||
: new Rest5ClientOptions(RequestOptions.DEFAULT, false).toBuilder();
|
||||
|
||||
Rest5ClientOptions.Builder rest5ClientOptionsBuilder = getRest5ClientOptionsBuilder(transportOptions);
|
||||
|
||||
rest5ClientOptionsBuilder.addHeader(X_SPRING_DATA_ELASTICSEARCH_CLIENT,
|
||||
VersionInfo.clientVersions() + " / " + clientType);
|
||||
|
||||
return new Rest5ClientTransport(rest5Client, jsonpMapper, rest5ClientOptionsBuilder.build());
|
||||
}
|
||||
// endregion
|
||||
|
||||
private static List<String> formattedHosts(List<InetSocketAddress> hosts, boolean useSsl) {
|
||||
return hosts.stream().map(it -> (useSsl ? "https" : "http") + "://" + it.getHostString() + ':' + it.getPort())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static org.apache.http.Header[] toHeaderArray(HttpHeaders headers) {
|
||||
return headers.entrySet().stream() //
|
||||
.flatMap(entry -> entry.getValue().stream() //
|
||||
.map(value -> new BasicHeader(entry.getKey(), value))) //
|
||||
.toArray(org.apache.http.Header[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interceptor to inject custom supplied headers.
|
||||
*
|
||||
* @since 4.4
|
||||
*/
|
||||
private record CustomHeaderInjector(Supplier<HttpHeaders> headersSupplier) implements HttpRequestInterceptor {
|
||||
|
||||
@Override
|
||||
public void process(HttpRequest request, HttpContext context) {
|
||||
HttpHeaders httpHeaders = headersSupplier.get();
|
||||
|
||||
if (httpHeaders != null && !httpHeaders.isEmpty()) {
|
||||
Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||
* the Elasticsearch RestClient's Http client with a {@link HttpAsyncClientBuilder}
|
||||
*
|
||||
* @since 4.4
|
||||
*/
|
||||
public interface ElasticsearchHttpClientConfigurationCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
|
||||
|
||||
static ElasticsearchHttpClientConfigurationCallback from(
|
||||
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> httpClientBuilderCallback) {
|
||||
|
||||
Assert.notNull(httpClientBuilderCallback, "httpClientBuilderCallback must not be null");
|
||||
|
||||
return httpClientBuilderCallback::apply;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||
* the RestClient client with a {@link RestClientBuilder}
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface ElasticsearchRestClientConfigurationCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<RestClientBuilder> {
|
||||
|
||||
static ElasticsearchRestClientConfigurationCallback from(
|
||||
Function<RestClientBuilder, RestClientBuilder> restClientBuilderCallback) {
|
||||
|
||||
Assert.notNull(restClientBuilderCallback, "restClientBuilderCallback must not be null");
|
||||
|
||||
return restClientBuilderCallback::apply;
|
||||
}
|
||||
}
|
||||
// todo #3117 remove and document that ElasticsearchHttpClientConfigurationCallback has been move to RestClients.
|
||||
}
|
||||
|
@ -20,12 +20,13 @@ import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||
import co.elastic.clients.transport.rest5_client.Rest5ClientOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.RequestOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
|
||||
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest5_client.Rest5Clients;
|
||||
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
@ -38,7 +39,9 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
/**
|
||||
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
|
||||
* connection using the Elasticsearch Client. This class exposes different parts of the setup as Spring beans. Deriving
|
||||
* classes must provide the {@link ClientConfiguration} to use.
|
||||
* classes must provide the {@link ClientConfiguration} to use. From Version 6.0 on, this class uses the new Rest5Client
|
||||
* from Elasticsearch 9. The old implementation using the RestClient is still available under the name
|
||||
* {@link ElasticsearchLegacyRestClientConfiguration}.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
@ -60,27 +63,27 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
|
||||
* @return RestClient
|
||||
*/
|
||||
@Bean
|
||||
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
|
||||
public Rest5Client elasticsearchRest5Client(ClientConfiguration clientConfiguration) {
|
||||
|
||||
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
||||
|
||||
return ElasticsearchClients.getRestClient(clientConfiguration);
|
||||
return Rest5Clients.getRest5Client(clientConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the Elasticsearch transport to be used. The default implementation uses the {@link RestClient} bean and
|
||||
* Provides the Elasticsearch transport to be used. The default implementation uses the {@link Rest5Client} bean and
|
||||
* the {@link JsonpMapper} bean provided in this class.
|
||||
*
|
||||
* @return the {@link ElasticsearchTransport}
|
||||
* @since 5.2
|
||||
*/
|
||||
@Bean
|
||||
public ElasticsearchTransport elasticsearchTransport(RestClient restClient, JsonpMapper jsonpMapper) {
|
||||
public ElasticsearchTransport elasticsearchTransport(Rest5Client rest5Client, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(restClient, "restClient must not be null");
|
||||
Assert.notNull(rest5Client, "restClient must not be null");
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
return ElasticsearchClients.getElasticsearchTransport(restClient, ElasticsearchClients.IMPERATIVE_CLIENT,
|
||||
return ElasticsearchClients.getElasticsearchTransport(rest5Client, ElasticsearchClients.IMPERATIVE_CLIENT,
|
||||
transportOptions(), jsonpMapper);
|
||||
}
|
||||
|
||||
@ -115,7 +118,7 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the JsonpMapper bean that is used in the {@link #elasticsearchTransport(RestClient, JsonpMapper)} method.
|
||||
* Provides the JsonpMapper bean that is used in the {@link #elasticsearchTransport(Rest5Client, JsonpMapper)} method.
|
||||
*
|
||||
* @return the {@link JsonpMapper} to use
|
||||
* @since 5.2
|
||||
@ -135,6 +138,6 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
|
||||
* @return the options that should be added to every request. Must not be {@literal null}
|
||||
*/
|
||||
public TransportOptions transportOptions() {
|
||||
return new RestClientOptions(RequestOptions.DEFAULT, false);
|
||||
return new Rest5ClientOptions(RequestOptions.DEFAULT, false);
|
||||
}
|
||||
}
|
||||
|
@ -119,14 +119,19 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
||||
String message = null;
|
||||
|
||||
if (exception instanceof ResponseException responseException) {
|
||||
// this code is for the old RestClient
|
||||
status = responseException.getResponse().getStatusLine().getStatusCode();
|
||||
message = responseException.getMessage();
|
||||
} else if (exception instanceof ElasticsearchException elasticsearchException) {
|
||||
// using the RestClient throws this
|
||||
status = elasticsearchException.status();
|
||||
message = elasticsearchException.getMessage();
|
||||
} else if (exception.getCause() != null) {
|
||||
checkForConflictException(exception.getCause());
|
||||
}
|
||||
|
||||
if (status != null && message != null) {
|
||||
if (status == 409 && message.contains("type\":\"version_conflict_engine_exception"))
|
||||
if (status == 409 && message.contains("version_conflict_engine_exception"))
|
||||
if (message.contains("version conflict, required seqNo")) {
|
||||
throw new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict",
|
||||
exception);
|
||||
|
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2021-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest_client.RestClients;
|
||||
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
|
||||
* connection using the Elasticsearch Client. This class exposes different parts of the setup as Spring beans. Deriving
|
||||
* classes must provide the {@link ClientConfiguration} to use. <br/>
|
||||
* This class uses the Elasticsearch RestClient which was replaced by the Rest5Client in Elasticsearch 9. It is still
|
||||
* available here but deprecated.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
* @deprecated since 6.0, use {@link ElasticsearchConfiguration}
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval=true)
|
||||
public abstract class ElasticsearchLegacyRestClientConfiguration extends ElasticsearchConfigurationSupport {
|
||||
|
||||
/**
|
||||
* Must be implemented by deriving classes to provide the {@link ClientConfiguration}.
|
||||
*
|
||||
* @return configuration, must not be {@literal null}
|
||||
*/
|
||||
@Bean(name = "elasticsearchClientConfiguration")
|
||||
public abstract ClientConfiguration clientConfiguration();
|
||||
|
||||
/**
|
||||
* Provides the underlying low level Elasticsearch RestClient.
|
||||
*
|
||||
* @param clientConfiguration configuration for the client, must not be {@literal null}
|
||||
* @return RestClient
|
||||
*/
|
||||
@Bean
|
||||
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
|
||||
|
||||
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
||||
|
||||
return RestClients.getRestClient(clientConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the Elasticsearch transport to be used. The default implementation uses the {@link RestClient} bean and
|
||||
* the {@link JsonpMapper} bean provided in this class.
|
||||
*
|
||||
* @return the {@link ElasticsearchTransport}
|
||||
* @since 5.2
|
||||
*/
|
||||
@Bean
|
||||
public ElasticsearchTransport elasticsearchTransport(RestClient restClient, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(restClient, "restClient must not be null");
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
return ElasticsearchClients.getElasticsearchTransport(restClient, ElasticsearchClients.IMPERATIVE_CLIENT,
|
||||
transportOptions(), jsonpMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the {@link ElasticsearchClient} to be used.
|
||||
*
|
||||
* @param transport the {@link ElasticsearchTransport} to use
|
||||
* @return ElasticsearchClient instance
|
||||
*/
|
||||
@Bean
|
||||
public ElasticsearchClient elasticsearchClient(ElasticsearchTransport transport) {
|
||||
|
||||
Assert.notNull(transport, "transport must not be null");
|
||||
|
||||
return ElasticsearchClients.createImperative(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ElasticsearchOperations} implementation using an {@link ElasticsearchClient}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
|
||||
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
|
||||
ElasticsearchClient elasticsearchClient) {
|
||||
|
||||
ElasticsearchTemplate template = new ElasticsearchTemplate(elasticsearchClient, elasticsearchConverter);
|
||||
template.setRefreshPolicy(refreshPolicy());
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the JsonpMapper bean that is used in the {@link #elasticsearchTransport(RestClient, JsonpMapper)} method.
|
||||
*
|
||||
* @return the {@link JsonpMapper} to use
|
||||
* @since 5.2
|
||||
*/
|
||||
@Bean
|
||||
public JsonpMapper jsonpMapper() {
|
||||
// we need to create our own objectMapper that keeps null values in order to provide the storeNullValue
|
||||
// functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get
|
||||
// into this mapper, so we can safely keep them here.
|
||||
var objectMapper = (new ObjectMapper())
|
||||
.configure(SerializationFeature.INDENT_OUTPUT, false)
|
||||
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
|
||||
return new JacksonJsonpMapper(objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the options that should be added to every request. Must not be {@literal null}
|
||||
*/
|
||||
public TransportOptions transportOptions() {
|
||||
return new RestClientOptions(RequestOptions.DEFAULT, false);
|
||||
}
|
||||
}
|
@ -19,12 +19,18 @@ import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.rest5_client.Rest5ClientOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.RequestOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
|
||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest5_client.Rest5Clients;
|
||||
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
@ -55,11 +61,11 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
|
||||
* @return RestClient
|
||||
*/
|
||||
@Bean
|
||||
public RestClient elasticsearchRestClient(ClientConfiguration clientConfiguration) {
|
||||
public Rest5Client elasticsearchRestClient(ClientConfiguration clientConfiguration) {
|
||||
|
||||
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
||||
|
||||
return ElasticsearchClients.getRestClient(clientConfiguration);
|
||||
return Rest5Clients.getRest5Client(clientConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,12 +76,12 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
|
||||
* @since 5.2
|
||||
*/
|
||||
@Bean
|
||||
public ElasticsearchTransport elasticsearchTransport(RestClient restClient, JsonpMapper jsonpMapper) {
|
||||
public ElasticsearchTransport elasticsearchTransport(Rest5Client rest5Client, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(restClient, "restClient must not be null");
|
||||
Assert.notNull(rest5Client, "restClient must not be null");
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
return ElasticsearchClients.getElasticsearchTransport(restClient, ElasticsearchClients.REACTIVE_CLIENT,
|
||||
return ElasticsearchClients.getElasticsearchTransport(rest5Client, ElasticsearchClients.REACTIVE_CLIENT,
|
||||
transportOptions(), jsonpMapper);
|
||||
}
|
||||
|
||||
@ -110,7 +116,7 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the JsonpMapper that is used in the {@link #elasticsearchTransport(RestClient, JsonpMapper)} method and
|
||||
* Provides the JsonpMapper that is used in the {@link #elasticsearchTransport(Rest5Client, JsonpMapper)} method and
|
||||
* exposes it as a bean.
|
||||
*
|
||||
* @return the {@link JsonpMapper} to use
|
||||
@ -118,13 +124,19 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
|
||||
*/
|
||||
@Bean
|
||||
public JsonpMapper jsonpMapper() {
|
||||
return new JacksonJsonpMapper();
|
||||
// we need to create our own objectMapper that keeps null values in order to provide the storeNullValue
|
||||
// functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get
|
||||
// into this mapper, so we can safely keep them here.
|
||||
var objectMapper = (new ObjectMapper())
|
||||
.configure(SerializationFeature.INDENT_OUTPUT, false)
|
||||
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
|
||||
return new JacksonJsonpMapper(objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the options that should be added to every request. Must not be {@literal null}
|
||||
*/
|
||||
public TransportOptions transportOptions() {
|
||||
return new RestClientOptions(RequestOptions.DEFAULT, false).toBuilder().build();
|
||||
return new Rest5ClientOptions(RequestOptions.DEFAULT, false);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -16,6 +16,7 @@
|
||||
package org.springframework.data.elasticsearch.client;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import static io.specto.hoverfly.junit.dsl.HoverflyDsl.*;
|
||||
import static io.specto.hoverfly.junit.verification.HoverflyVerifications.*;
|
||||
@ -40,10 +41,13 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients;
|
||||
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest5_client.Rest5Clients;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest_client.RestClients;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
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.EqualToPattern;
|
||||
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
|
||||
@ -115,25 +119,38 @@ public class RestClientsTest {
|
||||
return httpHeaders;
|
||||
});
|
||||
|
||||
if (clientUnderTestFactory instanceof ELCUnderTestFactory) {
|
||||
if (clientUnderTestFactory instanceof ELCRest5ClientUnderTestFactory) {
|
||||
configurationBuilder.withClientConfigurer(
|
||||
ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpClientBuilder -> {
|
||||
Rest5Clients.ElasticsearchHttpClientConfigurationCallback.from(httpClientBuilder -> {
|
||||
httpClientConfigurerCount.incrementAndGet();
|
||||
return httpClientBuilder;
|
||||
}));
|
||||
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();
|
||||
return restClientBuilder;
|
||||
}));
|
||||
|
||||
} else if (clientUnderTestFactory instanceof ReactiveELCUnderTestFactory) {
|
||||
configurationBuilder
|
||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(webClient -> {
|
||||
.withClientConfigurer(RestClients.ElasticsearchHttpClientConfigurationCallback.from(webClient -> {
|
||||
httpClientConfigurerCount.incrementAndGet();
|
||||
return webClient;
|
||||
}));
|
||||
configurationBuilder.withClientConfigurer(
|
||||
ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||
RestClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||
restClientConfigurerCount.incrementAndGet();
|
||||
return restClientBuilder;
|
||||
}));
|
||||
@ -155,8 +172,9 @@ public class RestClientsTest {
|
||||
.withHeader("def2", new EqualToPattern("def2-1")) //
|
||||
.withHeader("supplied", new EqualToPattern("val0")) //
|
||||
// on the first call Elasticsearch does the version check and thus already increments the counter
|
||||
.withHeader("supplied", new EqualToPattern("val" + (i))) //
|
||||
);
|
||||
.withHeader("supplied", new EqualToPattern("val" + i)) //
|
||||
.withHeader("supplied", including("val0", "val" + i)));
|
||||
;
|
||||
}
|
||||
|
||||
assertThat(httpClientConfigurerCount).hasValue(1);
|
||||
@ -166,8 +184,8 @@ public class RestClientsTest {
|
||||
|
||||
@ParameterizedTest // #2088
|
||||
@MethodSource("clientUnderTestFactorySource")
|
||||
@DisplayName("should set compatibility headers")
|
||||
void shouldSetCompatibilityHeaders(ClientUnderTestFactory clientUnderTestFactory) {
|
||||
@DisplayName("should set explicit compatibility headers")
|
||||
void shouldSetExplicitCompatibilityHeaders(ClientUnderTestFactory clientUnderTestFactory) {
|
||||
|
||||
wireMockServer(server -> {
|
||||
|
||||
@ -190,7 +208,8 @@ public class RestClientsTest {
|
||||
}
|
||||
""" //
|
||||
, 201) //
|
||||
.withHeader("Content-Type", "application/vnd.elasticsearch+json;compatible-with=7") //
|
||||
.withHeader("Content-Type",
|
||||
"application/vnd.elasticsearch+json;compatible-with=7") //
|
||||
.withHeader("X-Elastic-Product", "Elasticsearch")));
|
||||
|
||||
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
|
||||
@ -217,8 +236,63 @@ public class RestClientsTest {
|
||||
clientUnderTest.index(new Foo("42"));
|
||||
|
||||
verify(putRequestedFor(urlMatching(urlPattern)) //
|
||||
.withHeader("Accept", new EqualToPattern("application/vnd.elasticsearch+json;compatible-with=7")) //
|
||||
.withHeader("Content-Type", new EqualToPattern("application/vnd.elasticsearch+json;compatible-with=7")) //
|
||||
.withHeader("Accept", new EqualToPattern("application/vnd.elasticsearch+json; compatible-with=7"))
|
||||
.withHeader("Content-Type", new EqualToPattern("application/vnd.elasticsearch+json; compatible-with=7")));
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("clientUnderTestFactorySource")
|
||||
@DisplayName("should set implicit compatibility headers")
|
||||
void shouldSetImplicitCompatibilityHeaders(ClientUnderTestFactory clientUnderTestFactory) {
|
||||
|
||||
wireMockServer(server -> {
|
||||
|
||||
String urlPattern = "^/index/_doc/42(\\?.*)?$";
|
||||
var elasticsearchMajorVersion = clientUnderTestFactory.getElasticsearchMajorVersion();
|
||||
stubFor(put(urlMatching(urlPattern)) //
|
||||
.willReturn(jsonResponse("""
|
||||
{
|
||||
"_id": "42",
|
||||
"_index": "test",
|
||||
"_primary_term": 1,
|
||||
"_seq_no": 0,
|
||||
"_shards": {
|
||||
"failed": 0,
|
||||
"successful": 1,
|
||||
"total": 2
|
||||
},
|
||||
"_type": "_doc",
|
||||
"_version": 1,
|
||||
"result": "created"
|
||||
}
|
||||
""" //
|
||||
, 201) //
|
||||
.withHeader("Content-Type",
|
||||
"application/vnd.elasticsearch+json; compatible-with=" + elasticsearchMajorVersion) //
|
||||
.withHeader("X-Elastic-Product", "Elasticsearch")));
|
||||
|
||||
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
|
||||
configurationBuilder.connectedTo("localhost:" + server.port());
|
||||
|
||||
ClientConfiguration clientConfiguration = configurationBuilder.build();
|
||||
ClientUnderTest clientUnderTest = clientUnderTestFactory.create(clientConfiguration);
|
||||
|
||||
class Foo {
|
||||
public final String id;
|
||||
|
||||
Foo(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
clientUnderTest.index(new Foo("42"));
|
||||
|
||||
verify(putRequestedFor(urlMatching(urlPattern)) //
|
||||
.withHeader("Accept",
|
||||
new EqualToPattern("application/vnd.elasticsearch+json; compatible-with=" + elasticsearchMajorVersion)) //
|
||||
.withHeader("Content-Type",
|
||||
new EqualToPattern("application/vnd.elasticsearch+json; compatible-with=" + elasticsearchMajorVersion)) //
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -280,6 +354,7 @@ public class RestClientsTest {
|
||||
private void wireMockServer(WiremockConsumer consumer) {
|
||||
WireMockServer wireMockServer = new WireMockServer(options() //
|
||||
.dynamicPort() //
|
||||
// .notifier(new ConsoleNotifier(true)) // for debugging output
|
||||
.usingFilesUnderDirectory("src/test/resources/wiremock-mappings")); // needed, otherwise Wiremock goes to
|
||||
// test/resources/mappings
|
||||
try {
|
||||
@ -295,7 +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 {
|
||||
/**
|
||||
@ -332,16 +407,19 @@ public class RestClientsTest {
|
||||
protected Integer getExpectedRestClientConfigCalls() {
|
||||
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
|
||||
protected String getDisplayName() {
|
||||
return "ElasticsearchClient";
|
||||
return "ElasticsearchRest5Client";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -349,6 +427,11 @@ public class RestClientsTest {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getElasticsearchMajorVersion() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
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}.
|
||||
*/
|
||||
@ -387,6 +515,11 @@ public class RestClientsTest {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getElasticsearchMajorVersion() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
||||
|
||||
@ -416,8 +549,9 @@ public class RestClientsTest {
|
||||
* @return stream of factories
|
||||
*/
|
||||
static Stream<ClientUnderTestFactory> clientUnderTestFactorySource() {
|
||||
return Stream.of( //
|
||||
new ELCUnderTestFactory(), //
|
||||
return Stream.of(
|
||||
new ELCRestClientUnderTestFactory(),
|
||||
new ELCRest5ClientUnderTestFactory(),
|
||||
new ReactiveELCUnderTestFactory());
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,9 @@ import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest5_client.Rest5Clients;
|
||||
import org.springframework.data.elasticsearch.client.elc.rest_client.RestClients;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -85,7 +88,7 @@ public class DevTests {
|
||||
private final ReactiveElasticsearchClient reactiveElasticsearchClient = ElasticsearchClients
|
||||
.createReactive(clientConfiguration(), transportOptions);
|
||||
private final ElasticsearchClient imperativeElasticsearchClient = ElasticsearchClients
|
||||
.createImperative(ElasticsearchClients.getRestClient(clientConfiguration()), transportOptions, jsonpMapper);
|
||||
.createImperative(Rest5Clients.getRest5Client(clientConfiguration()), transportOptions, jsonpMapper);
|
||||
|
||||
@Test
|
||||
void someTest() throws IOException {
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
package org.springframework.data.elasticsearch.client.elc.rest5_client;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*;
|
||||
@ -29,6 +29,7 @@ import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@ -41,7 +42,7 @@ import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
|
||||
*/
|
||||
@SuppressWarnings("UastIncorrectHttpHeaderInspection")
|
||||
@ExtendWith(SpringExtension.class)
|
||||
public class ELCWiremockTests {
|
||||
public class ELCRest5ClientWiremockTests {
|
||||
|
||||
@RegisterExtension static WireMockExtension wireMock = WireMockExtension.newInstance()
|
||||
.options(wireMockConfig()
|
||||
@ -69,7 +70,7 @@ public class ELCWiremockTests {
|
||||
wireMock.stubFor(put(urlPathEqualTo("/null-fields/_doc/42"))
|
||||
.withRequestBody(equalToJson("""
|
||||
{
|
||||
"_class": "org.springframework.data.elasticsearch.client.elc.ELCWiremockTests$EntityWithNullFields",
|
||||
"_class": "org.springframework.data.elasticsearch.client.elc.rest5_client.ELCRest5ClientWiremockTests$EntityWithNullFields",
|
||||
"id": "42",
|
||||
"field1": null
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2024-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc.rest_client;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchLegacyRestClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
|
||||
|
||||
/**
|
||||
* Tests that need to check the data produced by the Elasticsearch client
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SuppressWarnings("UastIncorrectHttpHeaderInspection")
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
public class ELCRestClientWiremockTests {
|
||||
|
||||
@RegisterExtension static WireMockExtension wireMock = WireMockExtension.newInstance()
|
||||
.options(wireMockConfig()
|
||||
.dynamicPort()
|
||||
// needed, otherwise Wiremock goes to test/resources/mappings
|
||||
.usingFilesUnderDirectory("src/test/resources/wiremock-mappings"))
|
||||
.build();
|
||||
|
||||
@Configuration
|
||||
static class Config extends ElasticsearchLegacyRestClientConfiguration {
|
||||
@Override
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder()
|
||||
.connectedTo("localhost:" + wireMock.getPort())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired ElasticsearchOperations operations;
|
||||
|
||||
@Test // #2839
|
||||
@DisplayName("should store null values if configured")
|
||||
void shouldStoreNullValuesIfConfigured() {
|
||||
|
||||
wireMock.stubFor(put(urlPathEqualTo("/null-fields/_doc/42"))
|
||||
.withRequestBody(equalToJson("""
|
||||
{
|
||||
"_class": "org.springframework.data.elasticsearch.client.elc.rest_client.ELCRestClientWiremockTests$EntityWithNullFields",
|
||||
"id": "42",
|
||||
"field1": null
|
||||
}
|
||||
"""))
|
||||
.willReturn(
|
||||
aResponse()
|
||||
.withStatus(200)
|
||||
.withHeader("X-elastic-product", "Elasticsearch")
|
||||
.withHeader("content-type", "application/vnd.elasticsearch+json;compatible-with=8")
|
||||
.withBody("""
|
||||
{
|
||||
"_index": "null-fields",
|
||||
"_id": "42",
|
||||
"_version": 1,
|
||||
"result": "created",
|
||||
"forced_refresh": true,
|
||||
"_shards": {
|
||||
"total": 2,
|
||||
"successful": 1,
|
||||
"failed": 0
|
||||
},
|
||||
"_seq_no": 1,
|
||||
"_primary_term": 1
|
||||
}
|
||||
""")));
|
||||
|
||||
var entity = new EntityWithNullFields();
|
||||
entity.setId("42");
|
||||
|
||||
operations.save(entity);
|
||||
// no need to assert anything, if the field1:null is not sent, we run into a 404 error
|
||||
}
|
||||
|
||||
@Document(indexName = "null-fields")
|
||||
static class EntityWithNullFields {
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
@Nullable
|
||||
@Field(storeNullValue = true) private String field1;
|
||||
@Nullable
|
||||
@Field private String field2;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getField1() {
|
||||
return field1;
|
||||
}
|
||||
|
||||
public void setField1(@Nullable String field1) {
|
||||
this.field1 = field1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getField2() {
|
||||
return field2;
|
||||
}
|
||||
|
||||
public void setField2(@Nullable String field2) {
|
||||
this.field2 = field2;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
@Deprecated(since = "6.0", forRemoval=true)
|
||||
package org.springframework.data.elasticsearch.client.elc.rest_client;
|
@ -19,7 +19,8 @@ import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@ -50,7 +51,7 @@ public class ElasticsearchConfigurationELCTests {
|
||||
considerNestedRepositories = true)
|
||||
static class Config extends ElasticsearchConfiguration {
|
||||
@Override
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
public @NonNull ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() //
|
||||
.connectedTo("localhost:9200") //
|
||||
.build();
|
||||
@ -61,7 +62,7 @@ public class ElasticsearchConfigurationELCTests {
|
||||
* using a repository with an entity that is set to createIndex = false as we have no elastic running for this test
|
||||
* and just check that all the necessary beans are created.
|
||||
*/
|
||||
@Autowired private RestClient restClient;
|
||||
@Autowired private Rest5Client rest5Client;
|
||||
@Autowired private ElasticsearchClient elasticsearchClient;
|
||||
@Autowired private ElasticsearchOperations elasticsearchOperations;
|
||||
|
||||
@ -69,7 +70,7 @@ public class ElasticsearchConfigurationELCTests {
|
||||
|
||||
@Test
|
||||
public void providesRequiredBeans() {
|
||||
assertThat(restClient).isNotNull();
|
||||
assertThat(rest5Client).isNotNull();
|
||||
assertThat(elasticsearchClient).isNotNull();
|
||||
assertThat(elasticsearchOperations).isNotNull();
|
||||
assertThat(repository).isNotNull();
|
||||
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2021-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.config.configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchLegacyRestClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
/**
|
||||
* Tests for {@link ElasticsearchConfiguration}.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
public class ElasticsearchLegacyRestClientConfigurationELCTests {
|
||||
|
||||
@Configuration
|
||||
@EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.config.configuration" },
|
||||
considerNestedRepositories = true)
|
||||
static class Config extends ElasticsearchLegacyRestClientConfiguration {
|
||||
@Override
|
||||
public @NonNull ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() //
|
||||
.connectedTo("localhost:9200") //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* using a repository with an entity that is set to createIndex = false as we have no elastic running for this test
|
||||
* and just check that all the necessary beans are created.
|
||||
*/
|
||||
@Autowired private RestClient restClient;
|
||||
@Autowired private ElasticsearchClient elasticsearchClient;
|
||||
@Autowired private ElasticsearchOperations elasticsearchOperations;
|
||||
|
||||
@Autowired private CreateIndexFalseRepository repository;
|
||||
|
||||
@Test
|
||||
public void providesRequiredBeans() {
|
||||
assertThat(restClient).isNotNull();
|
||||
assertThat(elasticsearchClient).isNotNull();
|
||||
assertThat(elasticsearchOperations).isNotNull();
|
||||
assertThat(repository).isNotNull();
|
||||
}
|
||||
|
||||
@Document(indexName = "test-index-config-configuration", createIndex = false)
|
||||
static class CreateIndexFalseEntity {
|
||||
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
}
|
||||
|
||||
interface CreateIndexFalseRepository extends ElasticsearchRepository<CreateIndexFalseEntity, String> {}
|
||||
}
|
@ -66,7 +66,6 @@ public class ReactiveElasticsearchConfigurationELCTests {
|
||||
|
||||
@Test
|
||||
public void providesRequiredBeans() {
|
||||
// assertThat(webClient).isNotNull();
|
||||
assertThat(reactiveElasticsearchClient).isNotNull();
|
||||
assertThat(reactiveElasticsearchOperations).isNotNull();
|
||||
assertThat(repository).isNotNull();
|
||||
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2019-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.config.configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
|
||||
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchLegacyRestClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
public class ReactiveElasticsearchLegacyRestClientConfigurationELCTests {
|
||||
|
||||
@Configuration
|
||||
@EnableReactiveElasticsearchRepositories(
|
||||
basePackages = { "org.springframework.data.elasticsearch.config.configuration" },
|
||||
considerNestedRepositories = true)
|
||||
static class Config extends ReactiveElasticsearchLegacyRestClientConfiguration {
|
||||
|
||||
@Override
|
||||
public @NonNull ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() //
|
||||
.connectedTo("localhost:9200") //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* using a repository with an entity that is set to createIndex = false as we have no elastic running for this test
|
||||
* and just check that all the necessary beans are created.
|
||||
*/
|
||||
@Autowired private ReactiveElasticsearchClient reactiveElasticsearchClient;
|
||||
@Autowired private ReactiveElasticsearchOperations reactiveElasticsearchOperations;
|
||||
@Autowired private CreateIndexFalseRepository repository;
|
||||
|
||||
@Test
|
||||
public void providesRequiredBeans() {
|
||||
// assertThat(webClient).isNotNull();
|
||||
assertThat(reactiveElasticsearchClient).isNotNull();
|
||||
assertThat(reactiveElasticsearchOperations).isNotNull();
|
||||
assertThat(repository).isNotNull();
|
||||
}
|
||||
|
||||
@Document(indexName = "test-index-config-configuration", createIndex = false)
|
||||
static class CreateIndexFalseEntity {
|
||||
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
}
|
||||
|
||||
interface CreateIndexFalseRepository extends ReactiveElasticsearchRepository<CreateIndexFalseEntity, String> {}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user