Commit 4a030d5a authored by Andy Wilkinson's avatar Andy Wilkinson

Drop support for auto-configuring an embedded Elasticsearch node

Elastic have announced [1] that embedded Elasticsearch is no longer
supported. This commit brings us into line with that announcement by
removing the auto-configuration that would create an Elasticsearch
Node and NodeClient.

To use the Elasticsearch auto-configuration, a user must now provide
the address of one or more cluster nodes
(via the spring.elastisearch.cluster-nodes property) which will then
be used to create a TransportClient.

See gh-9374

[1] https://www.elastic.co/blog/elasticsearch-the-server
parent dbabfc22
...@@ -441,7 +441,7 @@ public class HealthIndicatorAutoConfigurationTests { ...@@ -441,7 +441,7 @@ public class HealthIndicatorAutoConfigurationTests {
@Test @Test
public void elasticsearchHealthIndicator() { public void elasticsearchHealthIndicator() {
TestPropertyValues TestPropertyValues
.of("spring.data.elasticsearch.properties.path.home:target", .of("spring.data.elasticsearch.cluster-nodes:localhost:0",
"management.health.diskspace.enabled:false") "management.health.diskspace.enabled:false")
.applyTo(this.context); .applyTo(this.context);
this.context.register(JestClientConfiguration.class, JestAutoConfiguration.class, this.context.register(JestClientConfiguration.class, JestAutoConfiguration.class,
......
...@@ -16,34 +16,18 @@ ...@@ -16,34 +16,18 @@
package org.springframework.boot.autoconfigure.data.elasticsearch; package org.springframework.boot.autoconfigure.data.elasticsearch;
import java.io.Closeable;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.InternalSettingsPreparer;
import org.elasticsearch.node.Node;
import org.elasticsearch.plugins.Plugin;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
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.NodeClientFactoryBean;
import org.springframework.data.elasticsearch.client.TransportClientFactoryBean; import org.springframework.data.elasticsearch.client.TransportClientFactoryBean;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/** /**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
...@@ -55,101 +39,25 @@ import org.springframework.util.StringUtils; ...@@ -55,101 +39,25 @@ import org.springframework.util.StringUtils;
* @since 1.1.0 * @since 1.1.0
*/ */
@Configuration @Configuration
@ConditionalOnClass({ Client.class, TransportClientFactoryBean.class, @ConditionalOnClass({ Client.class, TransportClientFactoryBean.class })
NodeClientFactoryBean.class }) @ConditionalOnProperty(prefix = "spring.data.elasticsearch", name = "cluster-nodes", matchIfMissing = false)
@EnableConfigurationProperties(ElasticsearchProperties.class) @EnableConfigurationProperties(ElasticsearchProperties.class)
public class ElasticsearchAutoConfiguration implements DisposableBean { public class ElasticsearchAutoConfiguration {
private static final Map<String, String> DEFAULTS;
static {
Map<String, String> defaults = new LinkedHashMap<>();
defaults.put("http.enabled", String.valueOf(false));
defaults.put("transport.type", "local");
defaults.put("path.home", System.getProperty("user.dir"));
DEFAULTS = Collections.unmodifiableMap(defaults);
}
private static final Set<String> TRANSPORT_PLUGINS;
static {
Set<String> plugins = new LinkedHashSet<>();
plugins.add("org.elasticsearch.transport.Netty4Plugin");
plugins.add("org.elasticsearch.transport.Netty3Plugin");
TRANSPORT_PLUGINS = Collections.unmodifiableSet(plugins);
}
private static final Log logger = LogFactory
.getLog(ElasticsearchAutoConfiguration.class);
private final ElasticsearchProperties properties; private final ElasticsearchProperties properties;
private Closeable closeable;
public ElasticsearchAutoConfiguration(ElasticsearchProperties properties) { public ElasticsearchAutoConfiguration(ElasticsearchProperties properties) {
this.properties = properties; this.properties = properties;
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public Client elasticsearchClient() { public TransportClient elasticsearchClient() throws Exception {
try {
return createClient();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private Client createClient() throws Exception {
if (StringUtils.hasLength(this.properties.getClusterNodes())) {
return createTransportClient();
}
return createNodeClient();
}
private Client createNodeClient() throws Exception {
Settings.Builder settings = Settings.builder();
for (Map.Entry<String, String> entry : DEFAULTS.entrySet()) {
if (!this.properties.getProperties().containsKey(entry.getKey())) {
settings.put(entry.getKey(), entry.getValue());
}
}
settings.put(this.properties.getProperties());
settings.put("cluster.name", this.properties.getClusterName());
Node node = createNode(settings.build());
this.closeable = node;
node.start();
return node.client();
}
private Node createNode(Settings settings) {
Collection<Class<? extends Plugin>> plugins = findPlugins();
if (plugins.isEmpty()) {
return new Node(settings);
}
return new PluggableNode(settings, plugins);
}
@SuppressWarnings("unchecked")
private Collection<Class<? extends Plugin>> findPlugins() {
for (String candidate : TRANSPORT_PLUGINS) {
if (ClassUtils.isPresent(candidate, null)) {
Class<? extends Plugin> pluginClass = (Class<? extends Plugin>) ClassUtils
.resolveClassName(candidate, null);
return Collections.singleton(pluginClass);
}
}
return Collections.emptySet();
}
private Client createTransportClient() throws Exception {
TransportClientFactoryBean factory = new TransportClientFactoryBean(); TransportClientFactoryBean factory = new TransportClientFactoryBean();
factory.setClusterNodes(this.properties.getClusterNodes()); factory.setClusterNodes(this.properties.getClusterNodes());
factory.setProperties(createProperties()); factory.setProperties(createProperties());
factory.afterPropertiesSet(); factory.afterPropertiesSet();
TransportClient client = factory.getObject(); TransportClient client = factory.getObject();
this.closeable = client;
return client; return client;
} }
...@@ -160,34 +68,4 @@ public class ElasticsearchAutoConfiguration implements DisposableBean { ...@@ -160,34 +68,4 @@ public class ElasticsearchAutoConfiguration implements DisposableBean {
return properties; return properties;
} }
@Override
public void destroy() throws Exception {
if (this.closeable != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Closing Elasticsearch client");
}
this.closeable.close();
}
catch (final Exception ex) {
if (logger.isErrorEnabled()) {
logger.error("Error closing Elasticsearch client: ", ex);
}
}
}
}
/**
* {@link Node} subclass to support {@link Plugin Plugins}.
*/
private static class PluggableNode extends Node {
PluggableNode(Settings preparedSettings,
Collection<Class<? extends Plugin>> classpathPlugins) {
super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, null),
classpathPlugins);
}
}
} }
...@@ -37,8 +37,7 @@ public class ElasticsearchProperties { ...@@ -37,8 +37,7 @@ public class ElasticsearchProperties {
private String clusterName = "elasticsearch"; private String clusterName = "elasticsearch";
/** /**
* Comma-separated list of cluster node addresses. If not specified, starts a client * Comma-separated list of cluster node addresses.
* node.
*/ */
private String clusterNodes; private String clusterNodes;
......
...@@ -230,6 +230,10 @@ public class SpringBootWebSecurityConfiguration { ...@@ -230,6 +230,10 @@ public class SpringBootWebSecurityConfiguration {
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(
(request) -> request.getHeader("Host").equals("whatever"))
.permitAll();
http.requestMatcher(new RequestMatcher() { http.requestMatcher(new RequestMatcher() {
@Override @Override
public boolean matches(HttpServletRequest request) { public boolean matches(HttpServletRequest request) {
......
...@@ -16,15 +16,17 @@ ...@@ -16,15 +16,17 @@
package org.springframework.boot.autoconfigure.data.elasticsearch; package org.springframework.boot.autoconfigure.data.elasticsearch;
import java.util.List;
import org.assertj.core.api.Assertions;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
...@@ -38,6 +40,7 @@ import static org.mockito.Mockito.mock; ...@@ -38,6 +40,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link ElasticsearchAutoConfiguration}. * Tests for {@link ElasticsearchAutoConfiguration}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson
*/ */
public class ElasticsearchAutoConfigurationTests { public class ElasticsearchAutoConfigurationTests {
...@@ -46,57 +49,11 @@ public class ElasticsearchAutoConfigurationTests { ...@@ -46,57 +49,11 @@ public class ElasticsearchAutoConfigurationTests {
private AnnotationConfigApplicationContext context; private AnnotationConfigApplicationContext context;
@Before
public void preventElasticsearchFromConfiguringNetty() {
System.setProperty("es.set.netty.runtime.available.processors", "false");
}
@After @After
public void close() { public void close() {
if (this.context != null) { if (this.context != null) {
this.context.close(); this.context.close();
} }
System.clearProperty("es.set.netty.runtime.available.processors");
}
@Test
public void createNodeClientWithDefaults() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues
.of("spring.data.elasticsearch.properties.monitor.process.refresh_interval:2s",
"spring.data.elasticsearch.properties.path.home:target")
.applyTo(this.context);
this.context.register(PropertyPlaceholderAutoConfiguration.class,
ElasticsearchAutoConfiguration.class);
this.context.refresh();
assertThat(this.context.getBeanNamesForType(Client.class).length).isEqualTo(1);
NodeClient client = (NodeClient) this.context.getBean(Client.class);
assertThat(client.settings().get("monitor.process.refresh_interval"))
.isEqualTo("2s");
assertThat(client.settings().get("transport.type")).isEqualTo("local");
assertThat(client.settings().get("http.enabled")).isEqualTo("false");
}
@Test
public void createNodeClientWithOverrides() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues
.of("spring.data.elasticsearch.properties.monitor.process.refresh_interval:2s",
"spring.data.elasticsearch.properties.path.home:target",
"spring.data.elasticsearch.properties.transport.type:local",
"spring.data.elasticsearch.properties.node.data:true",
"spring.data.elasticsearch.properties.http.enabled:true")
.applyTo(this.context);
this.context.register(PropertyPlaceholderAutoConfiguration.class,
ElasticsearchAutoConfiguration.class);
this.context.refresh();
assertThat(this.context.getBeanNamesForType(Client.class).length).isEqualTo(1);
NodeClient client = (NodeClient) this.context.getBean(Client.class);
assertThat(client.settings().get("monitor.process.refresh_interval"))
.isEqualTo("2s");
assertThat(client.settings().get("transport.type")).isEqualTo("local");
assertThat(client.settings().get("node.data")).isEqualTo("true");
assertThat(client.settings().get("http.enabled")).isEqualTo("true");
} }
@Test @Test
...@@ -106,25 +63,28 @@ public class ElasticsearchAutoConfigurationTests { ...@@ -106,25 +63,28 @@ public class ElasticsearchAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
ElasticsearchAutoConfiguration.class); ElasticsearchAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
assertThat(this.context.getBeanNamesForType(Client.class).length).isEqualTo(1); Assertions.assertThat(this.context.getBeanNamesForType(Client.class).length)
assertThat(this.context.getBean("myClient")) .isEqualTo(1);
Assertions.assertThat(this.context.getBean("myClient"))
.isSameAs(this.context.getBean(Client.class)); .isSameAs(this.context.getBean(Client.class));
} }
@Test @Test
public void createTransportClient() throws Exception { public void createTransportClient() throws Exception {
// We don't have a local elasticsearch server so use an address that's missing
// a port and check the exception
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
new ElasticsearchNodeTemplate().doWithNode((node) -> {
TestPropertyValues TestPropertyValues
.of("spring.data.elasticsearch.cluster-nodes:localhost", .of("spring.data.elasticsearch.cluster-nodes:localhost:"
"spring.data.elasticsearch.properties.path.home:target") + node.getTcpPort(),
"spring.data.elasticsearch.properties.path.home:target/es/client")
.applyTo(this.context); .applyTo(this.context);
this.context.register(PropertyPlaceholderAutoConfiguration.class, this.context.register(PropertyPlaceholderAutoConfiguration.class,
ElasticsearchAutoConfiguration.class); ElasticsearchAutoConfiguration.class);
this.thrown.expect(BeanCreationException.class);
this.thrown.expectMessage("port");
this.context.refresh(); this.context.refresh();
List<DiscoveryNode> connectedNodes = this.context
.getBean(TransportClient.class).connectedNodes();
assertThat(connectedNodes).hasSize(1);
});
} }
@Configuration @Configuration
......
...@@ -48,43 +48,55 @@ public class ElasticsearchDataAutoConfigurationTests { ...@@ -48,43 +48,55 @@ public class ElasticsearchDataAutoConfigurationTests {
@Test @Test
public void templateExists() { public void templateExists() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
new ElasticsearchNodeTemplate().doWithNode((node) -> {
TestPropertyValues TestPropertyValues
.of("spring.data.elasticsearch.properties.path.data:target/data", .of("spring.data.elasticsearch.properties.path.data:target/data",
"spring.data.elasticsearch.properties.path.logs:target/logs") "spring.data.elasticsearch.properties.path.logs:target/logs",
"spring.data.elasticsearch.cluster-nodes:localhost:"
+ node.getTcpPort())
.applyTo(this.context); .applyTo(this.context);
this.context.register(PropertyPlaceholderAutoConfiguration.class, this.context.register(PropertyPlaceholderAutoConfiguration.class,
ElasticsearchAutoConfiguration.class, ElasticsearchAutoConfiguration.class,
ElasticsearchDataAutoConfiguration.class); ElasticsearchDataAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
assertHasSingleBean(ElasticsearchTemplate.class); assertHasSingleBean(ElasticsearchTemplate.class);
});
} }
@Test @Test
public void mappingContextExists() { public void mappingContextExists() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
new ElasticsearchNodeTemplate().doWithNode((node) -> {
TestPropertyValues TestPropertyValues
.of("spring.data.elasticsearch.properties.path.data:target/data", .of("spring.data.elasticsearch.properties.path.data:target/data",
"spring.data.elasticsearch.properties.path.logs:target/logs") "spring.data.elasticsearch.properties.path.logs:target/logs",
"spring.data.elasticsearch.cluster-nodes:localhost:"
+ node.getTcpPort())
.applyTo(this.context); .applyTo(this.context);
this.context.register(PropertyPlaceholderAutoConfiguration.class, this.context.register(PropertyPlaceholderAutoConfiguration.class,
ElasticsearchAutoConfiguration.class, ElasticsearchAutoConfiguration.class,
ElasticsearchDataAutoConfiguration.class); ElasticsearchDataAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
assertHasSingleBean(SimpleElasticsearchMappingContext.class); assertHasSingleBean(SimpleElasticsearchMappingContext.class);
});
} }
@Test @Test
public void converterExists() { public void converterExists() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
new ElasticsearchNodeTemplate().doWithNode((node) -> {
TestPropertyValues TestPropertyValues
.of("spring.data.elasticsearch.properties.path.data:target/data", .of("spring.data.elasticsearch.properties.path.data:target/data",
"spring.data.elasticsearch.properties.path.logs:target/logs") "spring.data.elasticsearch.properties.path.logs:target/logs",
"spring.data.elasticsearch.cluster-nodes:localhost:"
+ node.getTcpPort())
.applyTo(this.context); .applyTo(this.context);
this.context.register(PropertyPlaceholderAutoConfiguration.class, this.context.register(PropertyPlaceholderAutoConfiguration.class,
ElasticsearchAutoConfiguration.class, ElasticsearchAutoConfiguration.class,
ElasticsearchDataAutoConfiguration.class); ElasticsearchDataAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
assertHasSingleBean(ElasticsearchConverter.class); assertHasSingleBean(ElasticsearchConverter.class);
});
} }
private void assertHasSingleBean(Class<?> type) { private void assertHasSingleBean(Class<?> type) {
......
/*
* Copyright 2012-2017 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
*
* http://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.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.function.Consumer;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.InternalSettingsPreparer;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeValidationException;
import org.elasticsearch.transport.Netty4Plugin;
import org.elasticsearch.transport.Transport;
/**
* Helper class for managing an Elasticsearch {@link Node}.
*
* @author Andy Wilkinson
*/
public class ElasticsearchNodeTemplate {
public void doWithNode(Consumer<ElasticsearchNode> consumer) {
System.setProperty("es.set.netty.runtime.available.processors", "false");
Node node = null;
try {
node = startNode();
consumer.accept(new ElasticsearchNode(node));
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
finally {
if (node != null) {
try {
node.close();
}
catch (Exception ex) {
// Continue
}
}
System.clearProperty("es.set.netty.runtime.available.processors");
}
}
private Node startNode() throws NodeValidationException {
Node node = new NettyTransportNode();
node.start();
return node;
}
private static final class NettyTransportNode extends Node {
private NettyTransportNode() {
super(InternalSettingsPreparer.prepareEnvironment(Settings.builder()
.put("path.home", "target/es/node").put("transport.type", "netty4")
.put("http.enabled", true).put("node.portsfile", true)
.put("http.port", 0).put("transport.tcp.port", 0).build(), null),
Arrays.asList(Netty4Plugin.class));
new File("target/es/node/logs").mkdirs();
}
}
public final class ElasticsearchNode {
private final Node node;
private ElasticsearchNode(Node node) {
this.node = node;
}
public int getTcpPort() {
return this.node.injector().getInstance(Transport.class).boundAddress()
.publishAddress().getPort();
}
public int getHttpPort() {
try {
for (String line : Files
.readAllLines(Paths.get("target/es/node/logs/http.ports"))) {
if (line.startsWith("127.0.0.1")) {
return Integer.parseInt(line.substring(line.indexOf(":") + 1));
}
}
throw new IllegalStateException("HTTP port not found");
}
catch (IOException ex) {
throw new IllegalStateException("Failed to read HTTP port", ex);
}
}
}
}
...@@ -23,6 +23,7 @@ import org.junit.Test; ...@@ -23,6 +23,7 @@ import org.junit.Test;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.data.alt.elasticsearch.CityElasticsearchDbRepository; import org.springframework.boot.autoconfigure.data.alt.elasticsearch.CityElasticsearchDbRepository;
import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchNodeTemplate.ElasticsearchNode;
import org.springframework.boot.autoconfigure.data.elasticsearch.city.City; import org.springframework.boot.autoconfigure.data.elasticsearch.city.City;
import org.springframework.boot.autoconfigure.data.elasticsearch.city.CityRepository; import org.springframework.boot.autoconfigure.data.elasticsearch.city.CityRepository;
import org.springframework.boot.autoconfigure.data.empty.EmptyDataPackage; import org.springframework.boot.autoconfigure.data.empty.EmptyDataPackage;
...@@ -37,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -37,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link ElasticsearchRepositoriesAutoConfiguration}. * Tests for {@link ElasticsearchRepositoriesAutoConfiguration}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson
*/ */
public class ElasticsearchRepositoriesAutoConfigurationTests { public class ElasticsearchRepositoriesAutoConfigurationTests {
...@@ -49,46 +51,45 @@ public class ElasticsearchRepositoriesAutoConfigurationTests { ...@@ -49,46 +51,45 @@ public class ElasticsearchRepositoriesAutoConfigurationTests {
@Test @Test
public void testDefaultRepositoryConfiguration() throws Exception { public void testDefaultRepositoryConfiguration() throws Exception {
this.context = new AnnotationConfigApplicationContext(); new ElasticsearchNodeTemplate().doWithNode((node) -> {
addElasticsearchProperties(this.context); load(TestConfiguration.class, node);
this.context.register(TestConfiguration.class,
ElasticsearchAutoConfiguration.class,
ElasticsearchRepositoriesAutoConfiguration.class,
ElasticsearchDataAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertThat(this.context.getBean(CityRepository.class)).isNotNull(); assertThat(this.context.getBean(CityRepository.class)).isNotNull();
assertThat(this.context.getBean(Client.class)).isNotNull(); assertThat(this.context.getBean(Client.class)).isNotNull();
});
} }
@Test @Test
public void testNoRepositoryConfiguration() throws Exception { public void testNoRepositoryConfiguration() throws Exception {
this.context = new AnnotationConfigApplicationContext(); new ElasticsearchNodeTemplate().doWithNode((address) -> {
addElasticsearchProperties(this.context); load(EmptyConfiguration.class, address);
this.context.register(EmptyConfiguration.class,
ElasticsearchAutoConfiguration.class,
ElasticsearchRepositoriesAutoConfiguration.class,
ElasticsearchDataAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertThat(this.context.getBean(Client.class)).isNotNull(); assertThat(this.context.getBean(Client.class)).isNotNull();
});
} }
@Test @Test
public void doesNotTriggerDefaultRepositoryDetectionIfCustomized() { public void doesNotTriggerDefaultRepositoryDetectionIfCustomized() {
new ElasticsearchNodeTemplate().doWithNode((address) -> {
load(CustomizedConfiguration.class, address);
assertThat(this.context.getBean(CityElasticsearchDbRepository.class))
.isNotNull();
});
}
private void load(Class<?> config, ElasticsearchNode node) {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
addElasticsearchProperties(this.context); addElasticsearchProperties(this.context, node);
this.context.register(CustomizedConfiguration.class, this.context.register(config, ElasticsearchAutoConfiguration.class,
ElasticsearchAutoConfiguration.class,
ElasticsearchRepositoriesAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class,
ElasticsearchDataAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class); PropertyPlaceholderAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
assertThat(this.context.getBean(CityElasticsearchDbRepository.class)).isNotNull();
} }
private void addElasticsearchProperties(AnnotationConfigApplicationContext context) { private void addElasticsearchProperties(AnnotationConfigApplicationContext context,
TestPropertyValues.of("spring.data.elasticsearch.properties.path.home:target") ElasticsearchNode node) {
TestPropertyValues.of("spring.data.elasticsearch.properties.path.home:target",
"spring.data.elasticsearch.cluster-nodes:localhost:" + node.getTcpPort())
.applyTo(context); .applyTo(context);
} }
......
...@@ -16,21 +16,18 @@ ...@@ -16,21 +16,18 @@
package org.springframework.boot.autoconfigure.elasticsearch.jest; package org.springframework.boot.autoconfigure.elasticsearch.jest;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.google.gson.Gson; import com.google.gson.Gson;
import io.searchbox.action.Action;
import io.searchbox.client.JestClient; import io.searchbox.client.JestClient;
import io.searchbox.client.JestResult;
import io.searchbox.client.config.HttpClientConfig; import io.searchbox.client.config.HttpClientConfig;
import io.searchbox.client.http.JestHttpClient; import io.searchbox.client.http.JestHttpClient;
import io.searchbox.core.Index; import io.searchbox.core.Index;
import io.searchbox.core.Search; import io.searchbox.core.Search;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.junit.After; import org.junit.After;
...@@ -39,16 +36,10 @@ import org.junit.Rule; ...@@ -39,16 +36,10 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchNodeTemplate;
import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration;
import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration; import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration;
import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -122,22 +113,31 @@ public class JestAutoConfigurationTests { ...@@ -122,22 +113,31 @@ public class JestAutoConfigurationTests {
@Test @Test
public void jestCanCommunicateWithElasticsearchInstance() throws IOException { public void jestCanCommunicateWithElasticsearchInstance() throws IOException {
new File("target/elastic/logs").mkdirs(); new ElasticsearchNodeTemplate().doWithNode((node) -> {
load(HttpPortConfiguration.class, load("spring.elasticsearch.jest.uris=http://localhost:" + node.getHttpPort());
"spring.data.elasticsearch.properties.path.home:target/elastic",
"spring.data.elasticsearch.properties.http.enabled:true",
"spring.data.elasticsearch.properties.http.port:0",
"spring.data.elasticsearch.properties.node.portsfile:true");
JestClient client = this.context.getBean(JestClient.class); JestClient client = this.context.getBean(JestClient.class);
Map<String, String> source = new HashMap<>(); Map<String, String> source = new HashMap<>();
source.put("a", "alpha"); source.put("a", "alpha");
source.put("b", "bravo"); source.put("b", "bravo");
Index index = new Index.Builder(source).index("foo").type("bar").build(); Index index = new Index.Builder(source).index("foo").type("bar").build();
client.execute(index); execute(client, index);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchQuery("a", "alpha")); searchSourceBuilder.query(QueryBuilders.matchQuery("a", "alpha"));
assertThat(client.execute(new Search.Builder(searchSourceBuilder.toString()) assertThat(
.addIndex("foo").build()).getResponseCode()).isEqualTo(200); execute(client,
new Search.Builder(searchSourceBuilder.toString())
.addIndex("foo").build()).getResponseCode())
.isEqualTo(200);
});
}
private JestResult execute(JestClient client, Action<? extends JestResult> action) {
try {
return client.execute(action);
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
} }
private void load(String... environment) { private void load(String... environment) {
...@@ -199,53 +199,4 @@ public class JestAutoConfigurationTests { ...@@ -199,53 +199,4 @@ public class JestAutoConfigurationTests {
} }
@Configuration
@Import(ElasticsearchAutoConfiguration.class)
static class HttpPortConfiguration {
@Bean
public static BeanPostProcessor portPropertyConfigurer() {
return new PortPropertyConfigurer();
}
private static final class PortPropertyConfigurer
implements BeanPostProcessor, ApplicationContextAware {
private ConfigurableApplicationContext applicationContext;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof NodeClient) {
this.applicationContext.getBean(JestProperties.class)
.setUris(Arrays.asList("http://localhost:" + readHttpPort()));
}
return bean;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
private int readHttpPort() {
try {
for (String line : Files
.readAllLines(Paths.get("target/elastic/logs/http.ports"))) {
if (line.startsWith("127.0.0.1")) {
return Integer
.parseInt(line.substring(line.indexOf(":") + 1));
}
}
throw new FatalBeanException("HTTP port not found");
}
catch (IOException ex) {
throw new FatalBeanException("Failed to read HTTP port", ex);
}
}
}
}
} }
...@@ -3712,19 +3712,10 @@ To take full control over the registration, define a `JestClient` bean. ...@@ -3712,19 +3712,10 @@ 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 using Spring Data ==== Connecting to Elasticsearch using Spring Data
You can inject an auto-configured `ElasticsearchTemplate` or Elasticsearch `Client` To connect to Elasticsearch you must provide the address of one or more cluster nodes.
instance as you would any other Spring Bean. By default the instance will embed a The address can be specified by setting the `spring.data.elasticsearch.cluster-nodes`
local in-memory server (a `Node` in Elasticsearch terms) and use the current working property to a comma-separated '`host:port`' list. With this configuration in place, an
directory as the home directory for the server. In this setup, the first thing to do `ElasticsearchTemplate` or `TransportClient` can be injected like any other Spring bean:
is to tell Elasticsearch where to store its files:
[source,properties,indent=0]
----
spring.data.elasticsearch.properties.path.home=/foo/bar
----
Alternatively, you can switch to a remote server (i.e. a `TransportClient`) by setting
`spring.data.elasticsearch.cluster-nodes` to a comma-separated '`host:port`' list.
[source,properties,indent=0] [source,properties,indent=0]
---- ----
...@@ -3736,9 +3727,8 @@ Alternatively, you can switch to a remote server (i.e. a `TransportClient`) by s ...@@ -3736,9 +3727,8 @@ Alternatively, you can switch to a remote server (i.e. a `TransportClient`) by s
@Component @Component
public class MyBean { public class MyBean {
private ElasticsearchTemplate template; private final ElasticsearchTemplate template;
@Autowired
public MyBean(ElasticsearchTemplate template) { public MyBean(ElasticsearchTemplate template) {
this.template = template; this.template = template;
} }
...@@ -3748,8 +3738,8 @@ Alternatively, you can switch to a remote server (i.e. a `TransportClient`) by s ...@@ -3748,8 +3738,8 @@ Alternatively, you can switch to a remote server (i.e. a `TransportClient`) by s
} }
---- ----
If you add a `@Bean` of your own of type `ElasticsearchTemplate` it will replace the If you add your own `ElasticsearchTemplate` or `TransportClient` `@Bean` it will
default. replace the default.
......
...@@ -27,12 +27,6 @@ ...@@ -27,12 +27,6 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency> </dependency>
<!-- Runtime -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Test --> <!-- Test -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
......
# spring.data.elasticsearch.cluster-nodes=localhost:9200
# Home directory of the embedded Elasticsearch instance. Default to the
# current working directory.
#
spring.data.elasticsearch.properties.path.home=target/elastic
spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s
...@@ -18,6 +18,8 @@ package sample.data.elasticsearch; ...@@ -18,6 +18,8 @@ package sample.data.elasticsearch;
import java.io.File; import java.io.File;
import org.assertj.core.api.Assertions;
import org.elasticsearch.client.transport.NoNodeAvailableException;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
...@@ -28,8 +30,6 @@ import org.junit.runners.model.Statement; ...@@ -28,8 +30,6 @@ import org.junit.runners.model.Statement;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.rule.OutputCapture; import org.springframework.boot.test.rule.OutputCapture;
import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link SampleElasticsearchApplication}. * Tests for {@link SampleElasticsearchApplication}.
* *
...@@ -45,9 +45,28 @@ public class SampleElasticsearchApplicationTests { ...@@ -45,9 +45,28 @@ public class SampleElasticsearchApplicationTests {
@Test @Test
public void testDefaultSettings() throws Exception { public void testDefaultSettings() throws Exception {
try {
new SpringApplicationBuilder(SampleElasticsearchApplication.class).run(); new SpringApplicationBuilder(SampleElasticsearchApplication.class).run();
}
catch (Exception ex) {
if (!elasticsearchRunning(ex)) {
return;
}
throw ex;
}
String output = this.outputCapture.toString(); String output = this.outputCapture.toString();
assertThat(output).contains("firstName='Alice', lastName='Smith'"); Assertions.assertThat(output).contains("firstName='Alice', lastName='Smith'");
}
private boolean elasticsearchRunning(Exception ex) {
Throwable candidate = ex;
while (candidate != null) {
if (candidate instanceof NoNodeAvailableException) {
return false;
}
candidate = candidate.getCause();
}
return true;
} }
static class SkipOnWindows implements TestRule { static class SkipOnWindows implements TestRule {
......
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