Auto-configure the new Elasticsearch clients

This commit introduces auto-configuration for the new Elasticsearch
clients that are based upon their new Java client. The new Java
client builds on top of their existing low-level REST client,
replacing the high-level REST client which has been deprecated.
As part of introducing support for the new Elasticsearch client,
the auto-configuration for the templates (both imperative and
reactive) provided by Spring Data has also been updated to use the
new templates that build upon the new Java client.

As part of these changes, support for the high-level REST client and
the old Spring Data Elasticsearch templates has been removed. One
significant change is that the new reactive template is no longer
based on WebClient. As a result, the WebClient-specific configuration
property has been removed.

Closes gh-30647
Closes gh-28597
Closes gh-31755
This commit is contained in:
Andy Wilkinson 2022-07-12 14:30:49 +01:00
parent f9ccfc1e12
commit 5c057a2730
44 changed files with 604 additions and 852 deletions

View File

@ -103,7 +103,6 @@ dependencies {
optional("org.eclipse.jetty:jetty-server") {
exclude group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api"
}
optional("org.elasticsearch:elasticsearch")
optional("org.elasticsearch.client:elasticsearch-rest-client") {
exclude group: "commons-logging", module: "commons-logging"
}
@ -168,9 +167,6 @@ dependencies {
testImplementation("org.eclipse.jetty:jetty-webapp") {
exclude group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api"
}
testImplementation("org.elasticsearch.client:elasticsearch-rest-high-level-client") {
exclude(group: "commons-logging", module: "commons-logging")
}
testImplementation("org.hamcrest:hamcrest")
testImplementation("org.hsqldb:hsqldb")
testImplementation("org.junit.jupiter:junit-jupiter")

View File

@ -31,7 +31,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClientAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
/**
* {@link EnableAutoConfiguration Auto-configuration} for
@ -45,7 +45,7 @@ import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearch
@ConditionalOnClass({ ReactiveElasticsearchClient.class, Flux.class })
@ConditionalOnBean(ReactiveElasticsearchClient.class)
@ConditionalOnEnabledHealthIndicator("elasticsearch")
public class ElasticSearchReactiveHealthContributorAutoConfiguration extends
public class ElasticsearchReactiveHealthContributorAutoConfiguration extends
CompositeReactiveHealthContributorConfiguration<ElasticsearchReactiveHealthIndicator, ReactiveElasticsearchClient> {
@Bean

View File

@ -43,7 +43,7 @@ import org.springframework.context.annotation.Bean;
@ConditionalOnClass(RestClient.class)
@ConditionalOnBean(RestClient.class)
@ConditionalOnEnabledHealthIndicator("elasticsearch")
public class ElasticSearchRestHealthContributorAutoConfiguration
public class ElasticsearchRestHealthContributorAutoConfiguration
extends CompositeHealthContributorConfiguration<ElasticsearchRestClientHealthIndicator, RestClient> {
@Bean

View File

@ -14,8 +14,8 @@ org.springframework.boot.actuate.autoconfigure.context.properties.ConfigurationP
org.springframework.boot.actuate.autoconfigure.context.ShutdownEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.couchbase.CouchbaseHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.couchbase.CouchbaseReactiveHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticSearchReactiveHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticSearchRestHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticsearchReactiveHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticsearchRestHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration

View File

@ -30,7 +30,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ElasticSearchReactiveHealthContributorAutoConfiguration}.
* Tests for {@link ElasticsearchReactiveHealthContributorAutoConfiguration}.
*
* @author Aleksander Lech
*/
@ -39,7 +39,7 @@ class ElasticsearchReactiveHealthContributorAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ElasticsearchDataAutoConfiguration.class,
ReactiveElasticsearchClientAutoConfiguration.class, ElasticsearchRestClientAutoConfiguration.class,
ElasticSearchReactiveHealthContributorAutoConfiguration.class,
ElasticsearchReactiveHealthContributorAutoConfiguration.class,
HealthContributorAutoConfiguration.class));
@Test
@ -51,7 +51,7 @@ class ElasticsearchReactiveHealthContributorAutoConfigurationTests {
@Test
void runWithRegularIndicatorShouldOnlyCreateReactiveIndicator() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(ElasticSearchRestHealthContributorAutoConfiguration.class))
.withConfiguration(AutoConfigurations.of(ElasticsearchRestHealthContributorAutoConfiguration.class))
.run((context) -> assertThat(context).hasSingleBean(ElasticsearchReactiveHealthIndicator.class)
.hasBean("elasticsearchHealthContributor")
.doesNotHaveBean(ElasticsearchRestClientHealthIndicator.class));

View File

@ -32,16 +32,16 @@ import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ElasticSearchRestHealthContributorAutoConfiguration}.
* Tests for {@link ElasticsearchRestHealthContributorAutoConfiguration}.
*
* @author Filip Hrisafov
* @author Andy Wilkinson
*/
class ElasticSearchRestHealthContributorAutoConfigurationTests {
class ElasticsearchRestHealthContributorAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class,
ElasticSearchRestHealthContributorAutoConfiguration.class,
ElasticsearchRestHealthContributorAutoConfiguration.class,
HealthContributorAutoConfiguration.class));
@Test
@ -51,29 +51,19 @@ class ElasticSearchRestHealthContributorAutoConfigurationTests {
}
@Test
@SuppressWarnings("deprecation")
void runWithoutRestHighLevelClientAndWithoutRestClientShouldNotCreateIndicator() {
this.contextRunner
.withClassLoader(
new FilteredClassLoader(org.elasticsearch.client.RestHighLevelClient.class, RestClient.class))
void runWithoutRestClientShouldNotCreateIndicator() {
this.contextRunner.withClassLoader(new FilteredClassLoader(RestClient.class))
.run((context) -> assertThat(context).doesNotHaveBean(ElasticsearchRestClientHealthIndicator.class)
.doesNotHaveBean("elasticsearchHealthContributor"));
}
@Test
void runWithoutRestHighLevelClientAndWithRestClientShouldCreateIndicator() {
void runWithRestClientShouldCreateIndicator() {
this.contextRunner.withUserConfiguration(CustomRestClientConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestClientHealthIndicator.class)
.hasBean("elasticsearchHealthContributor"));
}
@Test
void runWithRestHighLevelClientAndWithRestClientShouldCreateIndicator() {
this.contextRunner.withUserConfiguration(CustomRestHighClientConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestClientHealthIndicator.class)
.hasBean("elasticsearchHealthContributor"));
}
@Test
void runWhenDisabledShouldNotCreateIndicator() {
this.contextRunner.withPropertyValues("management.health.elasticsearch.enabled:false")
@ -91,20 +81,4 @@ class ElasticSearchRestHealthContributorAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@SuppressWarnings("deprecation")
static class CustomRestHighClientConfiguration {
@Bean
org.elasticsearch.client.RestHighLevelClient customRestHighClient(RestClientBuilder builder) {
return new org.elasticsearch.client.RestHighLevelClient(builder);
}
@Bean
RestClient customClient(org.elasticsearch.client.RestHighLevelClient restHighLevelClient) {
return restHighLevelClient.getLowLevelClient();
}
}
}

View File

