Commit 16c521a9 authored by Brian Clozel's avatar Brian Clozel

Auto-configure reactive Elasticsearch components

As of Spring Data Moore, a new reactive template and the
corresponding repositories support have been added.

This commit auto-configures a `ReactiveElasticsearchTemplate`
with the configuration properties under the
`spring.data.elasticsearch.client.reactive` namespace.
To enable this feature, applications require both Spring Data
Elasticsearch dependencies (typically
`spring-boot-starter-data-elasticsearch`) and dependencies
for a `WebClient` (often `spring-boot-starter-webflux`).

The support for the reactive Elasticsearch repositories is
also provided.

Closes gh-16214
parent 05ad9554
...@@ -24,27 +24,30 @@ import org.springframework.context.annotation.Configuration; ...@@ -24,27 +24,30 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.core.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;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Elasticsearch * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Elasticsearch
* support. * support.
* <p> * <p>
* Registers an {@link ElasticsearchTemplate} if no other bean of the same type is * Registers an {@link ElasticsearchTemplate} if no other bean of the same type and the
* configured. * same name {@code "elasticsearchTemplate"} is configured.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Artur Konczak * @author Artur Konczak
* @author Mohsin Husen * @author Mohsin Husen
* @see EnableElasticsearchRepositories * @see EnableElasticsearchRepositories
* @see EnableReactiveElasticsearchRepositories
* @since 1.1.0 * @since 1.1.0
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ElasticsearchTemplate.class }) @ConditionalOnClass({ ElasticsearchTemplate.class })
@AutoConfigureAfter({ ElasticsearchAutoConfiguration.class, @AutoConfigureAfter({ ElasticsearchAutoConfiguration.class,
RestClientAutoConfiguration.class }) RestClientAutoConfiguration.class, ReactiveRestClientAutoConfiguration.class })
@Import({ ElasticsearchDataConfiguration.BaseConfiguration.class, @Import({ ElasticsearchDataConfiguration.BaseConfiguration.class,
ElasticsearchDataConfiguration.TransportClientConfiguration.class, ElasticsearchDataConfiguration.TransportClientConfiguration.class,
ElasticsearchDataConfiguration.RestHighLevelClientConfiguration.class }) ElasticsearchDataConfiguration.RestClientConfiguration.class,
ElasticsearchDataConfiguration.ReactiveRestClientConfiguration.class })
public class ElasticsearchDataAutoConfiguration { public class ElasticsearchDataAutoConfiguration {
} }
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.data.elasticsearch; package org.springframework.boot.autoconfigure.data.elasticsearch;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.RestHighLevelClient;
...@@ -24,12 +26,20 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; ...@@ -24,12 +26,20 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.core.DefaultEntityMapper;
import org.springframework.data.elasticsearch.core.DefaultResultMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ResultsMapper;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
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
...@@ -57,19 +67,33 @@ abstract class ElasticsearchDataConfiguration { ...@@ -57,19 +67,33 @@ abstract class ElasticsearchDataConfiguration {
return new SimpleElasticsearchMappingContext(); return new SimpleElasticsearchMappingContext();
} }
@Bean
public EntityMapper entityMapper(
SimpleElasticsearchMappingContext mappingContext) {
return new DefaultEntityMapper(mappingContext);
}
@Bean
@ConditionalOnMissingBean
public ResultsMapper resultsMapper(
SimpleElasticsearchMappingContext mappingContext,
EntityMapper entityMapper) {
return new DefaultResultMapper(mappingContext, entityMapper);
}
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestHighLevelClient.class) @ConditionalOnClass(RestHighLevelClient.class)
static class RestHighLevelClientConfiguration { static class RestClientConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(value = ElasticsearchOperations.class, @ConditionalOnMissingBean(value = ElasticsearchOperations.class,
name = "elasticsearchTemplate") name = "elasticsearchTemplate")
@ConditionalOnBean(RestHighLevelClient.class) @ConditionalOnBean(RestHighLevelClient.class)
public ElasticsearchRestTemplate elasticsearchTemplate(RestHighLevelClient client, public ElasticsearchRestTemplate elasticsearchTemplate(RestHighLevelClient client,
ElasticsearchConverter converter) { ElasticsearchConverter converter, ResultsMapper resultsMapper) {
return new ElasticsearchRestTemplate(client, converter); return new ElasticsearchRestTemplate(client, converter, resultsMapper);
} }
} }
...@@ -83,9 +107,9 @@ abstract class ElasticsearchDataConfiguration { ...@@ -83,9 +107,9 @@ abstract class ElasticsearchDataConfiguration {
name = "elasticsearchTemplate") name = "elasticsearchTemplate")
@ConditionalOnBean(Client.class) @ConditionalOnBean(Client.class)
public ElasticsearchTemplate elasticsearchTemplate(Client client, public ElasticsearchTemplate elasticsearchTemplate(Client client,
ElasticsearchConverter converter) { ElasticsearchConverter converter, ResultsMapper resultsMapper) {
try { try {
return new ElasticsearchTemplate(client, converter); return new ElasticsearchTemplate(client, converter, resultsMapper);
} }
catch (Exception ex) { catch (Exception ex) {
throw new IllegalStateException(ex); throw new IllegalStateException(ex);
...@@ -94,4 +118,24 @@ abstract class ElasticsearchDataConfiguration { ...@@ -94,4 +118,24 @@ abstract class ElasticsearchDataConfiguration {
} }
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ WebClient.class, ReactiveElasticsearchOperations.class })
static class ReactiveRestClientConfiguration {
@Bean
@ConditionalOnMissingBean(value = ReactiveElasticsearchOperations.class,
name = "reactiveElasticsearchTemplate")
@ConditionalOnBean(ReactiveElasticsearchClient.class)
public ReactiveElasticsearchTemplate reactiveElasticsearchTemplate(
ReactiveElasticsearchClient client, ElasticsearchConverter converter,
ResultsMapper resultsMapper) {
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(
client, converter, resultsMapper);
template.setIndicesOptions(IndicesOptions.strictExpandOpenAndForbidClosed());
template.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
return template;
}
}
} }
/*
* 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.data.elasticsearch;
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.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import org.springframework.data.elasticsearch.repository.support.ReactiveElasticsearchRepositoryFactoryBean;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Elasticsearch
* Reactive Repositories.
*
* @author Brian Clozel
* @see EnableReactiveElasticsearchRepositories
* @since 2.2.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ReactiveElasticsearchClient.class,
ReactiveElasticsearchRepository.class })
@ConditionalOnProperty(prefix = "spring.data.elasticsearch.repositories",
name = "enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnMissingBean(ReactiveElasticsearchRepositoryFactoryBean.class)
@Import(ReactiveElasticsearchRepositoriesRegistrar.class)
public class ReactiveElasticsearchRepositoriesAutoConfiguration {
}
/*
* 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.data.elasticsearch;
import java.lang.annotation.Annotation;
import org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import org.springframework.data.elasticsearch.repository.config.ReactiveElasticsearchRepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
/**
* {@link ImportBeanDefinitionRegistrar} used to auto-configure Spring Data Elasticsearch
* Reactive Repositories.
*
* @author Brian Clozel
* @since 2.2.0
*/
class ReactiveElasticsearchRepositoriesRegistrar
extends AbstractRepositoryConfigurationSourceSupport {
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableReactiveElasticsearchRepositories.class;
}
@Override
protected Class<?> getConfiguration() {
return EnableElasticsearchRepositoriesConfiguration.class;
}
@Override
protected RepositoryConfigurationExtension getRepositoryConfigurationExtension() {
return new ReactiveElasticsearchRepositoryConfigurationExtension();
}
@EnableReactiveElasticsearchRepositories
private static class EnableElasticsearchRepositoriesConfiguration {
}
}
/*
* 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.data.elasticsearch;
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.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.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.function.client.WebClient;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Elasticsearch Reactive REST
* clients.
*
* @author Brian Clozel
* @since 2.2.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ReactiveRestClients.class, WebClient.class, HttpClient.class })
@EnableConfigurationProperties(ReactiveRestClientProperties.class)
public class ReactiveRestClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ClientConfiguration clientConfiguration(
ReactiveRestClientProperties properties) {
ClientConfiguration.MaybeSecureClientConfigurationBuilder builder = ClientConfiguration
.builder().connectedTo(properties.getEndpoints().toArray(new String[0]));
if (properties.isUseSsl()) {
builder.usingSsl();
}
configureTimeouts(builder, properties);
return builder.build();
}
private void configureTimeouts(
ClientConfiguration.TerminalClientConfigurationBuilder builder,
ReactiveRestClientProperties properties) {
PropertyMapper map = PropertyMapper.get();
map.from(properties.getConnectionTimeout()).whenNonNull()
.to(builder::withConnectTimeout);
map.from(properties.getSocketTimeout()).whenNonNull()
.to(builder::withSocketTimeout);
map.from(properties.getUsername()).whenHasText().to((username) -> {
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(username, properties.getPassword());
builder.withDefaultHeaders(headers);
});
}
@Bean
@ConditionalOnMissingBean
public ReactiveElasticsearchClient reactiveElasticsearchClient(
ClientConfiguration clientConfiguration) {
return ReactiveRestClients.create(clientConfiguration);
}
}
/*
* 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.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;
/**
* Configuration properties for Elasticsearch Reactive REST clients.
*
* @author Brian Clozel
* @since 2.2.0
*/
@ConfigurationProperties(prefix = "spring.data.elasticsearch.client.reactive")
public class ReactiveRestClientProperties {
/**
* 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;
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;
}
}
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2019 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.
......
...@@ -42,6 +42,8 @@ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoC ...@@ -42,6 +42,8 @@ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoC
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
......
/*
* 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.data.alt.elasticsearch;
import org.springframework.boot.autoconfigure.data.elasticsearch.city.City;
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
public interface CityReactiveElasticsearchDbRepository
extends ReactiveElasticsearchRepository<City, Long> {
}
...@@ -41,7 +41,6 @@ import static org.mockito.Mockito.mock; ...@@ -41,7 +41,6 @@ import static org.mockito.Mockito.mock;
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
@Deprecated
@Testcontainers @Testcontainers
public class ElasticsearchAutoConfigurationTests { public class ElasticsearchAutoConfigurationTests {
......
...@@ -28,6 +28,8 @@ import org.springframework.context.annotation.Bean; ...@@ -28,6 +28,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
...@@ -50,6 +52,7 @@ public class ElasticsearchDataAutoConfigurationTests { ...@@ -50,6 +52,7 @@ public class ElasticsearchDataAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ElasticsearchAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(ElasticsearchAutoConfiguration.class,
RestClientAutoConfiguration.class, RestClientAutoConfiguration.class,
ReactiveRestClientAutoConfiguration.class,
ElasticsearchDataAutoConfiguration.class)); ElasticsearchDataAutoConfiguration.class));
@Test @Test
...@@ -75,6 +78,10 @@ public class ElasticsearchDataAutoConfigurationTests { ...@@ -75,6 +78,10 @@ public class ElasticsearchDataAutoConfigurationTests {
public void defaultRestBeansRegistered() { public void defaultRestBeansRegistered() {
this.contextRunner.run((context) -> assertThat(context) this.contextRunner.run((context) -> assertThat(context)
.hasSingleBean(ElasticsearchRestTemplate.class) .hasSingleBean(ElasticsearchRestTemplate.class)
.hasSingleBean(ReactiveElasticsearchTemplate.class)
.hasSingleBean(ElasticsearchConverter.class)
.hasSingleBean(SimpleElasticsearchMappingContext.class)
.hasSingleBean(EntityMapper.class)
.hasSingleBean(ElasticsearchConverter.class)); .hasSingleBean(ElasticsearchConverter.class));
} }
...@@ -94,6 +101,14 @@ public class ElasticsearchDataAutoConfigurationTests { ...@@ -94,6 +101,14 @@ public class ElasticsearchDataAutoConfigurationTests {
.contains("elasticsearchTemplate")); .contains("elasticsearchTemplate"));
} }
@Test
public void customReactiveRestTemplateShouldBeUsed() {
this.contextRunner.withUserConfiguration(CustomReactiveRestTemplate.class)
.run((context) -> assertThat(context)
.getBeanNames(ReactiveElasticsearchTemplate.class).hasSize(1)
.contains("reactiveElasticsearchTemplate"));
}
@Configuration @Configuration
static class CustomTransportTemplate { static class CustomTransportTemplate {
...@@ -114,4 +129,14 @@ public class ElasticsearchDataAutoConfigurationTests { ...@@ -114,4 +129,14 @@ public class ElasticsearchDataAutoConfigurationTests {
} }
@Configuration
static class CustomReactiveRestTemplate {
@Bean
ReactiveElasticsearchTemplate reactiveElasticsearchTemplate() {
return mock(ReactiveElasticsearchTemplate.class);
}
}
} }
/*
* 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.data.elasticsearch;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.data.alt.elasticsearch.CityReactiveElasticsearchDbRepository;
import org.springframework.boot.autoconfigure.data.elasticsearch.city.City;
import org.springframework.boot.autoconfigure.data.elasticsearch.city.ReactiveCityRepository;
import org.springframework.boot.autoconfigure.data.empty.EmptyDataPackage;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.testcontainers.ElasticsearchContainer;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ReactiveElasticsearchRepositoriesAutoConfiguration}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Brian Clozel
*/
@Testcontainers
public class ReactiveElasticsearchRepositoriesAutoConfigurationTests {
@Container
public static ElasticsearchContainer elasticsearch = new ElasticsearchContainer();
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(ReactiveRestClientAutoConfiguration.class,
ReactiveElasticsearchRepositoriesAutoConfiguration.class,
ElasticsearchDataAutoConfiguration.class))
.withPropertyValues(
"spring.data.elasticsearch.client.reactive.endpoints=localhost:"
+ elasticsearch.getMappedHttpPort());
@Test
public void testDefaultRepositoryConfiguration() {
this.contextRunner.withUserConfiguration(TestConfiguration.class)
.run((context) -> assertThat(context)
.hasSingleBean(ReactiveCityRepository.class)
.hasSingleBean(ReactiveElasticsearchTemplate.class));
}
@Test
public void testNoRepositoryConfiguration() {
this.contextRunner.withUserConfiguration(EmptyConfiguration.class)
.run((context) -> assertThat(context)
.hasSingleBean(ReactiveElasticsearchTemplate.class));
}
@Test
public void doesNotTriggerDefaultRepositoryDetectionIfCustomized() {
this.contextRunner.withUserConfiguration(CustomizedConfiguration.class)
.run((context) -> assertThat(context)
.hasSingleBean(CityReactiveElasticsearchDbRepository.class));
}
@Configuration(proxyBeanMethods = false)
@TestAutoConfigurationPackage(City.class)
static class TestConfiguration {
}
@Configuration(proxyBeanMethods = false)
@TestAutoConfigurationPackage(EmptyDataPackage.class)
static class EmptyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@TestAutoConfigurationPackage(ReactiveElasticsearchRepositoriesAutoConfigurationTests.class)
@EnableReactiveElasticsearchRepositories(
basePackageClasses = CityReactiveElasticsearchDbRepository.class)
static class CustomizedConfiguration {
}
}
/*
* 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.data.elasticsearch;
import java.util.HashMap;
import java.util.Map;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.index.get.GetResult;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.testcontainers.ElasticsearchContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ReactiveRestClientAutoConfiguration}
*
* @author Brian Clozel
*/
@Testcontainers
public class ReactiveRestClientAutoConfigurationTests {
@Container
public static ElasticsearchContainer elasticsearch = new ElasticsearchContainer();
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(ReactiveRestClientAutoConfiguration.class));
@Test
public void configureShouldCreateDefaultBeans() {
this.contextRunner.run(
(context) -> assertThat(context).hasSingleBean(ClientConfiguration.class)
.hasSingleBean(ReactiveElasticsearchClient.class));
}
@Test
public void configureWhenCustomClientShouldBackOff() {
this.contextRunner.withUserConfiguration(CustomClientConfiguration.class)
.run((context) -> assertThat(context)
.hasSingleBean(ReactiveElasticsearchClient.class)
.hasBean("customClient"));
}
@Test
public void configureWhenCustomClientConfig() {
this.contextRunner.withUserConfiguration(CustomClientConfigConfiguration.class)
.run((context) -> assertThat(context)
.hasSingleBean(ReactiveElasticsearchClient.class)
.hasSingleBean(ClientConfiguration.class)
.hasBean("customClientConfiguration"));
}
@Test
public void restClientCanQueryElasticsearchNode() {
this.contextRunner.withPropertyValues(
"spring.data.elasticsearch.client.reactive.endpoints=localhost:"
+ elasticsearch.getMappedPort())
.run((context) -> {
ReactiveElasticsearchClient client = context
.getBean(ReactiveElasticsearchClient.class);
Map<String, String> source = new HashMap<>();
source.put("a", "alpha");
source.put("b", "bravo");
IndexRequest index = new IndexRequest("foo", "bar", "1")
.source(source);
GetRequest getRequest = new GetRequest("foo", "bar", "1");
GetResult getResult = client.index(index).then(client.get(getRequest))
.block();
assertThat(getResult.isExists()).isTrue();
});
}
@Configuration(proxyBeanMethods = false)
static class CustomClientConfiguration {
@Bean
public ReactiveElasticsearchClient customClient() {
return mock(ReactiveElasticsearchClient.class);
}
}
@Configuration(proxyBeanMethods = false)
static class CustomClientConfigConfiguration {
@Bean
public ClientConfiguration customClientConfiguration() {
return ClientConfiguration.localhost();
}
}
}
/*
* 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.data.elasticsearch.city;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
/**
* @author Brian Clozel
*/
public interface ReactiveCityRepository extends ReactiveCrudRepository<City, String> {
}
...@@ -52,6 +52,8 @@ ...@@ -52,6 +52,8 @@
:spring-session: https://projects.spring.io/spring-session/ :spring-session: https://projects.spring.io/spring-session/
:spring-framework: https://projects.spring.io/spring-framework/ :spring-framework: https://projects.spring.io/spring-framework/
:spring-security: https://projects.spring.io/spring-security/ :spring-security: https://projects.spring.io/spring-security/
:spring-data-elasticsearch: https://spring.io/projects/spring-data-elasticsearch
:spring-data-elasticsearch-reference: https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/
:spring-data-jpa: https://projects.spring.io/spring-data-jpa/ :spring-data-jpa: https://projects.spring.io/spring-data-jpa/
:spring-security-reference: https://docs.spring.io/spring-security/site/docs/{spring-security-docs-version}/reference/htmlsingle :spring-security-reference: https://docs.spring.io/spring-security/site/docs/{spring-security-docs-version}/reference/htmlsingle
:spring-security-oauth2-reference: https://projects.spring.io/spring-security-oauth/docs/oauth2.html :spring-security-oauth2-reference: https://projects.spring.io/spring-security-oauth/docs/oauth2.html
......
...@@ -4865,12 +4865,15 @@ auto-configuration for Elasticsearch. ...@@ -4865,12 +4865,15 @@ auto-configuration for Elasticsearch.
Spring Boot supports several clients: Spring Boot supports several clients:
* The official Java "Low Level" and "High Level" REST clients * The official Java "Low Level" and "High Level" REST clients
* https://github.com/searchbox-io/Jest[Jest] * The `ReactiveElasticsearchClient` provided by Spring Data Elasticsearch
The transport client is still available but its support has been deprecated in The transport client is still available but its support has been deprecated in
https://github.com/spring-projects/spring-data-elasticsearch[Spring Data Elasticsearch] https://github.com/spring-projects/spring-data-elasticsearch[Spring Data Elasticsearch]
and will be removed in a future release. Spring Boot provides a dedicated "`Starter`", and Elasticsearch itself. It will be removed in a future release.
`spring-boot-starter-data-elasticsearch`. Spring Boot provides a dedicated "`Starter`", `spring-boot-starter-data-elasticsearch`.
The https://github.com/searchbox-io/Jest[Jest] client has been deprecated as well, since
both Elasticsearch and Spring Data Elasticsearch provide official support for REST clients.
[[boot-features-connecting-to-elasticsearch-rest]] [[boot-features-connecting-to-elasticsearch-rest]]
==== Connecting to Elasticsearch using REST clients ==== Connecting to Elasticsearch using REST clients
...@@ -4899,6 +4902,28 @@ If you have the `org.elasticsearch.client:elasticsearch-rest-high-level-client` ...@@ -4899,6 +4902,28 @@ If you have the `org.elasticsearch.client:elasticsearch-rest-high-level-client`
on the classpath, Spring Boot will auto-configure a `RestHighLevelClient`, which wraps on the classpath, Spring Boot will auto-configure a `RestHighLevelClient`, which wraps
any existing `RestClient` bean, reusing its HTTP configuration. any existing `RestClient` bean, reusing its HTTP configuration.
[[boot-features-connecting-to-elasticsearch-reactive-rest]]
==== Connecting to Elasticsearch using Reactive REST clients
{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:
[source,properties,indent=0]
----
spring.elasticsearch.reactive.endpoints=search.example.com:9200
spring.elasticsearch.reactive.use-ssl=true
spring.elasticsearch.reactive.socket-timeout=10s
spring.elasticsearch.reactive.username=user
spring.elasticsearch.reactive.password=secret
----
If the configuration properties are not enough and you'd like to fully control the client
configuration, you can register a custom `ClientConfiguration` bean.
[[boot-features-connecting-to-elasticsearch-jest]] [[boot-features-connecting-to-elasticsearch-jest]]
==== Connecting to Elasticsearch using Jest ==== Connecting to Elasticsearch using Jest
...@@ -4933,17 +4958,12 @@ To take full control over the registration, define a `JestClient` bean. ...@@ -4933,17 +4958,12 @@ To take full control over the registration, define a `JestClient` bean.
[[boot-features-connecting-to-elasticsearch-spring-data]] [[boot-features-connecting-to-elasticsearch-spring-data]]
==== Connecting to Elasticsearch by Using Spring Data ==== Connecting to Elasticsearch by Using Spring Data
To connect to Elasticsearch, you must provide the address of one or more Elasticsearch To connect to Elasticsearch, a `RestHighLevelClient` bean must be defined,
instances. The address can be specified by setting the `spring.elasticsearch.rest.uris` auto-configured by Spring Boot or manually provided by the application (see previous sections).
property to a comma-separated `host:port` list. With this configuration in place, an With this configuration in place, an
`ElasticsearchRestTemplate` or `RestHighLevelClient` can be injected like any other Spring bean, `ElasticsearchRestTemplate` can be injected like any other Spring bean,
as shown in the following example: as shown in the following example:
[source,properties,indent=0]
----
spring.elasticsearch.rest.uris=localhost:9200
----
[source,java,indent=0] [source,java,indent=0]
---- ----
@Component @Component
...@@ -4960,9 +4980,12 @@ as shown in the following example: ...@@ -4960,9 +4980,12 @@ as shown in the following example:
} }
---- ----
If you add your own `ElasticsearchRestTemplate` or `ElasticsearchOperations` `@Bean`, In the presence of `spring-data-elasticsearch` and the required dependencies
it replaces the default given it is named `"elasticsearchTemplate""`. for using a `WebClient` (typically `spring-boot-starter-webflux`), Spring Boot can also
auto-configure a
<<boot-features-connecting-to-elasticsearch-reactive-rest,ReactiveElasticsearchClient>>
and a `ReactiveElasticsearchTemplate` as beans. They are the reactive equivalent of the
other REST clients.
[[boot-features-spring-data-elasticsearch-repositories]] [[boot-features-spring-data-elasticsearch-repositories]]
...@@ -4977,8 +5000,24 @@ now an Elasticsearch `@Document` class rather than a JPA `@Entity`, it works in ...@@ -4977,8 +5000,24 @@ now an Elasticsearch `@Document` class rather than a JPA `@Entity`, it works in
way. way.
TIP: For complete details of Spring Data Elasticsearch, refer to the TIP: For complete details of Spring Data Elasticsearch, refer to the
https://docs.spring.io/spring-data/elasticsearch/docs/[reference documentation]. {spring-data-elasticsearch-reference}[reference documentation].
Spring Boot supports both classic and reactive Elasticsearch repositories, using the
`ElasticsearchRestTemplate` or `ReactiveElasticsearchTemplate` beans. Most likely those
beans are auto-configured by Spring Boot given the required dependencies are present.
If you wish to use your own template for backing the Elasticsearch repositories, you can
add your own `ElasticsearchRestTemplate` or `ElasticsearchOperations` `@Bean`,
as long as it is named `"elasticsearchTemplate"`. Same applies to
`ReactiveElasticsearchTemplate` and `ReactiveElasticsearchOperations`, with the bean
name `"reactiveElasticsearchTemplate"`.
You can choose to disable the repositories support with the following property:
[source,properties,indent=0]
----
spring.data.elasticsearch.repositories.enabled=false
----
[[boot-features-cassandra]] [[boot-features-cassandra]]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment