Auto-configure the new Elasticsearch clients

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -43,13 +43,9 @@ dependencies {
optional("org.eclipse.jetty:jetty-server") { optional("org.eclipse.jetty:jetty-server") {
exclude(group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api") exclude(group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api")
} }
optional("org.elasticsearch:elasticsearch")
optional("org.elasticsearch.client:elasticsearch-rest-client") { optional("org.elasticsearch.client:elasticsearch-rest-client") {
exclude(group: "commons-logging", module: "commons-logging") 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-core")
optional("org.hibernate.validator:hibernate-validator") optional("org.hibernate.validator:hibernate-validator")
optional("org.influxdb:influxdb-java") optional("org.influxdb:influxdb-java")

View File

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

View File

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

View File

@ -12,6 +12,9 @@ description = "Spring Boot AutoConfigure"
dependencies { dependencies {
api(project(":spring-boot-project:spring-boot")) 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.core:jackson-databind")
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor") optional("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor")
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml") optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
@ -99,9 +102,6 @@ dependencies {
optional("org.elasticsearch.client:elasticsearch-rest-client-sniffer") { optional("org.elasticsearch.client:elasticsearch-rest-client-sniffer") {
exclude group: "commons-logging", module: "commons-logging" 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-core")
optional("org.flywaydb:flyway-sqlserver") optional("org.flywaydb:flyway-sqlserver")
optional("org.freemarker:freemarker") optional("org.freemarker:freemarker")

View File

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

View File

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

View File

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

View File

@ -22,7 +22,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Import; 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.ReactiveElasticsearchRepository;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories; import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import org.springframework.data.elasticsearch.repository.support.ReactiveElasticsearchRepositoryFactoryBean; import org.springframework.data.elasticsearch.repository.support.ReactiveElasticsearchRepositoryFactoryBean;

View File

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

View File

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

View File

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

View File

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

View File

@ -35,7 +35,6 @@ import org.elasticsearch.client.sniff.SnifferBuilder;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 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.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -109,36 +108,7 @@ class ElasticsearchRestClientConfigurations {
} }
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false) @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) @ConditionalOnMissingBean(RestClient.class)
static class RestClientConfiguration { static class RestClientConfiguration {

View File

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

View File

@ -981,6 +981,15 @@
"http://localhost:9200" "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", "name": "spring.flyway.baseline-migration-prefix",
"defaultValue": "B" "defaultValue": "B"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -203,7 +203,8 @@ Spring Boot offers basic auto-configuration for Elasticsearch clients.
Spring Boot supports several clients: 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 * The `ReactiveElasticsearchClient` provided by Spring Data Elasticsearch
Spring Boot provides a dedicated "`Starter`", `spring-boot-starter-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]] [[data.nosql.elasticsearch.connecting-using-rest]]
==== Connecting to Elasticsearch using REST clients ==== 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. 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, based on Spring Framework's `WebClient`, from the `org.springframework.data:spring-data-elasticsearch` 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`. 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: 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]] [[data.nosql.elasticsearch.connecting-using-rest.restclient]]
===== Connecting to Elasticsearch using 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-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. 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.
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.
To take full control over the clients' configuration, define a `RestClientBuilder` bean. 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 ===== Connecting to Elasticsearch using ReactiveElasticsearchClient
{spring-data-elasticsearch}[Spring Data Elasticsearch] ships `ReactiveElasticsearchClient` for querying Elasticsearch instances in a reactive fashion. {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`. The `ReactiveElasticsearchclient` uses a transport that depends upon the previously described `RestClient`.
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: 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.
[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.
[[data.nosql.elasticsearch.connecting-using-spring-data]] [[data.nosql.elasticsearch.connecting-using-spring-data]]
==== Connecting to Elasticsearch by 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). auto-configured by Spring Boot or manually provided by the application (see previous sections).
With this configuration in place, an 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: as shown in the following example:
include::code:MyBean[] include::code:MyBean[]
In the presence of `spring-data-elasticsearch` and the required dependencies for using a `WebClient` (typically `spring-boot-starter-webflux`), Spring Boot can also auto-configure a <<features#data.nosql.elasticsearch.connecting-using-rest.webclient,ReactiveElasticsearchClient>> and a `ReactiveElasticsearchTemplate` as beans. In the presence of `spring-data-elasticsearch` and Reactor, Spring Boot can also auto-configure a <<features#data.nosql.elasticsearch.connecting-using-rest.reactiveclient,ReactiveElasticsearchClient>> and a `ReactiveElasticsearchTemplate` as beans.
They are the reactive equivalent of the other REST clients. They are the reactive equivalent of the other REST clients.

View File

@ -16,15 +16,15 @@
package org.springframework.boot.docs.data.nosql.elasticsearch.connectingusingspringdata; package org.springframework.boot.docs.data.nosql.elasticsearch.connectingusingspringdata;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
@SuppressWarnings("deprecation")
public class MyBean { 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; this.template = template;
} }

View File

@ -101,11 +101,6 @@ dependencies {
testImplementation("org.testcontainers:neo4j") testImplementation("org.testcontainers:neo4j")
testImplementation("org.testcontainers:testcontainers") testImplementation("org.testcontainers:testcontainers")
testImplementation("org.thymeleaf:thymeleaf") 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 { configurations {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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