@ -43,13 +43,9 @@ dependencies {
optional("org.eclipse.jetty:jetty-server") {
exclude(group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api")
}
optional("org.elasticsearch:elasticsearch")
optional("org.elasticsearch.client:elasticsearch-rest-client") {
exclude(group: "commons-logging", module: "commons-logging")
}
optional("org.elasticsearch.client:elasticsearch-rest-high-level-client") {
exclude(group: "commons-logging", module: "commons-logging")
}
optional("org.flywaydb:flyway-core")
optional("org.hibernate.validator:hibernate-validator")
optional("org.influxdb:influxdb-java")

View File

@ -16,20 +16,15 @@
package org.springframework.boot.actuate.elasticsearch;
import java.util.Map;
import co.elastic.clients.elasticsearch._types.HealthStatus;
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
/**
* {@link HealthIndicator} for an Elasticsearch cluster using a
@ -42,11 +37,6 @@ import org.springframework.web.reactive.function.client.WebClient;
*/
public class ElasticsearchReactiveHealthIndicator extends AbstractReactiveHealthIndicator {
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() {
};
private static final String RED_STATUS = "red";
private final ReactiveElasticsearchClient client;
public ElasticsearchReactiveHealthIndicator(ReactiveElasticsearchClient client) {
@ -56,32 +46,33 @@ public class ElasticsearchReactiveHealthIndicator extends AbstractReactiveHealth
@Override
protected Mono<Health> doHealthCheck(Health.Builder builder) {
return this.client.execute((webClient) -> getHealth(builder, webClient));
return this.client.cluster().health((b) -> b).map((response) -> processResponse(builder, response));
}
private Mono<Health> getHealth(Health.Builder builder, WebClient webClient) {
return webClient.get().uri("/_cluster/health/").exchangeToMono((response) -> doHealthCheck(builder, response));
}
private Mono<Health> doHealthCheck(Health.Builder builder, ClientResponse response) {
HttpStatusCode httpStatusCode = response.statusCode();
HttpStatus httpStatus = HttpStatus.resolve(httpStatusCode.value());
if (httpStatusCode.is2xxSuccessful()) {
return response.bodyToMono(STRING_OBJECT_MAP).map((body) -> getHealth(builder, body));
}
builder.down();
builder.withDetail("statusCode", httpStatusCode.value());
if (httpStatus != null) {
builder.withDetail("reasonPhrase", httpStatus.getReasonPhrase());
}
return response.releaseBody().thenReturn(builder.build());
}
private Health getHealth(Health.Builder builder, Map<String, Object> body) {
String status = (String) body.get("status");
builder.status(RED_STATUS.equals(status) ? Status.OUT_OF_SERVICE : Status.UP);
builder.withDetails(body);
private Health processResponse(Health.Builder builder, HealthResponse response) {
if (!response.timedOut()) {
HealthStatus status = response.status();
builder.status((HealthStatus.Red == status) ? Status.OUT_OF_SERVICE : Status.UP);
builder.withDetail("cluster_name", response.clusterName());
builder.withDetail("status", response.status().jsonValue());
builder.withDetail("timed_out", response.timedOut());
builder.withDetail("number_of_nodes", response.numberOfNodes());
builder.withDetail("number_of_data_nodes", response.numberOfDataNodes());
builder.withDetail("active_primary_shards", response.activePrimaryShards());
builder.withDetail("active_shards", response.activeShards());
builder.withDetail("relocating_shards", response.relocatingShards());
builder.withDetail("initializing_shards", response.initializingShards());
builder.withDetail("unassigned_shards", response.unassignedShards());
builder.withDetail("delayed_unassigned_shards", response.delayedUnassignedShards());
builder.withDetail("number_of_pending_tasks", response.numberOfPendingTasks());
builder.withDetail("number_of_in_flight_fetch", response.numberOfInFlightFetch());
builder.withDetail("task_max_waiting_in_queue_millis",
response.taskMaxWaitingInQueueMillis().toEpochMilli());
builder.withDetail("active_shards_percent_as_number",
Double.parseDouble(response.activeShardsPercentAsNumber()));
return builder.build();
}
return builder.down().build();
}
}

View File

@ -19,16 +19,20 @@ package org.springframework.boot.actuate.elasticsearch;
import java.time.Duration;
import java.util.Map;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.apache.http.HttpHost;
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.client.RestClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -50,13 +54,13 @@ class ElasticsearchReactiveHealthIndicatorTests {
private ElasticsearchReactiveHealthIndicator healthIndicator;
@SuppressWarnings("deprecation")
@BeforeEach
void setup() throws Exception {
this.server = new MockWebServer();
this.server.start();
ReactiveElasticsearchClient client = org.springframework.data.elasticsearch.client.erhlc.DefaultReactiveElasticsearchClient
.create(ClientConfiguration.create(this.server.getHostName() + ":" + this.server.getPort()));
ReactiveElasticsearchClient client = new ReactiveElasticsearchClient(new RestClientTransport(
RestClient.builder(HttpHost.create(this.server.getHostName() + ":" + this.server.getPort())).build(),
new JacksonJsonpMapper()));
this.healthIndicator = new ElasticsearchReactiveHealthIndicator(client);
}
@ -86,20 +90,15 @@ class ElasticsearchReactiveHealthIndicatorTests {
this.server.shutdown();
Health health = this.healthIndicator.health().block(TIMEOUT);
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat(health.getDetails().get("error")).asString()
.contains("org.springframework.data.elasticsearch.client.NoReachableHostException");
assertThat(health.getDetails().get("error")).asString().contains("Connection refused");
}
@Test
void elasticsearchIsDownByResponseCode() {
// first enqueue an OK response since the HostChecker first sends a HEAD request
// to "/"
this.server.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.value()));
this.server.enqueue(new MockResponse().setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.value()));
Health health = this.healthIndicator.health().block(TIMEOUT);
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat(health.getDetails().get("statusCode")).asString().isEqualTo("500");
assertThat(health.getDetails().get("reasonPhrase")).asString().isEqualTo("Internal Server Error");
assertThat(health.getDetails().get("error")).asString().startsWith(ResponseException.class.getName());
}
@Test
@ -116,24 +115,19 @@ class ElasticsearchReactiveHealthIndicatorTests {
entry("active_primary_shards", 0), entry("active_shards", 0), entry("relocating_shards", 0),
entry("initializing_shards", 0), entry("unassigned_shards", 0), entry("delayed_unassigned_shards", 0),
entry("number_of_pending_tasks", 0), entry("number_of_in_flight_fetch", 0),
entry("task_max_waiting_in_queue_millis", 0), entry("active_shards_percent_as_number", 100.0));
entry("task_max_waiting_in_queue_millis", 0L), entry("active_shards_percent_as_number", 100.0));
}
private void setupMockResponse(int responseCode, String status) {
// first enqueue an OK response since the HostChecker first sends a HEAD request
// to "/"
this.server.enqueue(new MockResponse());
MockResponse mockResponse = new MockResponse().setResponseCode(HttpStatus.valueOf(responseCode).value())
.setBody(createJsonResult(responseCode, status))
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
MockResponse mockResponse = new MockResponse().setBody(createJsonResult(status))
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.setHeader("X-Elastic-Product", "Elasticsearch");
this.server.enqueue(mockResponse);
}
private String createJsonResult(int responseCode, String status) {
if (responseCode == 200) {
private String createJsonResult(String status) {
return String.format(
"{\"cluster_name\":\"elasticsearch\","
+ "\"status\":\"%s\",\"timed_out\":false,\"number_of_nodes\":1,"
"{\"cluster_name\":\"elasticsearch\"," + "\"status\":\"%s\",\"timed_out\":false,\"number_of_nodes\":1,"
+ "\"number_of_data_nodes\":1,\"active_primary_shards\":0,"
+ "\"active_shards\":0,\"relocating_shards\":0,\"initializing_shards\":0,"
+ "\"unassigned_shards\":0,\"delayed_unassigned_shards\":0,"
@ -141,7 +135,5 @@ class ElasticsearchReactiveHealthIndicatorTests {
+ "\"task_max_waiting_in_queue_millis\":0,\"active_shards_percent_as_number\":100.0}",
status);
}
return "{\n \"error\": \"Server Error\",\n \"status\": " + responseCode + "\n}";
}
}

View File

@ -12,6 +12,9 @@ description = "Spring Boot AutoConfigure"
dependencies {
api(project(":spring-boot-project:spring-boot"))
optional("co.elastic.clients:elasticsearch-java") {
exclude group: "commons-logging", module: "commons-logging"
}
optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor")
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
@ -99,9 +102,6 @@ dependencies {
optional("org.elasticsearch.client:elasticsearch-rest-client-sniffer") {
exclude group: "commons-logging", module: "commons-logging"
}
optional("org.elasticsearch.client:elasticsearch-rest-high-level-client") {
exclude group: "commons-logging", module: "commons-logging"
}
optional("org.flywaydb:flyway-core")
optional("org.flywaydb:flyway-sqlserver")
optional("org.freemarker:freemarker")

View File

@ -19,10 +19,10 @@ package org.springframework.boot.autoconfigure.data.elasticsearch;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClientAutoConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
@ -38,12 +38,11 @@ import org.springframework.data.elasticsearch.repository.config.EnableReactiveEl
* @see EnableReactiveElasticsearchRepositories
*/
@AutoConfiguration(
after = { ElasticsearchRestClientAutoConfiguration.class, ReactiveElasticsearchClientAutoConfiguration.class })
@ConditionalOnClass({ ElasticsearchRestTemplate.class })
after = { ElasticsearchClientAutoConfiguration.class, ReactiveElasticsearchClientAutoConfiguration.class })
@ConditionalOnClass({ ElasticsearchTemplate.class })
@Import({ ElasticsearchDataConfiguration.BaseConfiguration.class,
ElasticsearchDataConfiguration.RestClientConfiguration.class,
ElasticsearchDataConfiguration.JavaClientConfiguration.class,
ElasticsearchDataConfiguration.ReactiveRestClientConfiguration.class })
@SuppressWarnings("deprecation")
public class ElasticsearchDataAutoConfiguration {
}

View File

@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.data.elasticsearch;
import java.util.Collections;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -26,14 +28,15 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Configuration classes for Spring Data for Elasticsearch
@ -77,33 +80,28 @@ abstract class ElasticsearchDataConfiguration {
}
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(org.elasticsearch.client.RestHighLevelClient.class)
static class RestClientConfiguration {
@ConditionalOnClass(ElasticsearchClient.class)
static class JavaClientConfiguration {
@Bean
@ConditionalOnMissingBean(value = ElasticsearchOperations.class, name = "elasticsearchTemplate")
@ConditionalOnBean(org.elasticsearch.client.RestHighLevelClient.class)
org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate elasticsearchTemplate(
org.elasticsearch.client.RestHighLevelClient client, ElasticsearchConverter converter) {
return new org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate(client, converter);
@ConditionalOnBean(ElasticsearchClient.class)
ElasticsearchTemplate elasticsearchTemplate(ElasticsearchClient client, ElasticsearchConverter converter) {
return new ElasticsearchTemplate(client, converter);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ WebClient.class, ReactiveElasticsearchOperations.class })
static class ReactiveRestClientConfiguration {
@Bean
@ConditionalOnMissingBean(value = ReactiveElasticsearchOperations.class, name = "reactiveElasticsearchTemplate")
@ConditionalOnBean(ReactiveElasticsearchClient.class)
@SuppressWarnings("deprecation")
org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchTemplate reactiveElasticsearchTemplate(
ReactiveElasticsearchClient client, ElasticsearchConverter converter) {
return new org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchTemplate(client,
converter);
ReactiveElasticsearchTemplate reactiveElasticsearchTemplate(ReactiveElasticsearchClient client,
ElasticsearchConverter converter) {
return new ReactiveElasticsearchTemplate(client, converter);
}
}

View File

@ -16,8 +16,6 @@
package org.springframework.boot.autoconfigure.data.elasticsearch;
import org.elasticsearch.client.Client;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -38,7 +36,7 @@ import org.springframework.data.elasticsearch.repository.support.ElasticsearchRe
* @see EnableElasticsearchRepositories
*/
@AutoConfiguration
@ConditionalOnClass({ Client.class, ElasticsearchRepository.class })
@ConditionalOnClass(ElasticsearchRepository.class)
@ConditionalOnProperty(prefix = "spring.data.elasticsearch.repositories", name = "enabled", havingValue = "true",
matchIfMissing = true)
@ConditionalOnMissingBean(ElasticsearchRepositoryFactoryBean.class)

View File

@ -22,7 +22,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import org.springframework.data.elasticsearch.repository.support.ReactiveElasticsearchRepositoryFactoryBean;

View File

@ -0,0 +1,42 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.elasticsearch;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientConfigurations.ElasticsearchClientConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientConfigurations.ElasticsearchTransportConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration;
import org.springframework.context.annotation.Import;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Elasticsearch's Java client.
*
* @author Andy Wilkinson
* @since 3.0.0
*/
@AutoConfiguration(after = { JacksonAutoConfiguration.class, JsonbAutoConfiguration.class,
ElasticsearchRestClientAutoConfiguration.class })
@ConditionalOnClass(ElasticsearchClient.class)
@Import({ ElasticsearchTransportConfiguration.class, ElasticsearchClientConfiguration.class })
public class ElasticsearchClientAutoConfiguration {
}

View File

@ -0,0 +1,107 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.elasticsearch;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.json.SimpleJsonpMapper;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.json.jsonb.JsonbJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.TransportOptions;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.json.bind.Jsonb;
import jakarta.json.spi.JsonProvider;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Configurations for import into {@link ElasticsearchClientAutoConfiguration}.
*
* @author Andy Wilkinson
*/
class ElasticsearchClientConfigurations {
@ConditionalOnMissingBean(JsonpMapper.class)
@ConditionalOnBean(ObjectMapper.class)
@Configuration(proxyBeanMethods = false)
static class JacksonJsonpMapperConfiguration {
@Bean
JacksonJsonpMapper jacksonJsonpMapper(ObjectMapper objectMapper) {
return new JacksonJsonpMapper(objectMapper);
}
}
@ConditionalOnMissingBean(JsonpMapper.class)
@ConditionalOnBean(Jsonb.class)
@Configuration(proxyBeanMethods = false)
static class JsonbJsonpMapperConfiguration {
@Bean
JsonbJsonpMapper jsonbJsonpMapper(Jsonb jsonb) {
return new JsonbJsonpMapper(JsonProvider.provider(), jsonb);
}
}
@ConditionalOnMissingBean(JsonpMapper.class)
@Configuration(proxyBeanMethods = false)
static class SimpleJsonpMapperConfiguration {
@Bean
SimpleJsonpMapper simpleJsonpMapper() {
return new SimpleJsonpMapper();
}
}
@Import({ JacksonJsonpMapperConfiguration.class, JsonbJsonpMapperConfiguration.class,
SimpleJsonpMapperConfiguration.class })
@ConditionalOnBean(RestClient.class)
@ConditionalOnMissingBean(ElasticsearchTransport.class)
static class ElasticsearchTransportConfiguration {
@Bean
RestClientTransport restClientTransport(RestClient restClient, JsonpMapper jsonMapper,
ObjectProvider<TransportOptions> transportOptions) {
return new RestClientTransport(restClient, jsonMapper, transportOptions.getIfAvailable());
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ElasticsearchTransport.class)
static class ElasticsearchClientConfiguration {
@Bean
@ConditionalOnMissingBean
ElasticsearchClient elasticsearchClient(ElasticsearchTransport transport) {
return new ElasticsearchClient(transport);
}
}
}

View File

@ -22,7 +22,6 @@ import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.unit.DataSize;
/**
* Configuration properties for Elasticsearch.
@ -65,8 +64,6 @@ public class ElasticsearchProperties {
private final Restclient restclient = new Restclient();
private final Webclient webclient = new Webclient();
public List<String> getUris() {
return this.uris;
}
@ -119,10 +116,6 @@ public class ElasticsearchProperties {
return this.restclient;
}
public Webclient getWebclient() {
return this.webclient;
}
public static class Restclient {
private final Sniffer sniffer = new Sniffer();
@ -163,22 +156,4 @@ public class ElasticsearchProperties {
}
public static class Webclient {
/**
* Limit on the number of bytes that can be buffered whenever the input stream
* needs to be aggregated.
*/
private DataSize maxInMemorySize;
public DataSize getMaxInMemorySize() {
return this.maxInMemorySize;
}
public void setMaxInMemorySize(DataSize maxInMemorySize) {
this.maxInMemorySize = maxInMemorySize;
}
}
}

View File

@ -23,9 +23,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestClientBuilderConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestClientConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestClientFromRestHighLevelClientConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestClientSnifferConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestHighLevelClientConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;
@ -39,9 +37,7 @@ import org.springframework.context.annotation.Import;
@AutoConfiguration
@ConditionalOnClass(RestClientBuilder.class)
@EnableConfigurationProperties(ElasticsearchProperties.class)
@Import({ RestClientBuilderConfiguration.class, RestHighLevelClientConfiguration.class,
RestClientFromRestHighLevelClientConfiguration.class, RestClientConfiguration.class,
RestClientSnifferConfiguration.class })
@Import({ RestClientBuilderConfiguration.class, RestClientConfiguration.class, RestClientSnifferConfiguration.class })
public class ElasticsearchRestClientAutoConfiguration {
}

View File

@ -35,7 +35,6 @@ import org.elasticsearch.client.sniff.SnifferBuilder;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
@ -109,36 +108,7 @@ class ElasticsearchRestClientConfigurations {
}
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(org.elasticsearch.client.RestHighLevelClient.class)
@ConditionalOnMissingBean({ org.elasticsearch.client.RestHighLevelClient.class, RestClient.class })
static class RestHighLevelClientConfiguration {
@Bean
org.elasticsearch.client.RestHighLevelClient elasticsearchRestHighLevelClient(
RestClientBuilder restClientBuilder) {
return new org.elasticsearch.client.RestHighLevelClient(restClientBuilder);
}
}
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(org.elasticsearch.client.RestHighLevelClient.class)
@ConditionalOnSingleCandidate(org.elasticsearch.client.RestHighLevelClient.class)
@ConditionalOnMissingBean(RestClient.class)
static class RestClientFromRestHighLevelClientConfiguration {
@Bean
RestClient elasticsearchRestClient(org.elasticsearch.client.RestHighLevelClient restHighLevelClient) {
return restHighLevelClient.getLowLevelClient();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.elasticsearch.client.RestHighLevelClient")
@ConditionalOnMissingBean(RestClient.class)
static class RestClientConfiguration {

View File

@ -16,207 +16,37 @@
package org.springframework.boot.autoconfigure.elasticsearch;
import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.client.indices.GetIndexRequest;
import reactor.netty.http.client.HttpClient;
import co.elastic.clients.transport.ElasticsearchTransport;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveRestClients;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.unit.DataSize;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Elasticsearch Reactive REST
* clients.
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data Elasticsearch's
* reactive client.
*
* @author Brian Clozel
* @since 3.0.0
*/
@AutoConfiguration
@ConditionalOnClass({ ReactiveRestClients.class, ElasticsearchException.class, GetIndexRequest.class, WebClient.class,
HttpClient.class })
@AutoConfiguration(after = ElasticsearchClientAutoConfiguration.class)
@ConditionalOnClass({ ReactiveElasticsearchClient.class, ElasticsearchTransport.class, Mono.class })
@EnableConfigurationProperties(ElasticsearchProperties.class)
@SuppressWarnings("deprecation")
@Import(ElasticsearchClientConfigurations.ElasticsearchTransportConfiguration.class)
public class ReactiveElasticsearchClientAutoConfiguration {
private final ConsolidatedProperties properties;
ReactiveElasticsearchClientAutoConfiguration(ElasticsearchProperties properties) {
this.properties = new ConsolidatedProperties(properties);
}
@Bean
@ConditionalOnMissingBean
public ClientConfiguration clientConfiguration() {
ClientConfiguration.MaybeSecureClientConfigurationBuilder builder = ClientConfiguration.builder()
.connectedTo(this.properties.getEndpoints().toArray(new String[0]));
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(this.properties.isUseSsl()).whenTrue().toCall(builder::usingSsl);
map.from(this.properties.getCredentials())
.to((credentials) -> builder.withBasicAuth(credentials.getUsername(), credentials.getPassword()));
map.from(this.properties.getConnectionTimeout()).to(builder::withConnectTimeout);
map.from(this.properties.getSocketTimeout()).to(builder::withSocketTimeout);
map.from(this.properties.getPathPrefix()).to(builder::withPathPrefix);
configureExchangeStrategies(map, builder);
return builder.build();
}
private void configureExchangeStrategies(PropertyMapper map,
ClientConfiguration.TerminalClientConfigurationBuilder builder) {
map.from(this.properties.getMaxInMemorySize()).asInt(DataSize::toBytes).to((maxInMemorySize) -> {
builder.withClientConfigurer(ElasticsearchClients.WebClientConfigurationCallback.from((webClient) -> {
ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
.codecs((configurer) -> configurer.defaultCodecs().maxInMemorySize(maxInMemorySize)).build();
return webClient.mutate().exchangeStrategies(exchangeStrategies).build();
}));
});
}
@Bean
@ConditionalOnMissingBean
public ReactiveElasticsearchClient reactiveElasticsearchClient(ClientConfiguration clientConfiguration) {
return ReactiveRestClients.create(clientConfiguration);
}
private static final class ConsolidatedProperties {
private final ElasticsearchProperties properties;
private final List<URI> uris;
private ConsolidatedProperties(ElasticsearchProperties properties) {
this.properties = properties;
this.uris = properties.getUris().stream().map((s) -> s.startsWith("http") ? s : "http://" + s)
.map(URI::create).collect(Collectors.toList());
}
private List<String> getEndpoints() {
return this.uris.stream().map(this::getEndpoint).collect(Collectors.toList());
}
private String getEndpoint(URI uri) {
return uri.getHost() + ":" + uri.getPort();
}
private Credentials getCredentials() {
Credentials propertyCredentials = Credentials.from(this.properties);
Credentials uriCredentials = Credentials.from(this.uris);
if (uriCredentials == null) {
return propertyCredentials;
}
Assert.isTrue(propertyCredentials == null || uriCredentials.equals(propertyCredentials),
"Credentials from URI user info do not match those from spring.elasticsearch.username and "
+ "spring.elasticsearch.password");
return uriCredentials;
}
private Duration getConnectionTimeout() {
return this.properties.getConnectionTimeout();
}
private Duration getSocketTimeout() {
return this.properties.getSocketTimeout();
}
private boolean isUseSsl() {
Set<String> schemes = this.uris.stream().map(URI::getScheme).collect(Collectors.toSet());
Assert.isTrue(schemes.size() == 1, "Configured Elasticsearch URIs have varying schemes");
return schemes.iterator().next().equals("https");
}
private DataSize getMaxInMemorySize() {
return this.properties.getWebclient().getMaxInMemorySize();
}
private String getPathPrefix() {
return this.properties.getPathPrefix();
}
private static final class Credentials {
private final String username;
private final String password;
private Credentials(String username, String password) {
this.username = username;
this.password = password;
}
private String getUsername() {
return this.username;
}
private String getPassword() {
return this.password;
}
private static Credentials from(List<URI> uris) {
Set<String> userInfos = uris.stream().map(URI::getUserInfo).collect(Collectors.toSet());
Assert.isTrue(userInfos.size() == 1, "Configured Elasticsearch URIs have varying user infos");
String userInfo = userInfos.iterator().next();
if (userInfo == null) {
return null;
}
String[] parts = userInfo.split(":");
String username = parts[0];
String password = (parts.length != 2) ? "" : parts[1];
return new Credentials(username, password);
}
private static Credentials from(ElasticsearchProperties properties) {
return getCredentials(properties.getUsername(), properties.getPassword());
}
private static Credentials getCredentials(String username, String password) {
if (username == null && password == null) {
return null;
}
return new Credentials(username, password);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Credentials other = (Credentials) obj;
return ObjectUtils.nullSafeEquals(this.username, other.username)
&& ObjectUtils.nullSafeEquals(this.password, other.password);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.username);
result = prime * result + ObjectUtils.nullSafeHashCode(this.password);
return result;
}
}
@ConditionalOnBean(ElasticsearchTransport.class)
ReactiveElasticsearchClient reactiveElasticsearchClient(ElasticsearchTransport transport) {
return new ReactiveElasticsearchClient(transport);
}
}

View File

@ -981,6 +981,15 @@
"http://localhost:9200"
]
},
{
"name": "spring.elasticsearch.webclient.max-in-memory-size",
"type": "org.springframework.util.unit.DataSize",
"description": "Limit on the number of bytes that can be buffered whenever the input stream needs to be aggregated.",
"deprecation": {
"level": "error",
"reason": "Reactive Elasticsearch client no longer uses WebClient."
}
},
{
"name": "spring.flyway.baseline-migration-prefix",
"defaultValue": "B"

View File

@ -39,6 +39,7 @@ org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration
org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClientAutoConfiguration
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration

View File

@ -20,21 +20,20 @@ import java.math.BigDecimal;
import java.util.Collections;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.data.elasticsearch.city.City;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClientAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
@ -53,26 +52,16 @@ import static org.mockito.Mockito.mock;
* @author Scott Frederick
* @author Stephane Nicoll
*/
@SuppressWarnings("deprecation")
class ElasticsearchDataAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class,
ReactiveElasticsearchClientAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class));
@BeforeEach
void setUp() {
System.setProperty("es.set.netty.runtime.available.processors", "false");
}
@AfterEach
void tearDown() {
System.clearProperty("es.set.netty.runtime.available.processors");
}
ElasticsearchClientAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class,
ReactiveElasticsearchClientAutoConfiguration.class));
@Test
void defaultRestBeansRegistered() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestTemplate.class)
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ElasticsearchTemplate.class)
.hasSingleBean(ReactiveElasticsearchTemplate.class).hasSingleBean(ElasticsearchConverter.class)
.hasSingleBean(ElasticsearchConverter.class).hasSingleBean(ElasticsearchCustomConversions.class));
}
@ -92,19 +81,19 @@ class ElasticsearchDataAutoConfigurationTests {
this.contextRunner.withUserConfiguration(CustomElasticsearchCustomConversions.class).run((context) -> {
assertThat(context).hasSingleBean(ElasticsearchCustomConversions.class).hasBean("testCustomConversions");
assertThat(context.getBean(ElasticsearchConverter.class).getConversionService()
.canConvert(ElasticsearchRestTemplate.class, Boolean.class)).isTrue();
.canConvert(ElasticsearchTemplate.class, Boolean.class)).isTrue();
});
}
@Test
void customRestTemplateShouldBeUsed() {
this.contextRunner.withUserConfiguration(CustomRestTemplate.class).run((context) -> assertThat(context)
.getBeanNames(ElasticsearchRestTemplate.class).hasSize(1).contains("elasticsearchTemplate"));
.getBeanNames(ElasticsearchTemplate.class).hasSize(1).contains("elasticsearchTemplate"));
}
@Test
void customReactiveRestTemplateShouldBeUsed() {
this.contextRunner.withUserConfiguration(CustomReactiveRestTemplate.class)
this.contextRunner.withUserConfiguration(CustomReactiveElasticsearchTemplate.class)
.run((context) -> assertThat(context).getBeanNames(ReactiveElasticsearchTemplate.class).hasSize(1)
.contains("reactiveElasticsearchTemplate"));
}
@ -131,14 +120,14 @@ class ElasticsearchDataAutoConfigurationTests {
static class CustomRestTemplate {
@Bean
ElasticsearchRestTemplate elasticsearchTemplate() {
return mock(ElasticsearchRestTemplate.class);
ElasticsearchTemplate elasticsearchTemplate() {
return mock(ElasticsearchTemplate.class);
}
}
@Configuration(proxyBeanMethods = false)
static class CustomReactiveRestTemplate {
static class CustomReactiveElasticsearchTemplate {
@Bean
ReactiveElasticsearchTemplate reactiveElasticsearchTemplate() {
@ -153,10 +142,10 @@ class ElasticsearchDataAutoConfigurationTests {
}
static class MyConverter implements Converter<ElasticsearchRestTemplate, Boolean> {
static class MyConverter implements Converter<ElasticsearchTemplate, Boolean> {
@Override
public Boolean convert(ElasticsearchRestTemplate source) {
public Boolean convert(ElasticsearchTemplate source) {
return null;
}

View File

@ -29,11 +29,12 @@ import org.springframework.boot.autoconfigure.data.alt.elasticsearch.CityElastic
import org.springframework.boot.autoconfigure.data.elasticsearch.city.City;
import org.springframework.boot.autoconfigure.data.elasticsearch.city.CityRepository;
import org.springframework.boot.autoconfigure.data.empty.EmptyDataPackage;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import static org.assertj.core.api.Assertions.assertThat;
@ -46,7 +47,6 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Brian Clozel
*/
@Testcontainers(disabledWithoutDocker = true)
@SuppressWarnings("deprecation")
class ElasticsearchRepositoriesAutoConfigurationTests {
@Container
@ -55,19 +55,20 @@ class ElasticsearchRepositoriesAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class,
ElasticsearchRepositoriesAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class))
ElasticsearchClientAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class,
ElasticsearchDataAutoConfiguration.class))
.withPropertyValues("spring.elasticsearch.uris=" + elasticsearch.getHttpHostAddress());
@Test
void testDefaultRepositoryConfiguration() {
this.contextRunner.withUserConfiguration(TestConfiguration.class).run((context) -> assertThat(context)
.hasSingleBean(CityRepository.class).hasSingleBean(ElasticsearchRestTemplate.class));
.hasSingleBean(CityRepository.class).hasSingleBean(ElasticsearchTemplate.class));
}
@Test
void testNoRepositoryConfiguration() {
this.contextRunner.withUserConfiguration(EmptyConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestTemplate.class));
.run((context) -> assertThat(context).hasSingleBean(ElasticsearchTemplate.class));
}
@Test

View File

@ -29,11 +29,12 @@ import org.springframework.boot.autoconfigure.data.alt.elasticsearch.CityReactiv
import org.springframework.boot.autoconfigure.data.elasticsearch.city.City;
import org.springframework.boot.autoconfigure.data.elasticsearch.city.ReactiveCityRepository;
import org.springframework.boot.autoconfigure.data.empty.EmptyDataPackage;
import org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClientAutoConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import static org.assertj.core.api.Assertions.assertThat;
@ -46,7 +47,6 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Brian Clozel
*/
@Testcontainers(disabledWithoutDocker = true)
@SuppressWarnings("deprecation")
class ReactiveElasticsearchRepositoriesAutoConfigurationTests {
@Container
@ -54,7 +54,8 @@ class ReactiveElasticsearchRepositoriesAutoConfigurationTests {
.withStartupAttempts(5).withStartupTimeout(Duration.ofMinutes(10));
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ReactiveElasticsearchClientAutoConfiguration.class,
.withConfiguration(AutoConfigurations.of(ElasticsearchClientAutoConfiguration.class,
ElasticsearchRestClientAutoConfiguration.class,
ReactiveElasticsearchRepositoriesAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class))
.withPropertyValues(
"spring.elasticsearch.uris=" + elasticsearch.getHost() + ":" + elasticsearch.getFirstMappedPort(),

View File

@ -0,0 +1,66 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.elasticsearch;
import java.time.Duration;
import java.util.Map;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.GetResponse;
import org.junit.jupiter.api.Test;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link ElasticsearchClientAutoConfiguration}.
*
* @author Andy Wilkinson
*/
@Testcontainers(disabledWithoutDocker = true)
class ElasticsearchClientAutoConfigurationIntegrationTests {
@Container
static ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch())
.withStartupAttempts(5).withStartupTimeout(Duration.ofMinutes(10));
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class,
ElasticsearchRestClientAutoConfiguration.class, ElasticsearchClientAutoConfiguration.class));
@Test
void reactiveClientCanQueryElasticsearchNode() {
this.contextRunner
.withPropertyValues("spring.elasticsearch.uris=" + elasticsearch.getHttpHostAddress(),
"spring.elasticsearch.connection-timeout=120s", "spring.elasticsearch.socket-timeout=120s")
.run((context) -> {
ElasticsearchClient client = context.getBean(ElasticsearchClient.class);
client.index((b) -> b.index("foo").id("1").document(Map.of("a", "alpha", "b", "bravo")));
GetResponse<Object> response = client.get((b) -> b.index("foo").id("1"), Object.class);
assertThat(response).isNotNull();
assertThat(response.found()).isTrue();
});
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.elasticsearch;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.json.SimpleJsonpMapper;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.json.jsonb.JsonbJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.elasticsearch.client.RestClient;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ElasticsearchClientAutoConfiguration}.
*
* @author Andy Wilkinson
*/
public class ElasticsearchClientAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ElasticsearchClientAutoConfiguration.class));
@Test
void withoutRestClientThenAutoConfigurationShouldBackOff() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ElasticsearchTransport.class)
.doesNotHaveBean(JsonpMapper.class).doesNotHaveBean(ElasticsearchClient.class));
}
@Test
void withRestClientAutoConfigurationShouldDefineClientAndSupportingBeans() {
this.contextRunner.withUserConfiguration(RestClientConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(JsonpMapper.class)
.hasSingleBean(RestClientTransport.class).hasSingleBean(ElasticsearchClient.class));
}
@Test
void withoutJsonbOrJacksonShouldDefineSimpleMapper() {
this.contextRunner.withUserConfiguration(RestClientConfiguration.class).run((context) -> assertThat(context)
.hasSingleBean(JsonpMapper.class).hasSingleBean(SimpleJsonpMapper.class));
}
@Test
void withJsonbShouldDefineJsonbMapper() {
this.contextRunner.withConfiguration(AutoConfigurations.of(JsonbAutoConfiguration.class))
.withUserConfiguration(RestClientConfiguration.class).run((context) -> assertThat(context)
.hasSingleBean(JsonpMapper.class).hasSingleBean(JsonbJsonpMapper.class));
}
@Test
void withJacksonShouldDefineJacksonMapper() {
this.contextRunner.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class))
.withUserConfiguration(RestClientConfiguration.class).run((context) -> assertThat(context)
.hasSingleBean(JsonpMapper.class).hasSingleBean(JacksonJsonpMapper.class));
}
@Test
void withJacksonAndJsonbShouldDefineJacksonMapper() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(JsonbAutoConfiguration.class, JacksonAutoConfiguration.class))
.withUserConfiguration(RestClientConfiguration.class).run((context) -> assertThat(context)
.hasSingleBean(JsonpMapper.class).hasSingleBean(JacksonJsonpMapper.class));
}
@Test
void withCustomMapperTransportShouldUseIt() {
this.contextRunner.withUserConfiguration(JsonpMapperConfiguration.class)
.withUserConfiguration(RestClientConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(JsonpMapper.class).hasBean("customJsonpMapper");
JsonpMapper mapper = context.getBean(JsonpMapper.class);
assertThat(context.getBean(ElasticsearchTransport.class).jsonpMapper()).isSameAs(mapper);
});
}
@Test
void withCustomTransportClientShouldUseIt() {
this.contextRunner.withUserConfiguration(TransportConfiguration.class)
.withUserConfiguration(RestClientConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(ElasticsearchTransport.class)
.hasBean("customElasticsearchTransport");
ElasticsearchTransport transport = context.getBean(ElasticsearchTransport.class);
assertThat(context.getBean(ElasticsearchClient.class)._transport()).isSameAs(transport);
});
}
@Configuration(proxyBeanMethods = false)
static class RestClientConfiguration {
@Bean
RestClient restClient() {
return mock(RestClient.class);
}
}
@Configuration(proxyBeanMethods = false)
static class JsonpMapperConfiguration {
@Bean
JsonpMapper customJsonpMapper() {
return mock(JsonpMapper.class);
}
}
@Configuration(proxyBeanMethods = false)
static class TransportConfiguration {
@Bean
ElasticsearchTransport customElasticsearchTransport() {
return mock(ElasticsearchTransport.class);
}
}
}

