Consolidate Elasticsearch configuration properties

Previously, a number of Elasticsearch properties were duplicated
across the spring.elasticsearch.rest and
spring.data.elasticsearch.client.reactive prefixes for configuring
the blocking REST client provided by Elasticsearch and the reactive
client provided by Spring Data respectively. This could cause
problems when using the Elasticsearch REST client configured with
a custom spring.elasticsearch.rest.uris. If Spring WebFlux (to make
use of WebClient) and Spring Data Elasticsearch were on the classpath,
the reactive Elasticsearch Client would be autoconfigured but it
would use the default value of its analogous
spring.data.elasticsearch.client.reactive.endpoints property. It
would be unable to connect, causing a startup failure.

This commit consoliates the configuration properties where possible.
Each setting that is common across the two clients is now configured
using a single, shared spring.elasticsearch property. Each setting
that is specific to the blocked REST client or the WebClient-based
reactive client now have prefixes of spring.elasticsearch.restclient
and spring.elasticsearch.webclient respectively.

The old properties beneath spring.elasticsearch.rest and
spring.data.elasticsearch.client.reactive have been deprecated. If a
any deprecated property is set, all of the new properties are
ignored. In other words, to migrate to the new properties, each usage
of a now-deprecated property must be updated to use its new
replacement instead.

Closes gh-23106
pull/28071/head
Andy Wilkinson 3 years ago
parent dd366af849
commit e2a355f003

@ -0,0 +1,150 @@
/*
* Copyright 2012-2021 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.data.elasticsearch;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.util.unit.DataSize;
/**
* Deprecated configuration properties for Elasticsearch Reactive REST clients.
*
* @author Brian Clozel
* @deprecated since 2.6.0 for removal in 2.8.0
*/
@Deprecated
@ConfigurationProperties(prefix = "spring.data.elasticsearch.client.reactive")
class DeprecatedReactiveElasticsearchRestClientProperties {
/**
* Comma-separated list of the Elasticsearch endpoints to connect to.
*/
private List<String> endpoints = new ArrayList<>(Collections.singletonList("localhost:9200"));
/**
* Whether the client should use SSL to connect to the endpoints.
*/
private boolean useSsl = false;
/**
* Credentials username.
*/
private String username;
/**
* Credentials password.
*/
private String password;
/**
* Connection timeout.
*/
private Duration connectionTimeout;
/**
* Read and Write Socket timeout.
*/
private Duration socketTimeout;
/**
* Limit on the number of bytes that can be buffered whenever the input stream needs
* to be aggregated.
*/
private DataSize maxInMemorySize;
private boolean customized = false;
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.uris")
public List<String> getEndpoints() {
return this.endpoints;
}
public void setEndpoints(List<String> endpoints) {
this.customized = true;
this.endpoints = endpoints;
}
@DeprecatedConfigurationProperty(reason = "Use of SSL should be indicated through an https URI scheme")
public boolean isUseSsl() {
return this.useSsl;
}
public void setUseSsl(boolean useSsl) {
this.customized = true;
this.useSsl = useSsl;
}
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.username")
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.customized = true;
this.username = username;
}
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.password")
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.customized = true;
this.password = password;
}
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.connection-timeout")
public Duration getConnectionTimeout() {
return this.connectionTimeout;
}
public void setConnectionTimeout(Duration connectionTimeout) {
this.customized = true;
this.connectionTimeout = connectionTimeout;
}
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.socket-timeout")
public Duration getSocketTimeout() {
return this.socketTimeout;
}
public void setSocketTimeout(Duration socketTimeout) {
this.customized = true;
this.socketTimeout = socketTimeout;
}
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.webclient.max-in-memory-size")
public DataSize getMaxInMemorySize() {
return this.maxInMemorySize;
}
public void setMaxInMemorySize(DataSize maxInMemorySize) {
this.customized = true;
this.maxInMemorySize = maxInMemorySize;
}
boolean isCustomized() {
return this.customized;
}
}

