diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/rest/RestClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/rest/RestClientAutoConfiguration.java index 2c2ba9db1cf..45dc2661472 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/rest/RestClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/rest/RestClientAutoConfiguration.java @@ -16,80 +16,27 @@ package org.springframework.boot.autoconfigure.elasticsearch.rest; -import java.time.Duration; - -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.impl.client.BasicCredentialsProvider; import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.RestClientBuilder; -import org.elasticsearch.client.RestHighLevelClient; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.PropertyMapper; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; /** * {@link EnableAutoConfiguration Auto-configuration} for Elasticsearch REST clients. * * @author Brian Clozel + * @author Stephane Nicoll * @since 2.1.0 */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RestClient.class) @EnableConfigurationProperties(RestClientProperties.class) +@Import({ RestClientConfigurations.RestClientBuilderConfiguration.class, + RestClientConfigurations.RestHighLevelClientConfiguration.class, + RestClientConfigurations.RestClientFallbackConfiguration.class }) public class RestClientAutoConfiguration { - @Bean - @ConditionalOnMissingBean - public RestClient restClient(RestClientBuilder builder) { - return builder.build(); - } - - @Bean - @ConditionalOnMissingBean - public RestClientBuilder restClientBuilder(RestClientProperties properties, - ObjectProvider builderCustomizers) { - HttpHost[] hosts = properties.getUris().stream().map(HttpHost::create).toArray(HttpHost[]::new); - RestClientBuilder builder = RestClient.builder(hosts); - PropertyMapper map = PropertyMapper.get(); - map.from(properties::getUsername).whenHasText().to((username) -> { - CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - Credentials credentials = new UsernamePasswordCredentials(properties.getUsername(), - properties.getPassword()); - credentialsProvider.setCredentials(AuthScope.ANY, credentials); - builder.setHttpClientConfigCallback( - (httpClientBuilder) -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); - }); - builder.setRequestConfigCallback((requestConfigBuilder) -> { - map.from(properties::getConnectionTimeout).whenNonNull().asInt(Duration::toMillis) - .to(requestConfigBuilder::setConnectTimeout); - map.from(properties::getReadTimeout).whenNonNull().asInt(Duration::toMillis) - .to(requestConfigBuilder::setSocketTimeout); - return requestConfigBuilder; - }); - builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); - return builder; - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(RestHighLevelClient.class) - public static class RestHighLevelClientConfiguration { - - @Bean - @ConditionalOnMissingBean - public RestHighLevelClient restHighLevelClient(RestClientBuilder restClientBuilder) { - return new RestHighLevelClient(restClientBuilder); - } - - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/rest/RestClientConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/rest/RestClientConfigurations.java new file mode 100644 index 00000000000..7e9b7523473 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/rest/RestClientConfigurations.java @@ -0,0 +1,110 @@ +/* + * Copyright 2012-2019 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.rest; + +import java.time.Duration; + +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.elasticsearch.client.RestHighLevelClient; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Elasticsearch rest client infrastructure configurations. + * + * @author Brian Clozel + * @author Stephane Nicoll + */ +class RestClientConfigurations { + + @Configuration(proxyBeanMethods = false) + static class RestClientBuilderConfiguration { + + @Bean + @ConditionalOnMissingBean + RestClientBuilder restClientBuilder(RestClientProperties properties, + ObjectProvider builderCustomizers) { + HttpHost[] hosts = properties.getUris().stream().map(HttpHost::create).toArray(HttpHost[]::new); + RestClientBuilder builder = RestClient.builder(hosts); + PropertyMapper map = PropertyMapper.get(); + map.from(properties::getUsername).whenHasText().to((username) -> { + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + Credentials credentials = new UsernamePasswordCredentials(properties.getUsername(), + properties.getPassword()); + credentialsProvider.setCredentials(AuthScope.ANY, credentials); + builder.setHttpClientConfigCallback( + (httpClientBuilder) -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); + }); + builder.setRequestConfigCallback((requestConfigBuilder) -> { + map.from(properties::getConnectionTimeout).whenNonNull().asInt(Duration::toMillis) + .to(requestConfigBuilder::setConnectTimeout); + map.from(properties::getReadTimeout).whenNonNull().asInt(Duration::toMillis) + .to(requestConfigBuilder::setSocketTimeout); + return requestConfigBuilder; + }); + builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder; + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(RestHighLevelClient.class) + static class RestHighLevelClientConfiguration { + + @Bean + @ConditionalOnMissingBean + RestHighLevelClient restHighLevelClient(RestClientBuilder restClientBuilder) { + return new RestHighLevelClient(restClientBuilder); + } + + @Bean + @ConditionalOnMissingBean + RestClient restClient(RestClientBuilder builder, ObjectProvider restHighLevelClient) { + RestHighLevelClient client = restHighLevelClient.getIfUnique(); + if (client != null) { + return client.getLowLevelClient(); + } + return builder.build(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class RestClientFallbackConfiguration { + + @Bean + @ConditionalOnMissingBean + RestClient restClient(RestClientBuilder builder) { + return builder.build(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/rest/RestClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/rest/RestClientAutoConfigurationTests.java index 953f94b4b3b..8e75a970941 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/rest/RestClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/rest/RestClientAutoConfigurationTests.java @@ -31,6 +31,7 @@ import org.testcontainers.elasticsearch.ElasticsearchContainer; import org.testcontainers.junit.jupiter.Container; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.testsupport.testcontainers.DisabledWithoutDockerTestcontainers; import org.springframework.context.annotation.Bean; @@ -56,14 +57,46 @@ class RestClientAutoConfigurationTests { @Test void configureShouldCreateBothRestClientVariants() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(RestClient.class) - .hasSingleBean(RestHighLevelClient.class)); + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(RestClient.class).hasSingleBean(RestHighLevelClient.class); + assertThat(context.getBean(RestClient.class)) + .isSameAs(context.getBean(RestHighLevelClient.class).getLowLevelClient()); + }); } @Test void configureWhenCustomClientShouldBackOff() { this.contextRunner.withUserConfiguration(CustomRestClientConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(RestClient.class).hasBean("customRestClient")); + .run((context) -> assertThat(context).getBeanNames(RestClient.class).containsOnly("customRestClient")); + } + + @Test + void configureWhenCustomRestHighLevelClientShouldBackOff() { + this.contextRunner.withUserConfiguration(CustomRestHighLevelClientConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(RestClient.class).hasSingleBean(RestHighLevelClient.class); + assertThat(context.getBean(RestClient.class)) + .isSameAs(context.getBean(RestHighLevelClient.class).getLowLevelClient()); + }); + } + + @Test + void configureWhenDefaultRestClientShouldCreateWhenNoUniqueRestHighLevelClient() { + this.contextRunner.withUserConfiguration(TwoCustomRestHighLevelClientConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(RestClient.class); + RestClient restClient = context.getBean(RestClient.class); + Map restHighLevelClients = context.getBeansOfType(RestHighLevelClient.class); + assertThat(restHighLevelClients).hasSize(2); + for (RestHighLevelClient restHighLevelClient : restHighLevelClients.values()) { + assertThat(restHighLevelClient.getLowLevelClient()).isNotSameAs(restClient); + } + }); + } + + @Test + void configureWhenHighLevelClientIsNotAvailableShouldCreateRestClientOnly() { + this.contextRunner.withClassLoader(new FilteredClassLoader(RestHighLevelClient.class)) + .run((context) -> assertThat(context).hasSingleBean(RestClient.class) + .doesNotHaveBean(RestHighLevelClient.class)); } @Test @@ -138,4 +171,29 @@ class RestClientAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class CustomRestHighLevelClientConfiguration { + + @Bean + RestHighLevelClient customRestHighLevelClient(RestClientBuilder builder) { + return new RestHighLevelClient(builder); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TwoCustomRestHighLevelClientConfiguration { + + @Bean + RestHighLevelClient customRestHighLevelClient(RestClientBuilder builder) { + return new RestHighLevelClient(builder); + } + + @Bean + RestHighLevelClient customRestHighLevelClient1(RestClientBuilder builder) { + return new RestHighLevelClient(builder); + } + + } + }