View File

@ -18,15 +18,10 @@ package org.springframework.boot.autoconfigure.elasticsearch;
import java.io.InputStream;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.junit.jupiter.api.Test;
@ -58,25 +53,6 @@ class ElasticsearchRestClientAutoConfigurationIntegrationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class));
@Test
@SuppressWarnings("deprecation")
void restHighLevelClientCanQueryElasticsearchNode() {
this.contextRunner
.withPropertyValues("spring.elasticsearch.uris=" + elasticsearch.getHttpHostAddress(),
"spring.elasticsearch.connection-timeout=120s", "spring.elasticsearch.socket-timeout=120s")
.run((context) -> {
org.elasticsearch.client.RestHighLevelClient client = context
.getBean(org.elasticsearch.client.RestHighLevelClient.class);
Map<String, String> source = new HashMap<>();
source.put("a", "alpha");
source.put("b", "bravo");
IndexRequest index = new IndexRequest("test").id("1").source(source);
client.index(index, RequestOptions.DEFAULT);
GetRequest getRequest = new GetRequest("test").id("1");
assertThat(client.get(getRequest, RequestOptions.DEFAULT).isExists()).isTrue();
});
}
@Test
void restClientCanQueryElasticsearchNode() {
this.contextRunner

View File

@ -50,74 +50,32 @@ import static org.mockito.Mockito.mock;
* @author Filip Hrisafov
* @author Andy Wilkinson
*/
@SuppressWarnings("deprecation")
class ElasticsearchRestClientAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class));
@Test
void configureShouldCreateHighLevelAndLowLevelRestClients() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(RestClient.class)
.hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class)
.hasSingleBean(RestClientBuilder.class);
assertThat(context.getBean(RestClient.class))
.isEqualTo(context.getBean(org.elasticsearch.client.RestHighLevelClient.class).getLowLevelClient());
});
}
@Test
void configureWithoutRestHighLevelClientShouldOnlyCreateRestClientBuilderAndRestClient() {
this.contextRunner.withClassLoader(new FilteredClassLoader(org.elasticsearch.client.RestHighLevelClient.class))
.run((context) -> assertThat(context).hasSingleBean(RestClient.class)
.hasSingleBean(RestClientBuilder.class)
.doesNotHaveBean(org.elasticsearch.client.RestHighLevelClient.class));
void configureShouldCreateRestClientBuilderAndRestClient() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(RestClient.class)
.hasSingleBean(RestClientBuilder.class));
}
@Test
void configureWhenCustomRestClientShouldBackOff() {
this.contextRunner.withUserConfiguration(CustomRestClientConfiguration.class)
.run((context) -> assertThat(context)
.doesNotHaveBean(org.elasticsearch.client.RestHighLevelClient.class)
.hasSingleBean(RestClientBuilder.class).hasSingleBean(RestClient.class)
.hasBean("customRestClient"));
}
@Test
void configureWhenCustomRestHighLevelClientShouldDefineRestClientFromCustomHighLevelClient() {
this.contextRunner.withUserConfiguration(CustomRestHighLevelClientConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class)
.hasSingleBean(RestClient.class).hasBean("elasticsearchRestClient").getBean(RestClient.class)
.isEqualTo(context.getBean(org.elasticsearch.client.RestHighLevelClient.class)
.getLowLevelClient()));
}
@Test
void configureWhenCustomRestHighLevelClientAndRestClientShouldBackOff() {
this.contextRunner.withUserConfiguration(CustomRestHighLevelClientWithRestClientConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class)
.hasBean("customRestHighLevelClient").hasSingleBean(RestClient.class)
.hasBean("customRestClient"));
}
@Test
void configureWhenNoUniqueRestHighLevelClientShouldNotDefineRestClient() {
this.contextRunner.withUserConfiguration(TwoCustomRestHighLevelClientsConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean(RestClient.class));
.run((context) -> assertThat(context).hasSingleBean(RestClientBuilder.class)
.hasSingleBean(RestClient.class).hasBean("customRestClient"));
}
@Test
void configureWhenBuilderCustomizerShouldApply() {
this.contextRunner.withUserConfiguration(BuilderCustomizerConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class)
.hasSingleBean(RestClient.class);
org.elasticsearch.client.RestHighLevelClient restClient = context
.getBean(org.elasticsearch.client.RestHighLevelClient.class);
RestClient lowLevelClient = restClient.getLowLevelClient();
assertThat(lowLevelClient).hasFieldOrPropertyWithValue("pathPrefix", "/test");
assertThat(lowLevelClient).extracting("client.connmgr.pool.maxTotal").isEqualTo(100);
assertThat(lowLevelClient).extracting("client.defaultConfig.cookieSpec").isEqualTo("rfc6265-lax");
assertThat(context).hasSingleBean(RestClient.class);
RestClient restClient = context.getBean(RestClient.class);
assertThat(restClient).hasFieldOrPropertyWithValue("pathPrefix", "/test");
assertThat(restClient).extracting("client.connmgr.pool.maxTotal").isEqualTo(100);
assertThat(restClient).extracting("client.defaultConfig.cookieSpec").isEqualTo("rfc6265-lax");
});
}
@ -228,14 +186,12 @@ class ElasticsearchRestClientAutoConfigurationTests {
@Test
void configureWithoutSnifferLibraryShouldNotCreateSniffer() {
this.contextRunner.withClassLoader(new FilteredClassLoader("org.elasticsearch.client.sniff"))
.run((context) -> assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class)
.hasSingleBean(RestClient.class).doesNotHaveBean(Sniffer.class));
.run((context) -> assertThat(context).hasSingleBean(RestClient.class).doesNotHaveBean(Sniffer.class));
}
@Test
void configureShouldCreateSnifferUsingRestClient() {
this.contextRunner.withClassLoader(new FilteredClassLoader(org.elasticsearch.client.RestHighLevelClient.class))
.run((context) -> {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(Sniffer.class);
assertThat(context.getBean(Sniffer.class)).hasFieldOrPropertyWithValue("restClient",
context.getBean(RestClient.class));
@ -297,46 +253,6 @@ class ElasticsearchRestClientAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class CustomRestHighLevelClientConfiguration {
@Bean
org.elasticsearch.client.RestHighLevelClient customRestHighLevelClient(RestClientBuilder builder) {
return new org.elasticsearch.client.RestHighLevelClient(builder);
}
}
@Configuration(proxyBeanMethods = false)
static class CustomRestHighLevelClientWithRestClientConfiguration {
@Bean
org.elasticsearch.client.RestHighLevelClient customRestHighLevelClient(RestClientBuilder builder) {
return new org.elasticsearch.client.RestHighLevelClient(builder);
}
@Bean
RestClient customRestClient(org.elasticsearch.client.RestHighLevelClient restHighLevelClient) {
return restHighLevelClient.getLowLevelClient();
}
}
@Configuration(proxyBeanMethods = false)
static class TwoCustomRestHighLevelClientsConfiguration {
@Bean
org.elasticsearch.client.RestHighLevelClient customRestHighLevelClient(RestClientBuilder builder) {
return new org.elasticsearch.client.RestHighLevelClient(builder);
}
@Bean
org.elasticsearch.client.RestHighLevelClient anotherCustomRestHighLevelClient(RestClientBuilder builder) {
return new org.elasticsearch.client.RestHighLevelClient(builder);
}
}
@Configuration(proxyBeanMethods = false)
static class CustomRestClientConfiguration {

View File

@ -17,21 +17,21 @@
package org.springframework.boot.autoconfigure.elasticsearch;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.index.get.GetResult;
import co.elastic.clients.elasticsearch.core.GetResponse;
import co.elastic.clients.elasticsearch.core.IndexResponse;
import org.junit.jupiter.api.Test;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
import static org.assertj.core.api.Assertions.assertThat;
@ -39,32 +39,33 @@ import static org.assertj.core.api.Assertions.assertThat;
* Integration tests for {@link ReactiveElasticsearchClientAutoConfiguration}.
*
* @author Brian Clozel
* @author Andy Wilkinson
*/
@Testcontainers(disabledWithoutDocker = true)
class ReactiveElasticsearchRestClientAutoConfigurationIntegrationTests {
class ReactiveElasticsearchClientAutoConfigurationIntegrationTests {
@Container
static ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch())
.withStartupAttempts(5).withStartupTimeout(Duration.ofMinutes(10));
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ReactiveElasticsearchClientAutoConfiguration.class));
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
AutoConfigurations.of(JacksonAutoConfiguration.class, ElasticsearchRestClientAutoConfiguration.class,
ReactiveElasticsearchClientAutoConfiguration.class));
@Test
void restClientCanQueryElasticsearchNode() {
void reactiveClientCanQueryElasticsearchNode() {
this.contextRunner
.withPropertyValues("spring.elasticsearch.uris=" + elasticsearch.getHttpHostAddress(),
"spring.elasticsearch.connection-timeout=120s", "spring.elasticsearch.socket-timeout=120s")
.run((context) -> {
ReactiveElasticsearchClient client = context.getBean(ReactiveElasticsearchClient.class);
Map<String, String> source = new HashMap<>();
source.put("a", "alpha");
source.put("b", "bravo");
IndexRequest indexRequest = new IndexRequest("foo").id("1").source(source);
GetRequest getRequest = new GetRequest("foo").id("1");
GetResult getResult = client.index(indexRequest).then(client.get(getRequest)).block();
assertThat(getResult).isNotNull();
assertThat(getResult.isExists()).isTrue();
Mono<IndexResponse> index = client
.index((b) -> b.index("foo").id("1").document(Map.of("a", "alpha", "b", "bravo")));
index.block();
Mono<GetResponse<Object>> get = client.get((b) -> b.index("foo").id("1"), Object.class);
GetResponse<Object> response = get.block();
assertThat(response).isNotNull();
assertThat(response.found()).isTrue();
});
}

View File

@ -16,25 +16,14 @@
package org.springframework.boot.autoconfigure.elasticsearch;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.List;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.elasticsearch.client.RestClient;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient;
import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.CodecConfigurer.DefaultCodecConfig;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@ -43,6 +32,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link ReactiveElasticsearchClientAutoConfiguration}.
*
* @author Brian Clozel
* @author Andy Wilkinson
*/
class ReactiveElasticsearchClientAutoConfigurationTests {
@ -50,206 +40,31 @@ class ReactiveElasticsearchClientAutoConfigurationTests {
.withConfiguration(AutoConfigurations.of(ReactiveElasticsearchClientAutoConfiguration.class));
@Test
void configureShouldCreateDefaultBeans() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(ClientConfiguration.class)
.hasSingleBean(ReactiveElasticsearchClient.class);
List<InetSocketAddress> endpoints = context.getBean(ClientConfiguration.class).getEndpoints();
assertThat(endpoints).hasSize(1);
assertThat(endpoints.get(0).getHostString()).isEqualTo("localhost");
assertThat(endpoints.get(0).getPort()).isEqualTo(9200);
});
void configureWithoutRestClientShouldBackOff() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ReactiveElasticsearchClient.class));
}
@Test
void configureWithRestClientShouldCreateTransportAndClient() {
this.contextRunner.withUserConfiguration(RestClientConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(ReactiveElasticsearchClient.class));
}
@Test
void configureWhenCustomClientShouldBackOff() {
this.contextRunner.withUserConfiguration(CustomClientConfiguration.class).run((context) -> assertThat(context)
.hasSingleBean(ReactiveElasticsearchClient.class).hasBean("customClient"));
}
@Test
void configureWhenCustomClientConfig() {
this.contextRunner.withUserConfiguration(CustomClientConfigConfiguration.class)
this.contextRunner.withUserConfiguration(RestClientConfiguration.class, CustomClientConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(ReactiveElasticsearchClient.class)
.hasSingleBean(ClientConfiguration.class).hasBean("customClientConfiguration"));
.hasBean("customClient"));
}
@Test
void whenUriIsCustomizedThenClientConfigurationHasCustomEndpoint() {
this.contextRunner.withPropertyValues("spring.elasticsearch.uris=http://localhost:9876").run((context) -> {
List<InetSocketAddress> endpoints = context.getBean(ClientConfiguration.class).getEndpoints();
assertThat(endpoints).hasSize(1);
assertThat(endpoints.get(0).getHostString()).isEqualTo("localhost");
assertThat(endpoints.get(0).getPort()).isEqualTo(9876);
});
@Configuration(proxyBeanMethods = false)
static class RestClientConfiguration {
@Bean
RestClient restClient() {
return mock(RestClient.class);
}
@Test
void whenUriHasHttpsSchemeThenClientConfigurationUsesSsl() {
this.contextRunner.withPropertyValues("spring.elasticsearch.uris=https://localhost:9876").run((context) -> {
ClientConfiguration clientConfiguration = context.getBean(ClientConfiguration.class);
List<InetSocketAddress> endpoints = clientConfiguration.getEndpoints();
assertThat(endpoints).hasSize(1);
assertThat(endpoints.get(0).getHostString()).isEqualTo("localhost");
assertThat(endpoints.get(0).getPort()).isEqualTo(9876);
assertThat(clientConfiguration.useSsl()).isTrue();
});
}
@Test
void whenMultipleUrisAreConfiguredThenClientConfigurationHasMultipleEndpoints() {
this.contextRunner.withPropertyValues("spring.elasticsearch.uris=http://localhost:9876,http://localhost:8765")
.run((context) -> {
List<InetSocketAddress> endpoints = context.getBean(ClientConfiguration.class).getEndpoints();
assertThat(endpoints).hasSize(2);
assertThat(endpoints.get(0).getHostString()).isEqualTo("localhost");
assertThat(endpoints.get(0).getPort()).isEqualTo(9876);
assertThat(endpoints.get(1).getHostString()).isEqualTo("localhost");
assertThat(endpoints.get(1).getPort()).isEqualTo(8765);
});
}
@Test
void whenMultipleUrisHaveHttpsSchemeThenClientConfigurationUsesSsl() {
this.contextRunner.withPropertyValues("spring.elasticsearch.uris=https://localhost:9876,https://localhost:8765")
.run((context) -> {
ClientConfiguration clientConfiguration = context.getBean(ClientConfiguration.class);
List<InetSocketAddress> endpoints = clientConfiguration.getEndpoints();
assertThat(endpoints).hasSize(2);
assertThat(endpoints.get(0).getHostString()).isEqualTo("localhost");
assertThat(endpoints.get(0).getPort()).isEqualTo(9876);
assertThat(endpoints.get(1).getHostString()).isEqualTo("localhost");
assertThat(endpoints.get(1).getPort()).isEqualTo(8765);
assertThat(clientConfiguration.useSsl()).isTrue();
});
}
@Test
void whenMultipleUrisHaveVaryingSchemesThenRunFails() {
this.contextRunner.withPropertyValues("spring.elasticsearch.uris=https://localhost:9876,http://localhost:8765")
.run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure().hasRootCauseInstanceOf(IllegalArgumentException.class)
.hasRootCauseMessage("Configured Elasticsearch URIs have varying schemes");
});
}
@Test
void whenUriHasUsernameOnlyThenDefaultAuthorizationHeaderHasUsernameAndEmptyPassword() {
this.contextRunner.withPropertyValues("spring.elasticsearch.uris=http://user@localhost:9200").run((context) -> {
ClientConfiguration clientConfiguration = context.getBean(ClientConfiguration.class);
assertThat(clientConfiguration.getDefaultHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly(
"Basic " + Base64.getEncoder().encodeToString("user:".getBytes(StandardCharsets.UTF_8)));
});
}
@Test
void whenUriHasUsernameAndPasswordThenDefaultAuthorizationHeaderHasUsernameAndPassword() {
this.contextRunner.withPropertyValues("spring.elasticsearch.uris=http://user:secret@localhost:9200")
.run((context) -> {
ClientConfiguration clientConfiguration = context.getBean(ClientConfiguration.class);
assertThat(clientConfiguration.getDefaultHeaders().get(HttpHeaders.AUTHORIZATION))
.containsExactly("Basic " + Base64.getEncoder()
.encodeToString("user:secret".getBytes(StandardCharsets.UTF_8)));
});
}
@Test
void whenMultipleUrisHaveVaryingUserInfosThenRunFails() {
this.contextRunner
.withPropertyValues("spring.elasticsearch.uris=http://user:secret@localhost:9876,http://localhost:8765")
.run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure().hasRootCauseInstanceOf(IllegalArgumentException.class)
.hasRootCauseMessage("Configured Elasticsearch URIs have varying user infos");
});
}
@Test
void whenUriUserInfoMatchesUsernameAndPasswordPropertiesThenDefaultAuthorizationHeaderIsConfigured() {
this.contextRunner.withPropertyValues("spring.elasticsearch.uris=http://user:secret@localhost:9876",
"spring.elasticsearch.username=user", "spring.elasticsearch.password=secret").run((context) -> {
ClientConfiguration clientConfiguration = context.getBean(ClientConfiguration.class);
assertThat(clientConfiguration.getDefaultHeaders().get(HttpHeaders.AUTHORIZATION))
.containsExactly("Basic " + Base64.getEncoder()
.encodeToString("user:secret".getBytes(StandardCharsets.UTF_8)));
});
}
@Test
void whenUriUserInfoAndUsernameAndPasswordPropertiesDoNotMatchThenRunFails() {
this.contextRunner
.withPropertyValues("spring.elasticsearch.uris=http://user:secret@localhost:9876",
"spring.elasticsearch.username=alice", "spring.elasticsearch.password=confidential")
.run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure().hasRootCauseInstanceOf(IllegalArgumentException.class)
.hasRootCauseMessage("Credentials from URI user info do not match those from "
+ "spring.elasticsearch.username and spring.elasticsearch.password");
});
}
@Test
void whenSocketTimeoutIsNotConfiguredThenClientConfigurationUsesDefault() {
this.contextRunner.run((context) -> assertThat(context.getBean(ClientConfiguration.class).getSocketTimeout())
.isEqualTo(Duration.ofSeconds(30)));
}
@Test
void whenConnectionTimeoutIsNotConfiguredThenClientConfigurationUsesDefault() {
this.contextRunner.run((context) -> assertThat(context.getBean(ClientConfiguration.class).getConnectTimeout())
.isEqualTo(Duration.ofSeconds(1)));
}
@Test
void whenSocketTimeoutIsConfiguredThenClientConfigurationHasCustomSocketTimeout() {
this.contextRunner.withPropertyValues("spring.elasticsearch.socket-timeout=2s")
.run((context) -> assertThat(context.getBean(ClientConfiguration.class).getSocketTimeout())
.isEqualTo(Duration.ofSeconds(2)));
}
@Test
void whenConnectionTimeoutIsConfiguredThenClientConfigurationHasCustomConnectTimeout() {
this.contextRunner.withPropertyValues("spring.elasticsearch.connection-timeout=2s")
.run((context) -> assertThat(context.getBean(ClientConfiguration.class).getConnectTimeout())
.isEqualTo(Duration.ofSeconds(2)));
}
@Test
void whenPathPrefixIsConfiguredThenClientConfigurationHasPathPrefix() {
this.contextRunner.withPropertyValues("spring.elasticsearch.path-prefix=/some/prefix")
.run((context) -> assertThat(context.getBean(ClientConfiguration.class).getPathPrefix())
.isEqualTo("/some/prefix"));
}
@Test
void whenCredentialsAreConfiguredThenClientConfigurationHasDefaultAuthorizationHeader() {
this.contextRunner
.withPropertyValues("spring.elasticsearch.username=alice", "spring.elasticsearch.password=secret")
.run((context) -> assertThat(
context.getBean(ClientConfiguration.class).getDefaultHeaders().get(HttpHeaders.AUTHORIZATION))
.containsExactly("Basic YWxpY2U6c2VjcmV0"));
}
@Test
void whenMaxInMemorySizeIsConfiguredThenUnderlyingWebClientHasCustomMaxInMemorySize() {
this.contextRunner.withPropertyValues("spring.elasticsearch.webclient.max-in-memory-size=1MB")
.run((context) -> {
WebClient client = configureWebClient(
context.getBean(ClientConfiguration.class).getClientConfigurers());
assertThat(client).extracting("exchangeFunction.strategies.codecConfigurer.defaultCodecs")
.asInstanceOf(InstanceOfAssertFactories.type(DefaultCodecConfig.class))
.extracting(DefaultCodecConfig::maxInMemorySize).isEqualTo(1024 * 1024);
});
}
@SuppressWarnings("unchecked")
private WebClient configureWebClient(List<ClientConfigurationCallback<?>> callbacks) {
WebClient webClient = WebClient.create();
for (ClientConfigurationCallback<?> callback : callbacks) {
webClient = ((ClientConfiguration.ClientConfigurationCallback<WebClient>) callback).configure(webClient);
}
return webClient;
}
@Configuration(proxyBeanMethods = false)
@ -262,14 +77,4 @@ class ReactiveElasticsearchClientAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class CustomClientConfigConfiguration {
@Bean
ClientConfiguration customClientConfiguration() {
return ClientConfiguration.localhost();
}
}
}

View File

@ -31,7 +31,6 @@ import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
@ -58,8 +57,7 @@ class HttpMessageConvertersTests {
StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
ResourceRegionHttpMessageConverter.class, SourceHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class, MappingJackson2HttpMessageConverter.class,
MappingJackson2SmileHttpMessageConverter.class, MappingJackson2CborHttpMessageConverter.class,
MappingJackson2XmlHttpMessageConverter.class);
MappingJackson2CborHttpMessageConverter.class, MappingJackson2XmlHttpMessageConverter.class);
}
@Test
@ -130,7 +128,7 @@ class HttpMessageConvertersTests {
StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
ResourceRegionHttpMessageConverter.class, SourceHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class, MappingJackson2HttpMessageConverter.class,
MappingJackson2SmileHttpMessageConverter.class, MappingJackson2CborHttpMessageConverter.class);
MappingJackson2CborHttpMessageConverter.class);
}
@Test
@ -151,7 +149,7 @@ class HttpMessageConvertersTests {
}
assertThat(converterClasses).containsExactly(ByteArrayHttpMessageConverter.class,
StringHttpMessageConverter.class, ResourceHttpMessageConverter.class, SourceHttpMessageConverter.class,
MappingJackson2HttpMessageConverter.class, MappingJackson2SmileHttpMessageConverter.class);
MappingJackson2HttpMessageConverter.class);
}
private List<HttpMessageConverter<?>> extractFormPartConverters(List<HttpMessageConverter<?>> converters) {

View File

@ -212,34 +212,20 @@ bom {
]
}
}
library("Elasticsearch", "7.17.5") {
group("org.elasticsearch") {
modules = [
"elasticsearch"
]
}
library("Elasticsearch Client", "8.3.2") {
group("org.elasticsearch.client") {
modules = [
"transport",
"elasticsearch-rest-client" {
exclude group: "commons-logging", module: "commons-logging"
},
"elasticsearch-rest-client-sniffer" {
exclude group: "commons-logging", module: "commons-logging"
},
"elasticsearch-rest-high-level-client"
]
}
group("org.elasticsearch.distribution.integ-test-zip") {
group("co.elastic.clients") {
modules = [
"elasticsearch" {
type = 'zip'
}
]
}
group("org.elasticsearch.plugin") {
modules = [
"transport-netty4-client"
"elasticsearch-java"
]
}
}

View File

@ -999,3 +999,6 @@ dependency-versions.properties=appendix.dependency-versions.properties
# gh-30405
web.servlet.spring-mvc.json=features.json.jackson.custom-serializers-and-deserializers
# gh-28597
data.nosql.elasticsearch.connecting-using-rest.webclient=data.nosql.elasticsearch.connecting-using-rest.reactiveclient

View File

@ -203,7 +203,8 @@ Spring Boot offers basic auto-configuration for Elasticsearch clients.
Spring Boot supports several clients:
* The official Java "Low Level" and "High Level" REST clients
* The official low-level REST client
* The official Java API client
* The `ReactiveElasticsearchClient` provided by Spring Data Elasticsearch
Spring Boot provides a dedicated "`Starter`", `spring-boot-starter-data-elasticsearch`.
@ -212,8 +213,8 @@ Spring Boot provides a dedicated "`Starter`", `spring-boot-starter-data-elastics
[[data.nosql.elasticsearch.connecting-using-rest]]
==== Connecting to Elasticsearch using REST clients
Elasticsearch ships https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html[two different REST clients] that you can use to query a cluster: the low-level client from the `org.elasticsearch.client:elasticsearch-rest-client` module and the high-level client from the `org.elasticsearch.client:elasticsearch-high-level-client` module.
Additionally, Spring Boot provides support for a reactive client, based on Spring Framework's `WebClient`, from the `org.springframework.data:spring-data-elasticsearch` module.
Elasticsearch ships two different REST clients] that you can use to query a cluster: the https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-rest-low.html[low-level client] from the `org.elasticsearch.client:elasticsearch-rest-client` module and the https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html[Java API client] from the `co.elastic.clients:elasticsearch-java` module.
Additionally, Spring Boot provides support for a reactive client from the `org.springframework.data:spring-data-elasticsearch` module.
By default, the clients will target `http://localhost:9200`.
You can use `spring.elasticsearch.*` properties to further tune how the clients are configured, as shown in the following example:
@ -230,9 +231,7 @@ You can use `spring.elasticsearch.*` properties to further tune how the clients
[[data.nosql.elasticsearch.connecting-using-rest.restclient]]
===== Connecting to Elasticsearch using RestClient
If you have `elasticsearch-rest-client` on the classpath, Spring Boot will auto-configure and register a `RestClient` bean.
If you have `elasticsearch-rest-high-level-client` on the classpath a `RestHighLevelClient` bean will be auto-configured as well.
Following Elasticsearch's deprecation of `RestHighLevelClient`, its auto-configuration is deprecated and will be removed in a future release.
In addition to the properties described previously, to fine-tune the `RestClient` and `RestHighLevelClient`, you can register an arbitrary number of beans that implement `RestClientBuilderCustomizer` for more advanced customizations.
In addition to the properties described previously, to fine-tune the `RestClient` you can register an arbitrary number of beans that implement `RestClientBuilderCustomizer` for more advanced customizations.
To take full control over the clients' configuration, define a `RestClientBuilder` bean.
@ -251,38 +250,38 @@ You can further tune how `Sniffer` is configured, as shown in the following exam
----
[[data.nosql.elasticsearch.connecting-using-rest.javaapiclient]]
===== Connecting to Elastixsearch using ElasticsearchClient
If you have `co.elastic.clients:elasticsearch-java` on the classpath, Spring Boot will auto-configure and register an `ElasticsearchClient` bean.
[[data.nosql.elasticsearch.connecting-using-rest.webclient]]
The `ElasticsearchClient` uses a transport that depends upon the previously described `RestClient`.
Therefore, the properties described previously can be used to configure the `ElasticsearchClient`.
Furthermore, you can define a `TransportOptions` bean to take further control of the behavior of the transport.
[[data.nosql.elasticsearch.connecting-using-rest.reactiveclient]]
===== Connecting to Elasticsearch using ReactiveElasticsearchClient
{spring-data-elasticsearch}[Spring Data Elasticsearch] ships `ReactiveElasticsearchClient` for querying Elasticsearch instances in a reactive fashion.
It is built on top of WebFlux's `WebClient`, so both `spring-boot-starter-elasticsearch` and `spring-boot-starter-webflux` dependencies are useful to enable this support.
If you have Spring Data Elasticsearch and Reactor on the classpath, Spring Boot will auto-configure and register a `ReactiveElasticsearchClient`.
By default, Spring Boot will auto-configure and register a `ReactiveElasticsearchClient`.
In addition to the properties described previously, the `spring.elasticsearch.webclient.*` properties can be used to configure reactive-specific settings, as shown in the following example:
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
----
spring:
elasticsearch:
webclient:
max-in-memory-size: "1MB"
----
If the `spring.elasticsearch.*` and `spring.elasticsearch.webclient.*` configuration properties are not enough and you'd like to fully control the client configuration, you can register a custom `ClientConfiguration` bean.
The `ReactiveElasticsearchclient` uses a transport that depends upon the previously described `RestClient`.
Therefore, the properties described previously can be used to configure the `ReactiveElasticsearchClient`.
Furthermore, you can define a `TransportOptions` bean to take further control of the behavior of the transport.
[[data.nosql.elasticsearch.connecting-using-spring-data]]
==== Connecting to Elasticsearch by Using Spring Data
To connect to Elasticsearch, a `RestHighLevelClient` bean must be defined,
To connect to Elasticsearch, an `ElasticsearchClient` bean must be defined,
auto-configured by Spring Boot or manually provided by the application (see previous sections).
With this configuration in place, an
`ElasticsearchRestTemplate` can be injected like any other Spring bean,
`ElasticsearchTemplate` can be injected like any other Spring bean,
as shown in the following example:
include::code:MyBean[]
In the presence of `spring-data-elasticsearch` and the required dependencies for using a `WebClient` (typically `spring-boot-starter-webflux`), Spring Boot can also auto-configure a <<features#data.nosql.elasticsearch.connecting-using-rest.webclient,ReactiveElasticsearchClient>> and a `ReactiveElasticsearchTemplate` as beans.
In the presence of `spring-data-elasticsearch` and Reactor, Spring Boot can also auto-configure a <<features#data.nosql.elasticsearch.connecting-using-rest.reactiveclient,ReactiveElasticsearchClient>> and a `ReactiveElasticsearchTemplate` as beans.
They are the reactive equivalent of the other REST clients.

View File

@ -16,15 +16,15 @@
package org.springframework.boot.docs.data.nosql.elasticsearch.connectingusingspringdata;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.stereotype.Component;
@Component
@SuppressWarnings("deprecation")
public class MyBean {
private final org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate template;
private final ElasticsearchTemplate template;
public MyBean(org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate template) {
public MyBean(ElasticsearchTemplate template) {
this.template = template;
}

View File

@ -101,11 +101,6 @@ dependencies {
testImplementation("org.testcontainers:neo4j")
testImplementation("org.testcontainers:testcontainers")
testImplementation("org.thymeleaf:thymeleaf")
testRuntimeOnly("org.elasticsearch:elasticsearch")
testRuntimeOnly("org.elasticsearch.client:elasticsearch-rest-high-level-client") {
exclude group: "commons-logging", module: "commons-logging"
}
}
configurations {

View File

@ -2,5 +2,8 @@
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration
org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClientAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration

View File

@ -28,6 +28,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
import org.springframework.context.ApplicationContext;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
@ -53,8 +54,7 @@ class DataElasticsearchTestIntegrationTests {
}
@Autowired
@SuppressWarnings("deprecation")
private org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate elasticsearchRestTemplate;
private ElasticsearchTemplate elasticsearchTemplate;
@Autowired
private ExampleRepository exampleRepository;
@ -75,7 +75,7 @@ class DataElasticsearchTestIntegrationTests {
String id = UUID.randomUUID().toString();
document.setId(id);
ExampleDocument savedDocument = this.exampleRepository.save(document);
ExampleDocument getDocument = this.elasticsearchRestTemplate.get(id, ExampleDocument.class);
ExampleDocument getDocument = this.elasticsearchTemplate.get(id, ExampleDocument.class);
assertThat(getDocument).isNotNull();
assertThat(getDocument.getId()).isNotNull();
assertThat(getDocument.getId()).isEqualTo(savedDocument.getId());

View File

@ -25,6 +25,7 @@ import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
@ -50,8 +51,7 @@ class DataElasticsearchTestReactiveIntegrationTests {
}
@Autowired
@SuppressWarnings("deprecation")
private org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchTemplate elasticsearchTemplate;
private ReactiveElasticsearchTemplate elasticsearchTemplate;
@Autowired
private ExampleReactiveRepository exampleReactiveRepository;

View File

@ -16,6 +16,7 @@
package org.springframework.boot.test.autoconfigure.data.elasticsearch;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.stereotype.Service;
/**
@ -24,18 +25,16 @@ import org.springframework.stereotype.Service;
* @author Eddú Meléndez
*/
@Service
@SuppressWarnings("deprecation")
public class ExampleService {
private final org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate elasticsearchRestTemplate;
private final ElasticsearchTemplate elasticsearchTemplate;
public ExampleService(
org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate elasticsearchRestTemplate) {
this.elasticsearchRestTemplate = elasticsearchRestTemplate;
public ExampleService(ElasticsearchTemplate elasticsearchRestTemplate) {
this.elasticsearchTemplate = elasticsearchRestTemplate;
}
public ExampleDocument findById(String id) {
return this.elasticsearchRestTemplate.get(id, ExampleDocument.class);
return this.elasticsearchTemplate.get(id, ExampleDocument.class);
}
}

View File

@ -13,7 +13,6 @@ dependencies {
}
compileOnly("jakarta.servlet:jakarta.servlet-api")
compileOnly("junit:junit")
compileOnly("org.elasticsearch:elasticsearch")
compileOnly("org.junit.jupiter:junit-jupiter")
compileOnly("org.junit.platform:junit-platform-engine")
compileOnly("org.junit.platform:junit-platform-launcher")

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -30,6 +30,8 @@ public final class DockerImageNames {
private static final String COUCHBASE_VERSION = "6.5.1";
private static final String ELASTICSEARCH_VERSION = "7.17.5";
private static final String MONGO_VERSION = "4.0.23";
private static final String NEO4J_VERSION = "4.0";
@ -60,13 +62,11 @@ public final class DockerImageNames {
}
/**
* Return a {@link DockerImageName} suitable for running Elasticsearch according to
* the version available on the classpath.
* Return a {@link DockerImageName} suitable for running Elasticsearch.
* @return a docker image name for running elasticsearch
*/
public static DockerImageName elasticsearch() {
String version = org.elasticsearch.Version.CURRENT.toString();
return DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch").withTag(version);
return DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch").withTag(ELASTICSEARCH_VERSION);
}
/**