@ -16,11 +16,18 @@
package org.springframework.boot.autoconfigure.data.elasticsearch;
import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import reactor.netty.http.client.HttpClient;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
@ -29,6 +36,7 @@ import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients.WebClientConfigurationCallback;
import org.springframework.util.Assert;
import org.springframework.util.unit.DataSize;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
@ -40,30 +48,39 @@ import org.springframework.web.reactive.function.client.WebClient;
* @author Brian Clozel
* @since 2.2.0
*/
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ReactiveRestClients.class, WebClient.class, HttpClient.class })
@EnableConfigurationProperties(ReactiveElasticsearchRestClientProperties.class)
@EnableConfigurationProperties({ ElasticsearchProperties.class, ReactiveElasticsearchRestClientProperties.class,
DeprecatedReactiveElasticsearchRestClientProperties.class })
public class ReactiveElasticsearchRestClientAutoConfiguration {
private final ConsolidatedProperties properties;
ReactiveElasticsearchRestClientAutoConfiguration(ElasticsearchProperties properties,
ReactiveElasticsearchRestClientProperties restClientProperties,
DeprecatedReactiveElasticsearchRestClientProperties reactiveProperties) {
this.properties = new ConsolidatedProperties(properties, restClientProperties, reactiveProperties);
}
@Bean
@ConditionalOnMissingBean
public ClientConfiguration clientConfiguration(ReactiveElasticsearchRestClientProperties properties) {
public ClientConfiguration clientConfiguration() {
ClientConfiguration.MaybeSecureClientConfigurationBuilder builder = ClientConfiguration.builder()
.connectedTo(properties.getEndpoints().toArray(new String[0]));
.connectedTo(this.properties.getEndpoints().toArray(new String[0]));
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties.isUseSsl()).whenTrue().toCall(builder::usingSsl);
map.from(properties.getUsername()).whenHasText()
.to((username) -> builder.withBasicAuth(username, properties.getPassword()));
map.from(properties.getConnectionTimeout()).to(builder::withConnectTimeout);
map.from(properties.getSocketTimeout()).to(builder::withSocketTimeout);
configureExchangeStrategies(map, builder, properties);
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);
configureExchangeStrategies(map, builder);
return builder.build();
}
private void configureExchangeStrategies(PropertyMapper map,
ClientConfiguration.TerminalClientConfigurationBuilder builder,
ReactiveElasticsearchRestClientProperties properties) {
map.from(properties.getMaxInMemorySize()).asInt(DataSize::toBytes).to((maxInMemorySize) -> {
ClientConfiguration.TerminalClientConfigurationBuilder builder) {
map.from(this.properties.getMaxInMemorySize()).asInt(DataSize::toBytes).to((maxInMemorySize) -> {
builder.withClientConfigurer(WebClientConfigurationCallback.from((webClient) -> {
ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
.codecs((configurer) -> configurer.defaultCodecs().maxInMemorySize(maxInMemorySize)).build();
@ -78,4 +95,166 @@ public class ReactiveElasticsearchRestClientAutoConfiguration {
return ReactiveRestClients.create(clientConfiguration);
}
private static final class ConsolidatedProperties {
private final ElasticsearchProperties properties;
private final ReactiveElasticsearchRestClientProperties restClientProperties;
private final DeprecatedReactiveElasticsearchRestClientProperties deprecatedProperties;
private final List<URI> uris;
private ConsolidatedProperties(ElasticsearchProperties properties,
ReactiveElasticsearchRestClientProperties restClientProperties,
DeprecatedReactiveElasticsearchRestClientProperties deprecatedreactiveProperties) {
this.properties = properties;
this.restClientProperties = restClientProperties;
this.deprecatedProperties = deprecatedreactiveProperties;
this.uris = properties.getUris().stream().map((s) -> s.startsWith("http") ? s : "http://" + s)
.map(URI::create).collect(Collectors.toList());
}
private List<String> getEndpoints() {
if (this.deprecatedProperties.isCustomized()) {
return this.deprecatedProperties.getEndpoints();
}
return this.uris.stream().map((uri) -> uri.getHost() + ":" + uri.getPort()).collect(Collectors.toList());
}
private Credentials getCredentials() {
if (this.deprecatedProperties.isCustomized()) {
return Credentials.from(this.deprecatedProperties);
}
Credentials propertyCredentials = Credentials.from(this.properties);
Credentials uriCredentials = Credentials.from(this.properties.getUris());
if (uriCredentials == null) {
return propertyCredentials;
}
if (propertyCredentials != null && !uriCredentials.equals(propertyCredentials)) {
throw new IllegalArgumentException(
"Credentials from URI user info do not match those from spring.elasticsearch.username and "
+ "spring.elasticsearch.password");
}
return uriCredentials;
}
private Duration getConnectionTimeout() {
return this.deprecatedProperties.isCustomized() ? this.deprecatedProperties.getConnectionTimeout()
: this.properties.getConnectionTimeout();
}
private Duration getSocketTimeout() {
return this.deprecatedProperties.isCustomized() ? this.deprecatedProperties.getSocketTimeout()
: this.properties.getSocketTimeout();
}
private boolean isUseSsl() {
if (this.deprecatedProperties.isCustomized()) {
return this.deprecatedProperties.isUseSsl();
}
Set<String> schemes = this.uris.stream().map((uri) -> 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.deprecatedProperties.isCustomized() ? this.deprecatedProperties.getMaxInMemorySize()
: this.restClientProperties.getMaxInMemorySize();
}
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<String> uris) {
Set<String> userInfos = uris.stream().map(URI::create).map((uri) -> 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) {
String[] parts = userInfo.split(":");
return new Credentials(parts[0], (parts.length == 2) ? parts[1] : "");
}
return null;
}
private static Credentials from(ElasticsearchProperties properties) {
String username = properties.getUsername();
String password = properties.getPassword();
if (username == null && password == null) {
return null;
}
return new Credentials(username, password);
}
private static Credentials from(DeprecatedReactiveElasticsearchRestClientProperties properties) {
String username = properties.getUsername();
String password = properties.getPassword();
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) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Credentials other = (Credentials) obj;
if (this.password == null) {
if (other.password != null) {
return false;
}
}
else if (!this.password.equals(other.password)) {
return false;
}
if (this.username == null) {
if (other.username != null) {
return false;
}
}
else if (!this.username.equals(other.username)) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.password == null) ? 0 : this.password.hashCode());
result = prime * result + ((this.username == null) ? 0 : this.username.hashCode());
return result;
}
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@ -16,11 +16,6 @@
package org.springframework.boot.autoconfigure.data.elasticsearch;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.unit.DataSize;
@ -30,93 +25,15 @@ import org.springframework.util.unit.DataSize;
* @author Brian Clozel
* @since 2.2.0
*/
@ConfigurationProperties(prefix = "spring.data.elasticsearch.client.reactive")
@ConfigurationProperties(prefix = "spring.elasticsearch.webclient")
public class ReactiveElasticsearchRestClientProperties {
/**
* Comma-separated list of the Elasticsearch endpoints to connect to.
*/
private List<String> endpoints = new ArrayList<>(Collections.singletonList("localhost:9200"));
/**
* Whether the client should use SSL to connect to the endpoints.
*/
private boolean useSsl = false;
/**
* Credentials username.
*/
private String username;
/**
* Credentials password.
*/
private String password;
/**
* Connection timeout.
*/
private Duration connectionTimeout;
/**
* Read and Write Socket timeout.
*/
private Duration socketTimeout;
/**
* Limit on the number of bytes that can be buffered whenever the input stream needs
* to be aggregated.
*/
private DataSize maxInMemorySize;
public List<String> getEndpoints() {
return this.endpoints;
}
public void setEndpoints(List<String> endpoints) {
this.endpoints = endpoints;
}
public boolean isUseSsl() {
return this.useSsl;
}
public void setUseSsl(boolean useSsl) {
this.useSsl = useSsl;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public Duration getConnectionTimeout() {
return this.connectionTimeout;
}
public void setConnectionTimeout(Duration connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public Duration getSocketTimeout() {
return this.socketTimeout;
}
public void setSocketTimeout(Duration socketTimeout) {
this.socketTimeout = socketTimeout;
}
public DataSize getMaxInMemorySize() {
return this.maxInMemorySize;
}

@ -0,0 +1,159 @@
/*
* Copyright 2012-2021 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.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
/**
* Deprecated configuration properties for Elasticsearch REST clients.
*
* @author Brian Clozel
* @deprecated since 2.6.0 for removal in 2.8.0.
*/
@ConfigurationProperties(prefix = "spring.elasticsearch.rest")
@Deprecated
class DeprecatedElasticsearchRestClientProperties {
/**
* Comma-separated list of the Elasticsearch instances to use.
*/
private List<String> uris = new ArrayList<>(Collections.singletonList("http://localhost:9200"));
/**
* Credentials username.
*/
private String username;
/**
* Credentials password.
*/
private String password;
/**
* Connection timeout.
*/
private Duration connectionTimeout = Duration.ofSeconds(1);
/**
* Read timeout.
*/
private Duration readTimeout = Duration.ofSeconds(30);
private final Sniffer sniffer = new Sniffer();
private boolean customized = false;
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.uris")
public List<String> getUris() {
return this.uris;
}
public void setUris(List<String> uris) {
this.customized = true;
this.uris = uris;
}
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.username")
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.customized = true;
this.username = username;
}
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.password")
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.customized = true;
this.password = password;
}
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.connection-timeout")
public Duration getConnectionTimeout() {
return this.connectionTimeout;
}
public void setConnectionTimeout(Duration connectionTimeout) {
this.customized = true;
this.connectionTimeout = connectionTimeout;
}
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.socket-timeout")
public Duration getReadTimeout() {
return this.readTimeout;
}
public void setReadTimeout(Duration readTimeout) {
this.customized = true;
this.readTimeout = readTimeout;
}
boolean isCustomized() {
return this.customized;
}
public Sniffer getSniffer() {
return this.sniffer;
}
@Deprecated
class Sniffer {
/**
* Interval between consecutive ordinary sniff executions.
*/
private Duration interval = Duration.ofMinutes(5);
/**
* Delay of a sniff execution scheduled after a failure.
*/
private Duration delayAfterFailure = Duration.ofMinutes(1);
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.restclient.sniffer.interval")
public Duration getInterval() {
return this.interval;
}
public void setInterval(Duration interval) {
DeprecatedElasticsearchRestClientProperties.this.customized = true;
this.interval = interval;
}
@DeprecatedConfigurationProperty(replacement = "spring.elasticsearch.restclient.sniffer.delay-after-failure")
public Duration getDelayAfterFailure() {
return this.delayAfterFailure;
}
public void setDelayAfterFailure(Duration delayAfterFailure) {
DeprecatedElasticsearchRestClientProperties.this.customized = true;
this.delayAfterFailure = delayAfterFailure;
}
}
}

@ -0,0 +1,100 @@
/*
* Copyright 2012-2021 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.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for Elasticsearch.
*
* @author Andy Wilkinson
* @since 2.4.0
*/
@ConfigurationProperties("spring.elasticsearch")
public class ElasticsearchProperties {
/**
* Comma-separated list of the Elasticsearch instances to use.
*/
private List<String> uris = new ArrayList<>(Collections.singletonList("http://localhost:9200"));
/**
* Username for authentication with Elasticsearch.
*/
private String username;
/**
* Password for authentication with Elasticsearch.
*/
private String password;
/**
* Connection timeout used when communicating with Elasticsearch.
*/
private Duration connectionTimeout = Duration.ofSeconds(1);
/**
* Socket timeout used when communicating with Elasticsearch.
*/
private Duration socketTimeout = Duration.ofSeconds(30);
public List<String> getUris() {
return this.uris;
}
public void setUris(List<String> uris) {
this.uris = uris;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public Duration getConnectionTimeout() {
return this.connectionTimeout;
}
public void setConnectionTimeout(Duration connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public Duration getSocketTimeout() {
return this.socketTimeout;
}
public void setSocketTimeout(Duration socketTimeout) {
this.socketTimeout = socketTimeout;
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@ -36,10 +36,12 @@ import org.springframework.context.annotation.Import;
* @author Stephane Nicoll
* @since 2.1.0
*/
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestHighLevelClient.class)
@ConditionalOnMissingBean(RestClient.class)
@EnableConfigurationProperties(ElasticsearchRestClientProperties.class)
@EnableConfigurationProperties({ ElasticsearchProperties.class, ElasticsearchRestClientProperties.class,
DeprecatedElasticsearchRestClientProperties.class })
@Import({ RestClientBuilderConfiguration.class, RestHighLevelClientConfiguration.class,
RestClientSnifferConfiguration.class })
public class ElasticsearchRestClientAutoConfiguration {

@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.elasticsearch;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.List;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
@ -53,15 +54,23 @@ class ElasticsearchRestClientConfigurations {
@ConditionalOnMissingBean(RestClientBuilder.class)
static class RestClientBuilderConfiguration {
private final ConsolidatedProperties properties;
@SuppressWarnings("deprecation")
RestClientBuilderConfiguration(ElasticsearchProperties properties,
DeprecatedElasticsearchRestClientProperties deprecatedProperties) {
this.properties = new ConsolidatedProperties(properties, deprecatedProperties);
}
@Bean
RestClientBuilderCustomizer defaultRestClientBuilderCustomizer(ElasticsearchRestClientProperties properties) {
return new DefaultRestClientBuilderCustomizer(properties);
RestClientBuilderCustomizer defaultRestClientBuilderCustomizer() {
return new DefaultRestClientBuilderCustomizer(this.properties);
}
@Bean
RestClientBuilder elasticsearchRestClientBuilder(ElasticsearchRestClientProperties properties,
RestClientBuilder elasticsearchRestClientBuilder(
ObjectProvider<RestClientBuilderCustomizer> builderCustomizers) {
HttpHost[] hosts = properties.getUris().stream().map(this::createHttpHost).toArray(HttpHost[]::new);
HttpHost[] hosts = this.properties.getUris().stream().map(this::createHttpHost).toArray(HttpHost[]::new);
RestClientBuilder builder = RestClient.builder(hosts);
builder.setHttpClientConfigCallback((httpClientBuilder) -> {
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(httpClientBuilder));
@ -117,13 +126,18 @@ class ElasticsearchRestClientConfigurations {
@Bean
@ConditionalOnMissingBean
Sniffer elasticsearchSniffer(RestHighLevelClient client, ElasticsearchRestClientProperties properties) {
@SuppressWarnings("deprecation")
Sniffer elasticsearchSniffer(RestHighLevelClient client, ElasticsearchRestClientProperties properties,
DeprecatedElasticsearchRestClientProperties deprecatedProperties) {
SnifferBuilder builder = Sniffer.builder(client.getLowLevelClient());
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties.getSniffer().getInterval()).asInt(Duration::toMillis)
.to(builder::setSniffIntervalMillis);
map.from(properties.getSniffer().getDelayAfterFailure()).asInt(Duration::toMillis)
.to(builder::setSniffAfterFailureDelayMillis);
Duration interval = deprecatedProperties.isCustomized() ? deprecatedProperties.getSniffer().getInterval()
: properties.getSniffer().getInterval();
map.from(interval).asInt(Duration::toMillis).to(builder::setSniffIntervalMillis);
Duration delayAfterFailure = deprecatedProperties.isCustomized()
? deprecatedProperties.getSniffer().getDelayAfterFailure()
: properties.getSniffer().getDelayAfterFailure();
map.from(delayAfterFailure).asInt(Duration::toMillis).to(builder::setSniffAfterFailureDelayMillis);
return builder.build();
}
@ -133,9 +147,9 @@ class ElasticsearchRestClientConfigurations {
private static final PropertyMapper map = PropertyMapper.get();
private final ElasticsearchRestClientProperties properties;
private final ConsolidatedProperties properties;
DefaultRestClientBuilderCustomizer(ElasticsearchRestClientProperties properties) {
DefaultRestClientBuilderCustomizer(ConsolidatedProperties properties) {
this.properties = properties;
}
@ -152,7 +166,7 @@ class ElasticsearchRestClientConfigurations {
public void customize(RequestConfig.Builder builder) {
map.from(this.properties::getConnectionTimeout).whenNonNull().asInt(Duration::toMillis)
.to(builder::setConnectTimeout);
map.from(this.properties::getReadTimeout).whenNonNull().asInt(Duration::toMillis)
map.from(this.properties::getSocketTimeout).whenNonNull().asInt(Duration::toMillis)
.to(builder::setSocketTimeout);
}
@ -160,7 +174,7 @@ class ElasticsearchRestClientConfigurations {
private static class PropertiesCredentialsProvider extends BasicCredentialsProvider {
PropertiesCredentialsProvider(ElasticsearchRestClientProperties properties) {
PropertiesCredentialsProvider(ConsolidatedProperties properties) {
if (StringUtils.hasText(properties.getUsername())) {
Credentials credentials = new UsernamePasswordCredentials(properties.getUsername(),
properties.getPassword());
@ -201,4 +215,44 @@ class ElasticsearchRestClientConfigurations {
}
@SuppressWarnings("deprecation")
private static final class ConsolidatedProperties {
private final ElasticsearchProperties properties;
private final DeprecatedElasticsearchRestClientProperties deprecatedProperties;
private ConsolidatedProperties(ElasticsearchProperties properties,
DeprecatedElasticsearchRestClientProperties deprecatedProperties) {
this.properties = properties;
this.deprecatedProperties = deprecatedProperties;
}
private List<String> getUris() {
return this.deprecatedProperties.isCustomized() ? this.deprecatedProperties.getUris()
: this.properties.getUris();
}
private String getUsername() {
return this.deprecatedProperties.isCustomized() ? this.deprecatedProperties.getUsername()
: this.properties.getUsername();
}
private String getPassword() {
return this.deprecatedProperties.isCustomized() ? this.deprecatedProperties.getPassword()
: this.properties.getPassword();
}
private Duration getConnectionTimeout() {
return this.deprecatedProperties.isCustomized() ? this.deprecatedProperties.getConnectionTimeout()
: this.properties.getConnectionTimeout();
}
private Duration getSocketTimeout() {
return this.deprecatedProperties.isCustomized() ? this.deprecatedProperties.getReadTimeout()
: this.properties.getSocketTimeout();
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@ -17,88 +17,24 @@
package org.springframework.boot.autoconfigure.elasticsearch;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for Elasticsearch REST clients.
* Configuration properties specific to Elasticsearch's {@link RestClient} and
* {@link RestHighLevelClient}.
*
* @author Brian Clozel
* @since 2.1.0
*/
@ConfigurationProperties(prefix = "spring.elasticsearch.rest")
@ConfigurationProperties(prefix = "spring.elasticsearch.restclient")
public class ElasticsearchRestClientProperties {
/**
* Comma-separated list of the Elasticsearch instances to use.
*/
private List<String> uris = new ArrayList<>(Collections.singletonList("http://localhost:9200"));
/**
* Credentials username.
*/
private String username;
/**
* Credentials password.
*/
private String password;
/**
* Connection timeout.
*/
private Duration connectionTimeout = Duration.ofSeconds(1);
/**
* Read timeout.
*/
private Duration readTimeout = Duration.ofSeconds(30);
private final Sniffer sniffer = new Sniffer();
public List<String> getUris() {
return this.uris;
}
public void setUris(List<String> uris) {
this.uris = uris;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public Duration getConnectionTimeout() {
return this.connectionTimeout;
}
public void setConnectionTimeout(Duration connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public Duration getReadTimeout() {
return this.readTimeout;
}
public void setReadTimeout(Duration readTimeout) {
this.readTimeout = readTimeout;
}
public Sniffer getSniffer() {
return this.sniffer;
}

@ -860,6 +860,12 @@
"http://localhost:9200"
]
},
{
"name": "spring.elasticsearch.uris",
"defaultValue": [
"http://localhost:9200"
]
},
{
"name": "spring.flyway.connect-retries-interval",
"defaultValue": 120

@ -52,11 +52,10 @@ class ReactiveElasticsearchRestClientAutoConfigurationIntegrationTests {
@Test
void restClientCanQueryElasticsearchNode() {
this.contextRunner.withPropertyValues(
"spring.data.elasticsearch.client.reactive.endpoints=" + elasticsearch.getHost() + ":"
+ elasticsearch.getFirstMappedPort(),
"spring.data.elasticsearch.client.reactive.connection-timeout=120s",
"spring.data.elasticsearch.client.reactive.socket-timeout=120s").run((context) -> {
this.contextRunner
.withPropertyValues("spring.elasticsearch.uris=" + elasticsearch.getHttpHostAddress(),
"spring.elasticsearch.connection-timeout=120s", "spring.elasticsearch.socket-timeout=120s")
.run((context) -> {
ReactiveElasticsearchClient client = context.getBean(ReactiveElasticsearchClient.class);
Map<String, String> source = new HashMap<>();
source.put("a", "alpha");

@ -16,12 +16,20 @@
package org.springframework.boot.autoconfigure.data.elasticsearch;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.List;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -50,8 +58,14 @@ class ReactiveElasticsearchRestClientAutoConfigurationTests {
@Test
void configureShouldCreateDefaultBeans() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ClientConfiguration.class)
.hasSingleBean(ReactiveElasticsearchClient.class));
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(ClientConfiguration.class)
.hasSingleBean(ReactiveElasticsearchClient.class);
List<InetSocketAddress> endpoints = context.getBean(ClientConfiguration.class).getEndpoints();
assertThat(endpoints).hasSize(1);
assertThat(endpoints.get(0).getHostString()).isEqualTo("localhost");
assertThat(endpoints.get(0).getPort()).isEqualTo(9200);
});
}
@Test
@ -68,6 +82,7 @@ class ReactiveElasticsearchRestClientAutoConfigurationTests {
}
@Test
@Deprecated
void whenEndpointIsCustomizedThenClientConfigurationHasCustomEndpoint() {
this.contextRunner.withPropertyValues("spring.data.elasticsearch.client.reactive.endpoints=localhost:9876")
.run((context) -> {
@ -79,6 +94,7 @@ class ReactiveElasticsearchRestClientAutoConfigurationTests {
}
@Test
@Deprecated
void whenMultipleEndpointsAreConfiguredThenClientConfigurationHasMultipleEndpoints() {
this.contextRunner
.withPropertyValues("spring.data.elasticsearch.client.reactive.endpoints=localhost:9876,localhost:8765")
@ -93,6 +109,122 @@ class ReactiveElasticsearchRestClientAutoConfigurationTests {
}
@Test
void whenUriIsCustomizedThenClientConfigurationHasCustomEndpoint() {
this.contextRunner.withPropertyValues("spring.elasticsearch.uris=http://localhost:9876").run((context) -> {
List<InetSocketAddress> endpoints = context.getBean(ClientConfiguration.class).getEndpoints();
assertThat(endpoints).hasSize(1);
assertThat(endpoints.get(0).getHostString()).isEqualTo("localhost");
assertThat(endpoints.get(0).getPort()).isEqualTo(9876);
});
}
@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
@Deprecated
void whenConfiguredToUseSslThenClientConfigurationUsesSsl() {
this.contextRunner.withPropertyValues("spring.data.elasticsearch.client.reactive.use-ssl=true")
.run((context) -> assertThat(context.getBean(ClientConfiguration.class).useSsl()).isTrue());
@ -101,48 +233,44 @@ class ReactiveElasticsearchRestClientAutoConfigurationTests {
@Test
void whenSocketTimeoutIsNotConfiguredThenClientConfigurationUsesDefault() {
this.contextRunner.run((context) -> assertThat(context.getBean(ClientConfiguration.class).getSocketTimeout())
.isEqualTo(Duration.ofSeconds(5)));
.isEqualTo(Duration.ofSeconds(30)));
}
@Test
void whenConnectionTimeoutIsNotConfiguredThenClientConfigurationUsesDefault() {
this.contextRunner.run((context) -> assertThat(context.getBean(ClientConfiguration.class).getConnectTimeout())
.isEqualTo(Duration.ofSeconds(10)));
.isEqualTo(Duration.ofSeconds(1)));
}
@Test
void whenSocketTimeoutIsConfiguredThenClientConfigurationHasCustomSocketTimeout() {
this.contextRunner.withPropertyValues("spring.data.elasticsearch.client.reactive.socket-timeout=2s")
@ParameterizedPropertyPrefixTest
void whenSocketTimeoutIsConfiguredThenClientConfigurationHasCustomSocketTimeout(String prefix) {
this.contextRunner.withPropertyValues(prefix + "socket-timeout=2s")
.run((context) -> assertThat(context.getBean(ClientConfiguration.class).getSocketTimeout())
.isEqualTo(Duration.ofSeconds(2)));
}
@Test
void whenConnectionTimeoutIsConfiguredThenClientConfigurationHasCustomConnectTimeout() {
this.contextRunner.withPropertyValues("spring.data.elasticsearch.client.reactive.connection-timeout=2s")
@ParameterizedPropertyPrefixTest
void whenConnectionTimeoutIsConfiguredThenClientConfigurationHasCustomConnectTimeout(String prefix) {
this.contextRunner.withPropertyValues(prefix + "connection-timeout=2s")
.run((context) -> assertThat(context.getBean(ClientConfiguration.class).getConnectTimeout())
.isEqualTo(Duration.ofSeconds(2)));
}
@Test
void whenCredentialsAreConfiguredThenClientConfigurationHasDefaultAuthorizationHeader() {
this.contextRunner
.withPropertyValues("spring.data.elasticsearch.client.reactive.username=alice",
"spring.data.elasticsearch.client.reactive.password=secret")
@ParameterizedPropertyPrefixTest
void whenCredentialsAreConfiguredThenClientConfigurationHasDefaultAuthorizationHeader(String prefix) {
this.contextRunner.withPropertyValues(prefix + "username=alice", prefix + "password=secret")
.run((context) -> assertThat(
context.getBean(ClientConfiguration.class).getDefaultHeaders().get(HttpHeaders.AUTHORIZATION))
.containsExactly("Basic YWxpY2U6c2VjcmV0"));
}
@Test
void whenMaxInMemorySizeIsConfiguredThenUnderlyingWebClientHasCustomMaxInMemorySize() {
this.contextRunner.withPropertyValues("spring.data.elasticsearch.client.reactive.max-in-memory-size=1MB")
.run((context) -> {
WebClient client = configureWebClient(
context.getBean(ClientConfiguration.class).getClientConfigurers());
assertThat(client).extracting("exchangeFunction").extracting("strategies")
.extracting("codecConfigurer").extracting("defaultCodecs")
.asInstanceOf(InstanceOfAssertFactories.type(DefaultCodecConfig.class))
@ParameterizedTest
@ValueSource(strings = { "spring.elasticsearch.webclient.", "spring.data.elasticsearch.client.reactive." })
void whenMaxInMemorySizeIsConfiguredThenUnderlyingWebClientHasCustomMaxInMemorySize(String prefix) {
this.contextRunner.withPropertyValues(prefix + "max-in-memory-size=1MB").run((context) -> {
WebClient client = configureWebClient(context.getBean(ClientConfiguration.class).getClientConfigurers());
assertThat(client).extracting("exchangeFunction").extracting("strategies").extracting("codecConfigurer")
.extracting("defaultCodecs").asInstanceOf(InstanceOfAssertFactories.type(DefaultCodecConfig.class))
.extracting(DefaultCodecConfig::maxInMemorySize).isEqualTo(1024 * 1024);
});
}
@ -175,4 +303,12 @@ class ReactiveElasticsearchRestClientAutoConfigurationTests {
}
@ParameterizedTest
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ValueSource(strings = { "spring.data.elasticsearch.client.reactive.", "spring.elasticsearch." })
static @interface ParameterizedPropertyPrefixTest {
}
}

@ -55,7 +55,8 @@ class ElasticsearchRestClientAutoConfigurationIntegrationTests {
@Test
void restClientCanQueryElasticsearchNode() {
this.contextRunner
.withPropertyValues("spring.elasticsearch.rest.uris=http://" + elasticsearch.getHttpHostAddress())
.withPropertyValues("spring.elasticsearch.uris=" + elasticsearch.getHttpHostAddress(),
"spring.elasticsearch.connection-timeout=120s", "spring.elasticsearch.socket-timeout=120s")
.run((context) -> {
RestHighLevelClient client = context.getBean(RestHighLevelClient.class);
Map<String, String> source = new HashMap<>();

@ -16,6 +16,10 @@
package org.springframework.boot.autoconfigure.elasticsearch;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.time.Duration;
import java.util.Map;
@ -32,6 +36,8 @@ import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.sniff.Sniffer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
@ -105,7 +111,7 @@ class ElasticsearchRestClientAutoConfigurationTests {
}
@Test
void configureWithCustomTimeouts() {
void configureWithLegacyCustomTimeouts() {
this.contextRunner.withPropertyValues("spring.elasticsearch.rest.connection-timeout=15s",
"spring.elasticsearch.rest.read-timeout=1m").run((context) -> {
assertThat(context).hasSingleBean(RestHighLevelClient.class);
@ -114,6 +120,16 @@ class ElasticsearchRestClientAutoConfigurationTests {
});
}
@Test
void configureWithCustomTimeouts() {
this.contextRunner.withPropertyValues("spring.elasticsearch.connection-timeout=15s",
"spring.elasticsearch.socket-timeout=1m").run((context) -> {
assertThat(context).hasSingleBean(RestHighLevelClient.class);
RestHighLevelClient restClient = context.getBean(RestHighLevelClient.class);
assertTimeouts(restClient, Duration.ofSeconds(15), Duration.ofMinutes(1));
});
}
private static void assertTimeouts(RestHighLevelClient restClient, Duration connectTimeout, Duration readTimeout) {
assertThat(restClient.getLowLevelClient()).extracting("client.defaultConfig.socketTimeout")
.isEqualTo(Math.toIntExact(readTimeout.toMillis()));
@ -121,50 +137,51 @@ class ElasticsearchRestClientAutoConfigurationTests {
.isEqualTo(Math.toIntExact(connectTimeout.toMillis()));
}
@Test
void configureUriWithUsernameOnly() {
this.contextRunner.withPropertyValues("spring.elasticsearch.rest.uris=http://user@localhost:9200")
.run((context) -> {
@ParameterizedPropertyPrefixTest
void configureUriWithNoScheme(String prefix) {
this.contextRunner.withPropertyValues(prefix + "uris=localhost:9876").run((context) -> {
RestClient client = context.getBean(RestHighLevelClient.class).getLowLevelClient();
assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString))
.containsExactly("http://localhost:9876");
});
}
@ParameterizedPropertyPrefixTest
void configureUriWithUsernameOnly(String prefix) {
this.contextRunner.withPropertyValues(prefix + "uris=http://user@localhost:9200").run((context) -> {
RestClient client = context.getBean(RestHighLevelClient.class).getLowLevelClient();
assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString))
.containsExactly("http://localhost:9200");
assertThat(client).extracting("client")
.extracting("credentialsProvider",
InstanceOfAssertFactories.type(CredentialsProvider.class))
.extracting("credentialsProvider", InstanceOfAssertFactories.type(CredentialsProvider.class))
.satisfies((credentialsProvider) -> {
Credentials credentials = credentialsProvider
.getCredentials(new AuthScope("localhost", 9200));
Credentials credentials = credentialsProvider.getCredentials(new AuthScope("localhost", 9200));
assertThat(credentials.getUserPrincipal().getName()).isEqualTo("user");
assertThat(credentials.getPassword()).isNull();
});
});
}
@Test
void configureUriWithUsernameAndEmptyPassword() {
this.contextRunner.withPropertyValues("spring.elasticsearch.rest.uris=http://user:@localhost:9200")
.run((context) -> {
@ParameterizedPropertyPrefixTest
void configureUriWithUsernameAndEmptyPassword(String prefix) {
this.contextRunner.withPropertyValues(prefix + "uris=http://user:@localhost:9200").run((context) -> {
RestClient client = context.getBean(RestHighLevelClient.class).getLowLevelClient();
assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString))
.containsExactly("http://localhost:9200");
assertThat(client).extracting("client")
.extracting("credentialsProvider",
InstanceOfAssertFactories.type(CredentialsProvider.class))
.extracting("credentialsProvider", InstanceOfAssertFactories.type(CredentialsProvider.class))
.satisfies((credentialsProvider) -> {
Credentials credentials = credentialsProvider
.getCredentials(new AuthScope("localhost", 9200));
Credentials credentials = credentialsProvider.getCredentials(new AuthScope("localhost", 9200));
assertThat(credentials.getUserPrincipal().getName()).isEqualTo("user");
assertThat(credentials.getPassword()).isEmpty();
});
});
}
@Test
void configureUriWithUsernameAndPasswordWhenUsernameAndPasswordPropertiesSet() {
this.contextRunner
.withPropertyValues("spring.elasticsearch.rest.uris=http://user:password@localhost:9200,localhost:9201",
"spring.elasticsearch.rest.username=admin", "spring.elasticsearch.rest.password=admin")
.run((context) -> {
@ParameterizedPropertyPrefixTest
void configureUriWithUsernameAndPasswordWhenUsernameAndPasswordPropertiesSet(String prefix) {
this.contextRunner.withPropertyValues(prefix + "uris=http://user:password@localhost:9200,localhost:9201",
prefix + "username=admin", prefix + "password=admin").run((context) -> {
RestClient client = context.getBean(RestHighLevelClient.class).getLowLevelClient();
assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString))
.containsExactly("http://localhost:9200", "http://localhost:9201");
@ -203,10 +220,10 @@ class ElasticsearchRestClientAutoConfigurationTests {
});
}
@Test
void configureWithCustomSnifferSettings() {
this.contextRunner.withPropertyValues("spring.elasticsearch.rest.sniffer.interval=180s",
"spring.elasticsearch.rest.sniffer.delay-after-failure=30s").run((context) -> {
@ParameterizedSnifferPropertyPrefixTest
void configureWithCustomSnifferSettings(String prefix) {
this.contextRunner.withPropertyValues(prefix + "interval=180s", prefix + "delay-after-failure=30s")
.run((context) -> {
assertThat(context).hasSingleBean(Sniffer.class);
Sniffer sniffer = context.getBean(Sniffer.class);
assertThat(sniffer).hasFieldOrPropertyWithValue("sniffIntervalMillis",
@ -279,4 +296,20 @@ class ElasticsearchRestClientAutoConfigurationTests {
}
@ParameterizedTest
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ValueSource(strings = { "spring.elasticsearch.rest.", "spring.elasticsearch." })
static @interface ParameterizedPropertyPrefixTest {
}
@ParameterizedTest
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ValueSource(strings = { "spring.elasticsearch.rest.sniffer.", "spring.elasticsearch.restclient.sniffer." })
static @interface ParameterizedSnifferPropertyPrefixTest {
}
}

@ -261,7 +261,7 @@ If you add your own `@Bean` of type `SolrClient`, it replaces the default.
[[data.nosql.elasticsearch]]
=== Elasticsearch
https://www.elastic.co/products/elasticsearch[Elasticsearch] is an open source, distributed, RESTful search and analytics engine.
Spring Boot offers basic auto-configuration for Elasticsearch.
Spring Boot offers basic auto-configuration for Elasticsearch clients.
Spring Boot supports several clients:
@ -276,34 +276,36 @@ Spring Boot provides a dedicated "`Starter`", `spring-boot-starter-data-elastics
==== 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 and the "High Level" client.
Spring Boot provides support for the "High Level" client, which ships with `org.elasticsearch.client:elasticsearch-rest-high-level-client`.
If you have this dependency on the classpath, Spring Boot will auto-configure and register a `RestHighLevelClient` bean that by default targets `http://localhost:9200`.
You can further tune how `RestHighLevelClient` is configured, as shown in the following example:
Additionally, Spring Boot provides support for a reactive client, based on Spring Framework's `WebClient`, that ships with `org.springframework.data:spring-data-elasticsearch`.
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:
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
----
spring:
elasticsearch:
rest:
uris: "https://search.example.com:9200"
read-timeout: "10s"
socket-timeout: "10s"
username: "user"
password: "secret"
----
You can also register an arbitrary number of beans that implement `RestClientBuilderCustomizer` for more advanced customizations.
To take full control over the registration, define a `RestClientBuilder` bean.
[[data.nosql.elasticsearch.connecting-using-rest.restclient]]
===== Connecting to Elasticsearch using RestHighLevelClient
If you have `elasticsearch-rest-high-level-client` on the classpath, Spring Boot will auto-configure and register a `RestHighLevelClient` bean.
In addition to the properties described previously, to fine-tune the `RestHighLevelClient`, you can register an arbitrary number of beans that implement `RestClientBuilderCustomizer` for more advanced customizations.
To take full control over its registration, define a `RestClientBuilder` bean.
TIP: If your application needs access to a "Low Level" `RestClient`, you can get it by calling `client.getLowLevelClient()` on the auto-configured `RestHighLevelClient`.
Additionally, if `elasticsearch-rest-client-sniffer` is on the classpath, a `Sniffer` is auto-configured to automatically discover nodes from a running Elasticsearch cluster and set them to the `RestHighLevelClient` bean.
Additionally, if `elasticsearch-rest-client-sniffer` is on the classpath, a `Sniffer` is auto-configured to automatically discover nodes from a running Elasticsearch cluster and set them on the `RestHighLevelClient` bean.
You can further tune how `Sniffer` is configured, as shown in the following example:
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
----
spring:
elasticsearch:
rest:
restclient:
sniffer:
interval: 10m
delay-after-failure: 30s
@ -311,31 +313,23 @@ You can further tune how `Sniffer` is configured, as shown in the following exam
[[data.nosql.elasticsearch.connecting-using-reactive-rest]]
==== Connecting to Elasticsearch using Reactive REST clients
[[data.nosql.elasticsearch.connecting-using-rest.webclient]]
===== Connecting to Elasticsearch using ReactiveElasticsearchClient
{spring-data-elasticsearch}[Spring Data Elasticsearch] ships `ReactiveElasticsearchClient` for querying Elasticsearch instances in a reactive fashion.
It is built on top of WebFlux's `WebClient`, so both `spring-boot-starter-elasticsearch` and `spring-boot-starter-webflux` dependencies are useful to enable this support.
By default, Spring Boot will auto-configure and register a `ReactiveElasticsearchClient`
bean that targets `http://localhost:9200`.
You can further tune how it is configured, as shown in the following example:
By default, Spring Boot will auto-configure and register a `ReactiveElasticsearchClient`.
In addition to the properties described previously, the `spring.elasticsearch.webclient.*` properties can be used to configure reactive-specific settings, as shown in the following example:
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
----
spring:
data:
elasticsearch:
client:
reactive:
endpoints: "search.example.com:9200"
use-ssl: true
socket-timeout: "10s"
username: "user"
password: "secret"
webclient:
max-in-memory-size: 1MB
----
If the configuration properties are not enough and you'd like to fully control the client
configuration, you can register a custom `ClientConfiguration` bean.
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.
@ -352,7 +346,7 @@ as shown in the following example:
include::{docs-java}/features/nosql/elasticsearch/connectingusingspringdata/MyBean.java[]
----
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-reactive-rest,ReactiveElasticsearchClient>> and a `ReactiveElasticsearchTemplate` as beans.
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.
They are the reactive equivalent of the other REST clients.

@ -54,4 +54,7 @@
<suppress files="BsdDomainSocket" checks="FinalClass" message="SockaddrUn" />
<suppress files="StringSequence" checks="SpringMethodVisibility" message="isEmpty"/>
<suppress files="ValidatorPropertiesWithDefaultValues\.java" checks="SpringMethodVisibility" />
<suppress files="DeprecatedElasticsearchRestClientProperties\.java" checks="SpringMethodVisibility" />
<suppress files="DeprecatedElasticsearchRestClientProperties\.java" checks="SpringMethodVisibility" />
<suppress files="DeprecatedReactiveElasticsearchRestClientProperties\.java" checks="SpringMethodVisibility" />
</suppressions>

Loading…
Cancel
Save