From 5c057a2730738fd8d9f5cf301b24d543c92effe1 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 12 Jul 2022 14:30:49 +0100 Subject: [PATCH] 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 --- .../build.gradle | 4 - ...veHealthContributorAutoConfiguration.java} | 4 +- ...stHealthContributorAutoConfiguration.java} | 2 +- ...ot.autoconfigure.AutoConfiguration.imports | 4 +- ...althContributorAutoConfigurationTests.java | 6 +- ...lthContributorAutoConfigurationTests.java} | 38 +-- .../spring-boot-actuator/build.gradle | 4 - .../ElasticsearchReactiveHealthIndicator.java | 63 ++--- ...ticsearchReactiveHealthIndicatorTests.java | 56 ++--- .../spring-boot-autoconfigure/build.gradle | 6 +- .../ElasticsearchDataAutoConfiguration.java | 11 +- .../ElasticsearchDataConfiguration.java | 28 +-- ...icsearchRepositoriesAutoConfiguration.java | 4 +- ...icsearchRepositoriesAutoConfiguration.java | 2 +- .../ElasticsearchClientAutoConfiguration.java | 42 ++++ .../ElasticsearchClientConfigurations.java | 107 ++++++++ .../ElasticsearchProperties.java | 25 -- ...sticsearchRestClientAutoConfiguration.java | 6 +- ...ElasticsearchRestClientConfigurations.java | 30 --- ...eElasticsearchClientAutoConfiguration.java | 196 +-------------- ...itional-spring-configuration-metadata.json | 9 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + ...asticsearchDataAutoConfigurationTests.java | 39 ++- ...rchRepositoriesAutoConfigurationTests.java | 11 +- ...rchRepositoriesAutoConfigurationTests.java | 9 +- ...ientAutoConfigurationIntegrationTests.java | 66 +++++ ...ticsearchClientAutoConfigurationTests.java | 141 +++++++++++ ...ientAutoConfigurationIntegrationTests.java | 24 -- ...earchRestClientAutoConfigurationTests.java | 124 ++-------- ...entAutoConfigurationIntegrationTests.java} | 35 +-- ...ticsearchClientAutoConfigurationTests.java | 233 ++---------------- .../http/HttpMessageConvertersTests.java | 8 +- .../spring-boot-dependencies/build.gradle | 20 +- .../docs/asciidoc/anchor-rewrite.properties | 5 +- .../src/docs/asciidoc/data/nosql.adoc | 45 ++-- .../connectingusingspringdata/MyBean.java | 6 +- .../spring-boot-parent/build.gradle | 2 +- .../build.gradle | 5 - ...rch.AutoConfigureDataElasticsearch.imports | 3 + ...DataElasticsearchTestIntegrationTests.java | 6 +- ...ticsearchTestReactiveIntegrationTests.java | 4 +- .../data/elasticsearch/ExampleService.java | 11 +- .../spring-boot-test-support/build.gradle | 1 - .../testcontainers/DockerImageNames.java | 10 +- 44 files changed, 604 insertions(+), 852 deletions(-) rename spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/{ElasticSearchReactiveHealthContributorAutoConfiguration.java => ElasticsearchReactiveHealthContributorAutoConfiguration.java} (94%) rename spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/{ElasticSearchRestHealthContributorAutoConfiguration.java => ElasticsearchRestHealthContributorAutoConfiguration.java} (97%) rename spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/{ElasticSearchRestHealthContributorAutoConfigurationTests.java => ElasticsearchRestHealthContributorAutoConfigurationTests.java} (68%) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientConfigurations.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationIntegrationTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationTests.java rename spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/{ReactiveElasticsearchRestClientAutoConfigurationIntegrationTests.java => ReactiveElasticsearchClientAutoConfigurationIntegrationTests.java} (65%) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index 97c1fcafa1..552e51be62 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -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") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchReactiveHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfiguration.java similarity index 94% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchReactiveHealthContributorAutoConfiguration.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfiguration.java index e9ff1c9e5e..ab3c640b7b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchReactiveHealthContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfiguration.java @@ -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 { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchRestHealthContributorAutoConfiguration.java similarity index 97% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfiguration.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchRestHealthContributorAutoConfiguration.java index adc2194cd7..c76c1e5752 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchRestHealthContributorAutoConfiguration.java @@ -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 { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 6fdfc58211..c6f4f89c4f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -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 diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfigurationTests.java index 3b9646ba62..fbe84d42af 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfigurationTests.java @@ -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)); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchRestHealthContributorAutoConfigurationTests.java similarity index 68% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfigurationTests.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchRestHealthContributorAutoConfigurationTests.java index 5c1f4d9aa4..a1294a97f7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchRestHealthContributorAutoConfigurationTests.java @@ -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(); - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator/build.gradle b/spring-boot-project/spring-boot-actuator/build.gradle index def8ded437..050afcf1e4 100644 --- a/spring-boot-project/spring-boot-actuator/build.gradle +++ b/spring-boot-project/spring-boot-actuator/build.gradle @@ -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") diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchReactiveHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchReactiveHealthIndicator.java index a64bce3877..93a872777d 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchReactiveHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchReactiveHealthIndicator.java @@ -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> 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 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 getHealth(Health.Builder builder, WebClient webClient) { - return webClient.get().uri("/_cluster/health/").exchangeToMono((response) -> doHealthCheck(builder, response)); - } - - private Mono 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)); + 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(); } - 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 body) { - String status = (String) body.get("status"); - builder.status(RED_STATUS.equals(status) ? Status.OUT_OF_SERVICE : Status.UP); - builder.withDetails(body); - return builder.build(); + return builder.down().build(); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchReactiveHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchReactiveHealthIndicatorTests.java index 87a9cdfc9e..cbdcba1753 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchReactiveHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchReactiveHealthIndicatorTests.java @@ -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,32 +115,25 @@ 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) { - return String.format( - "{\"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," - + "\"number_of_pending_tasks\":0,\"number_of_in_flight_fetch\":0," - + "\"task_max_waiting_in_queue_millis\":0,\"active_shards_percent_as_number\":100.0}", - status); - } - return "{\n \"error\": \"Server Error\",\n \"status\": " + responseCode + "\n}"; + private String createJsonResult(String status) { + return String.format( + "{\"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," + + "\"number_of_pending_tasks\":0,\"number_of_in_flight_fetch\":0," + + "\"task_max_waiting_in_queue_millis\":0,\"active_shards_percent_as_number\":100.0}", + status); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index c2024fa838..1bcee1cd16 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -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") diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataAutoConfiguration.java index 417a6ee409..dd5692b993 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataAutoConfiguration.java @@ -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 { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataConfiguration.java index 1fc7d1b5e4..ab544b13b1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataConfiguration.java @@ -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); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfiguration.java index 4bee66b094..ed6a7fe229 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfiguration.java @@ -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) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfiguration.java index b2ea0ceb88..415b5856f7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfiguration.java @@ -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; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfiguration.java new file mode 100644 index 0000000000..66f17d4c5a --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfiguration.java @@ -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 { + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientConfigurations.java new file mode 100644 index 0000000000..19569fb001 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientConfigurations.java @@ -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) { + 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); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchProperties.java index 4b155fd63f..38b0b0e4a2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchProperties.java @@ -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 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; - } - - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfiguration.java index 9ae88f3cdb..bbf626f07e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfiguration.java @@ -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 { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java index c12c53f5b4..c7b64d07c0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java @@ -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 { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfiguration.java index 02c61a6a1a..25ceac1186 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfiguration.java @@ -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 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 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 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 uris) { - Set 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); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 8e1d611ccb..a3bbf6c0ee 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -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" diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 3be3ff4dd2..139e872652 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -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 diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataAutoConfigurationTests.java index 20f17db761..8636229c7e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataAutoConfigurationTests.java @@ -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 { + static class MyConverter implements Converter { @Override - public Boolean convert(ElasticsearchRestTemplate source) { + public Boolean convert(ElasticsearchTemplate source) { return null; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java index 48a43075ae..8b5f407388 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java @@ -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 diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java index c9b5ab278c..dda40980ed 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java @@ -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(), diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationIntegrationTests.java new file mode 100644 index 0000000000..2684f3cf05 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationIntegrationTests.java @@ -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 response = client.get((b) -> b.index("foo").id("1"), Object.class); + assertThat(response).isNotNull(); + assertThat(response.found()).isTrue(); + }); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationTests.java new file mode 100644 index 0000000000..c15a90c6e2 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationTests.java @@ -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); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java index e979c4d1d8..31e5fc62b6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java @@ -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 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 diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java index 3a8f1723a0..5343e11953 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java @@ -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,22 +186,20 @@ 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) -> { - assertThat(context).hasSingleBean(Sniffer.class); - assertThat(context.getBean(Sniffer.class)).hasFieldOrPropertyWithValue("restClient", - context.getBean(RestClient.class)); - // Validate shutdown order as the sniffer must be shutdown before the - // client - assertThat(context.getBeanFactory().getDependentBeans("elasticsearchRestClient")) - .contains("elasticsearchSniffer"); - }); + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(Sniffer.class); + assertThat(context.getBean(Sniffer.class)).hasFieldOrPropertyWithValue("restClient", + context.getBean(RestClient.class)); + // Validate shutdown order as the sniffer must be shutdown before the + // client + assertThat(context.getBeanFactory().getDependentBeans("elasticsearchRestClient")) + .contains("elasticsearchSniffer"); + }); } @Test @@ -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 { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchRestClientAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationIntegrationTests.java similarity index 65% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchRestClientAutoConfigurationIntegrationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationIntegrationTests.java index 969d68cbb2..26a64cba64 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchRestClientAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationIntegrationTests.java @@ -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 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 index = client + .index((b) -> b.index("foo").id("1").document(Map.of("a", "alpha", "b", "bravo"))); + index.block(); + Mono> get = client.get((b) -> b.index("foo").id("1"), Object.class); + GetResponse response = get.block(); + assertThat(response).isNotNull(); + assertThat(response.found()).isTrue(); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationTests.java index 4df9e6c9eb..cb5f89d8e3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationTests.java @@ -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 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 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 { - @Test - void whenUriHasHttpsSchemeThenClientConfigurationUsesSsl() { - this.contextRunner.withPropertyValues("spring.elasticsearch.uris=https://localhost:9876").run((context) -> { - ClientConfiguration clientConfiguration = context.getBean(ClientConfiguration.class); - List 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 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 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> callbacks) { - WebClient webClient = WebClient.create(); - for (ClientConfigurationCallback callback : callbacks) { - webClient = ((ClientConfiguration.ClientConfigurationCallback) callback).configure(webClient); + @Bean + RestClient restClient() { + return mock(RestClient.class); } - return webClient; + } @Configuration(proxyBeanMethods = false) @@ -262,14 +77,4 @@ class ReactiveElasticsearchClientAutoConfigurationTests { } - @Configuration(proxyBeanMethods = false) - static class CustomClientConfigConfiguration { - - @Bean - ClientConfiguration customClientConfiguration() { - return ClientConfiguration.localhost(); - } - - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersTests.java index 669e2c7e76..103bbf3962 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersTests.java @@ -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> extractFormPartConverters(List> converters) { diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 3acb542c5c..6f3faf7b9a 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -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" ] } } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties index 990226259e..7f3437caef 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties @@ -998,4 +998,7 @@ dependency-versions.coordinates=appendix.dependency-versions.coordinates dependency-versions.properties=appendix.dependency-versions.properties # gh-30405 -web.servlet.spring-mvc.json=features.json.jackson.custom-serializers-and-deserializers \ No newline at end of file +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 diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc index 682b7f7b0b..3440b9b8cb 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc @@ -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 <> and a `ReactiveElasticsearchTemplate` as beans. +In the presence of `spring-data-elasticsearch` and Reactor, Spring Boot can also auto-configure a <> and a `ReactiveElasticsearchTemplate` as beans. They are the reactive equivalent of the other REST clients. diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/data/nosql/elasticsearch/connectingusingspringdata/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/data/nosql/elasticsearch/connectingusingspringdata/MyBean.java index 7a54d1cd24..95fd932409 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/data/nosql/elasticsearch/connectingusingspringdata/MyBean.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/data/nosql/elasticsearch/connectingusingspringdata/MyBean.java @@ -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; } diff --git a/spring-boot-project/spring-boot-parent/build.gradle b/spring-boot-project/spring-boot-parent/build.gradle index e62b98650c..40955f6f4a 100644 --- a/spring-boot-project/spring-boot-parent/build.gradle +++ b/spring-boot-project/spring-boot-parent/build.gradle @@ -134,7 +134,7 @@ bom { modules = [ "native-gradle-plugin" ] - } + } } library("Plexus Build API", "0.0.7") { group("org.sonatype.plexus") { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/build.gradle b/spring-boot-project/spring-boot-test-autoconfigure/build.gradle index d398fef2c0..cd58756e58 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-test-autoconfigure/build.gradle @@ -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 { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.data.elasticsearch.AutoConfigureDataElasticsearch.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.data.elasticsearch.AutoConfigureDataElasticsearch.imports index 24274c9ff9..9e23ddfa32 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.data.elasticsearch.AutoConfigureDataElasticsearch.imports +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.data.elasticsearch.AutoConfigureDataElasticsearch.imports @@ -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 diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java index 9ad2f73c23..059b7061b3 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java @@ -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()); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java index 5c180fc16a..794b42a2ce 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java @@ -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; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleService.java index 58ee668b06..778af7cf0e 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleService.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleService.java @@ -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); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-test-support/build.gradle index a185547f10..1a6ff9d08c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/build.gradle @@ -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") diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/DockerImageNames.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/DockerImageNames.java index 36803822c3..bea2bda637 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/DockerImageNames.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/DockerImageNames.java @@ -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); } /**