diff --git a/src/main/java/org/phoebus/channelfinder/configuration/ElasticConfig.java b/src/main/java/org/phoebus/channelfinder/configuration/ElasticConfig.java index a7fc37e1..9a868bd9 100644 --- a/src/main/java/org/phoebus/channelfinder/configuration/ElasticConfig.java +++ b/src/main/java/org/phoebus/channelfinder/configuration/ElasticConfig.java @@ -19,13 +19,8 @@ import co.elastic.clients.elasticsearch.indices.IndexSettings; import co.elastic.clients.elasticsearch.indices.PutIndicesSettingsRequest; import co.elastic.clients.elasticsearch.indices.PutIndicesSettingsResponse; -import co.elastic.clients.json.jackson.JacksonJsonpMapper; -import co.elastic.clients.transport.ElasticsearchTransport; import co.elastic.clients.transport.endpoints.BooleanResponse; -import co.elastic.clients.transport.rest_client.RestClientTransport; -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.ServletContextEvent; -import jakarta.servlet.ServletContextListener; +import jakarta.annotation.PostConstruct; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; @@ -42,10 +37,9 @@ import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.phoebus.channelfinder.common.TextUtil; -import org.phoebus.channelfinder.entity.Property; -import org.phoebus.channelfinder.entity.Tag; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -55,15 +49,16 @@ * @author Kunal Shroff {@literal } */ @Configuration -@ConfigurationProperties(prefix = "elasticsearch") @ComponentScan(basePackages = {"org.phoebus.channelfinder"}) @PropertySource(value = "classpath:application.properties") -public class ElasticConfig implements ServletContextListener { +public class ElasticConfig { private static final Logger logger = Logger.getLogger(ElasticConfig.class.getName()); - private ElasticsearchClient searchClient; - private ElasticsearchClient indexClient; + // Used to retrieve the auto-configured ElasticsearchClient lazily in @PostConstruct, + // avoiding a circular dependency (this bean provides RestClient → auto-config builds + // ElasticsearchClient from it). + @Autowired private ApplicationContext applicationContext; @Value("${elasticsearch.network.host:localhost}") private String host; @@ -118,51 +113,37 @@ public int getES_MAX_RESULT_WINDOW_SIZE() { return ES_QUERY_SIZE; } - ObjectMapper objectMapper = - new ObjectMapper() - .addMixIn(Tag.class, Tag.OnlyTag.class) - .addMixIn(Property.class, Property.OnlyProperty.class); + public ElasticsearchClient getElasticsearchClient() { + return applicationContext.getBean(ElasticsearchClient.class); + } - private static ElasticsearchClient createClient( - ElasticsearchClient currentClient, - ObjectMapper objectMapper, - HttpHost[] httpHosts, - String createIndices, - ElasticConfig config) { - ElasticsearchClient client; - if (currentClient == null) { - // Create the low-level client - RestClientBuilder clientBuilder = RestClient.builder(httpHosts); - // Configure authentication - if (!config.authorizationHeader.isEmpty()) { - clientBuilder.setDefaultHeaders( - new Header[] {new BasicHeader("Authorization", config.authorizationHeader)}); - if (!config.username.isEmpty() || !config.password.isEmpty()) { - logger.warning( - "elasticsearch.authorization_header is set, ignoring elasticsearch.username and elasticsearch.password."); - } - } else if (!config.username.isEmpty() || !config.password.isEmpty()) { - final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials( - AuthScope.ANY, new UsernamePasswordCredentials(config.username, config.password)); - clientBuilder.setHttpClientConfigCallback( - httpClientBuilder -> - httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); + /** + * Provides the low-level Elasticsearch {@link RestClient} built from the {@code elasticsearch.*} + * connection properties. Spring Boot's {@code ElasticsearchClientAutoConfiguration} detects this + * bean and uses it to auto-configure {@code ElasticsearchTransport} and {@code + * ElasticsearchClient}, which in turn activates the {@code /actuator/health} Elasticsearch + * indicator. + */ + @Bean + public RestClient restClient() { + RestClientBuilder clientBuilder = RestClient.builder(getHttpHosts()); + if (!authorizationHeader.isEmpty()) { + clientBuilder.setDefaultHeaders( + new Header[] {new BasicHeader("Authorization", authorizationHeader)}); + if (!username.isEmpty() || !password.isEmpty()) { + logger.warning( + "elasticsearch.authorization.header is set, ignoring" + + " elasticsearch.authorization.username and elasticsearch.authorization.password."); } - RestClient httpClient = clientBuilder.build(); - - // Create the Java API Client with the same low level client - ElasticsearchTransport transport = - new RestClientTransport(httpClient, new JacksonJsonpMapper(objectMapper)); - - client = new ElasticsearchClient(transport); - } else { - client = currentClient; - } - if (Boolean.parseBoolean(createIndices)) { - config.elasticIndexValidation(client); + } else if (!username.isEmpty() || !password.isEmpty()) { + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + AuthScope.ANY, new UsernamePasswordCredentials(username, password)); + clientBuilder.setHttpClientConfigCallback( + httpClientBuilder -> + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); } - return client; + return clientBuilder.build(); } private HttpHost[] getHttpHosts() { @@ -172,47 +153,33 @@ private HttpHost[] getHttpHosts() { boolean portIsDefault = (port == 9200); if (hostUrlsIsDefault && (!hostIsDefault || !portIsDefault)) { logger.warning( - "Specifying elasticsearch.network.host and elasticsearch.http.port is deprecated, please consider using elasticsearch.host_urls instead."); + "Specifying elasticsearch.network.host and elasticsearch.http.port is deprecated," + + " please consider using elasticsearch.host_urls instead."); return new HttpHost[] {new HttpHost(host, port)}; } else { if (!hostIsDefault) { logger.warning( - "Only one of elasticsearch.host_urls and elasticsearch.network.host can be set, ignoring elasticsearch.network.host."); + "Only one of elasticsearch.host_urls and elasticsearch.network.host can be set," + + " ignoring elasticsearch.network.host."); } if (!portIsDefault) { logger.warning( - "Only one of elasticsearch.host_urls and elasticsearch.http.port can be set, ignoring elasticsearch.http.port."); + "Only one of elasticsearch.host_urls and elasticsearch.http.port can be set," + + " ignoring elasticsearch.http.port."); } return Arrays.stream(httpHostUrls).map(HttpHost::create).toArray(HttpHost[]::new); } } - @Bean({"searchClient"}) - public ElasticsearchClient getSearchClient() { - searchClient = createClient(searchClient, objectMapper, getHttpHosts(), createIndices, this); - return searchClient; - } - - @Bean({"indexClient"}) - public ElasticsearchClient getIndexClient() { - indexClient = createClient(indexClient, objectMapper, getHttpHosts(), createIndices, this); - return indexClient; - } - - @Override - public void contextInitialized(ServletContextEvent sce) { - logger.log(Level.INFO, "Initializing a new Transport clients."); - } - - @Override - public void contextDestroyed(ServletContextEvent sce) { - logger.log(Level.INFO, "Closing the default Transport clients."); - if (searchClient != null) searchClient.shutdown(); - if (indexClient != null) indexClient.shutdown(); + @PostConstruct + public void init() { + if (Boolean.parseBoolean(createIndices)) { + elasticIndexValidation(applicationContext.getBean(ElasticsearchClient.class)); + } } /** - * Create the olog indices and templates if they don't exist + * Create the ChannelFinder indices and templates if they don't exist * * @param client client connected to elasticsearch */ diff --git a/src/main/java/org/phoebus/channelfinder/configuration/PopulateDBConfiguration.java b/src/main/java/org/phoebus/channelfinder/configuration/PopulateDBConfiguration.java index 59c42d89..6e6387f1 100644 --- a/src/main/java/org/phoebus/channelfinder/configuration/PopulateDBConfiguration.java +++ b/src/main/java/org/phoebus/channelfinder/configuration/PopulateDBConfiguration.java @@ -31,7 +31,6 @@ import org.phoebus.channelfinder.entity.Property; import org.phoebus.channelfinder.entity.Tag; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; /** @@ -113,9 +112,7 @@ public class PopulateDBConfiguration { @Autowired ElasticConfig esService; - @Autowired - @Qualifier("indexClient") - ElasticsearchClient client; + @Autowired ElasticsearchClient client; public static final ObjectMapper mapper = new ObjectMapper(); diff --git a/src/main/java/org/phoebus/channelfinder/repository/ChannelRepository.java b/src/main/java/org/phoebus/channelfinder/repository/ChannelRepository.java index 10a4877d..102c5b22 100644 --- a/src/main/java/org/phoebus/channelfinder/repository/ChannelRepository.java +++ b/src/main/java/org/phoebus/channelfinder/repository/ChannelRepository.java @@ -57,7 +57,6 @@ import org.phoebus.channelfinder.entity.SearchResult; import org.phoebus.channelfinder.entity.Tag; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.data.repository.CrudRepository; @@ -75,9 +74,7 @@ public class ChannelRepository implements CrudRepository { @Autowired ElasticConfig esService; - @Autowired - @Qualifier("indexClient") - ElasticsearchClient client; + @Autowired ElasticsearchClient client; @Value("${repository.chunk.size:10000}") private int chunkSize; diff --git a/src/main/java/org/phoebus/channelfinder/repository/PropertyRepository.java b/src/main/java/org/phoebus/channelfinder/repository/PropertyRepository.java index 696616d7..dd148fbc 100644 --- a/src/main/java/org/phoebus/channelfinder/repository/PropertyRepository.java +++ b/src/main/java/org/phoebus/channelfinder/repository/PropertyRepository.java @@ -37,7 +37,6 @@ import org.phoebus.channelfinder.entity.Property; import org.phoebus.channelfinder.entity.Property.OnlyNameOwnerProperty; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; import org.springframework.data.repository.CrudRepository; import org.springframework.http.HttpStatus; @@ -52,9 +51,7 @@ public class PropertyRepository implements CrudRepository { private static final Logger logger = Logger.getLogger(PropertyRepository.class.getName()); - @Autowired - @Qualifier("indexClient") - ElasticsearchClient client; + @Autowired ElasticsearchClient client; @Autowired ElasticConfig esService; diff --git a/src/main/java/org/phoebus/channelfinder/repository/TagRepository.java b/src/main/java/org/phoebus/channelfinder/repository/TagRepository.java index 37a30497..a82cabea 100644 --- a/src/main/java/org/phoebus/channelfinder/repository/TagRepository.java +++ b/src/main/java/org/phoebus/channelfinder/repository/TagRepository.java @@ -38,7 +38,6 @@ import org.phoebus.channelfinder.entity.Tag; import org.phoebus.channelfinder.entity.Tag.OnlyTag; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; import org.springframework.data.repository.CrudRepository; import org.springframework.http.HttpStatus; @@ -55,9 +54,7 @@ public class TagRepository implements CrudRepository { @Autowired ElasticConfig esService; - @Autowired - @Qualifier("indexClient") - ElasticsearchClient client; + @Autowired ElasticsearchClient client; @Autowired ChannelRepository channelRepository; diff --git a/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelScrollController.java b/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelScrollController.java index 893d17bf..2af0514f 100644 --- a/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelScrollController.java +++ b/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelScrollController.java @@ -21,7 +21,6 @@ import org.phoebus.channelfinder.entity.Channel; import org.phoebus.channelfinder.entity.Scroll; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.http.HttpStatus; import org.springframework.util.MultiValueMap; @@ -38,9 +37,7 @@ public class ChannelScrollController implements org.phoebus.channelfinder.rest.a @Autowired ElasticConfig esService; - @Autowired - @Qualifier("indexClient") - ElasticsearchClient client; + @Autowired ElasticsearchClient client; @Override public Scroll query(MultiValueMap allRequestParams) { diff --git a/src/main/java/org/phoebus/channelfinder/rest/controller/InfoController.java b/src/main/java/org/phoebus/channelfinder/rest/controller/InfoController.java index 3a0a96cb..e05669f4 100644 --- a/src/main/java/org/phoebus/channelfinder/rest/controller/InfoController.java +++ b/src/main/java/org/phoebus/channelfinder/rest/controller/InfoController.java @@ -11,7 +11,6 @@ import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; -import org.phoebus.channelfinder.configuration.ElasticConfig; import org.phoebus.channelfinder.rest.api.IInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -29,7 +28,7 @@ public class InfoController implements IInfo { @Value("${channelfinder.version:4.7.0}") private String version; - @Autowired private ElasticConfig esService; + @Autowired private ElasticsearchClient elasticsearchClient; private static final ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); @@ -44,8 +43,7 @@ public String info() { Map elasticInfo = new LinkedHashMap<>(); try { - ElasticsearchClient client = esService.getSearchClient(); - InfoResponse response = client.info(); + InfoResponse response = elasticsearchClient.info(); elasticInfo.put("status", "Connected"); elasticInfo.put("clusterName", response.clusterName()); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d4db1293..3ff6276d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -81,8 +81,8 @@ elasticsearch.http.port=9200 # Elasticsearch sever. This can be used for authentication using tokens or API # keys. # -# For example, for token authentication, set this to ?Bearer abcd1234?, where -# ?abcd1234? is the token. For API key authentication, set this to the Base64 +# For example, for token authentication, set this to "Bearer abcd1234", where +# "abcd1234" is the token. For API key authentication, set this to the Base64 # encoded version of the concatenation of the API key ID and the API key # secret, separated by a colon. See # https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/8.12/_other_authentication_methods.html diff --git a/src/test/java/org/phoebus/channelfinder/ElasticConfigIT.java b/src/test/java/org/phoebus/channelfinder/ElasticConfigIT.java index a731f13e..56baa51b 100644 --- a/src/test/java/org/phoebus/channelfinder/ElasticConfigIT.java +++ b/src/test/java/org/phoebus/channelfinder/ElasticConfigIT.java @@ -20,8 +20,8 @@ static void teardown(ElasticConfig elasticConfig) throws IOException { elasticConfig.getES_TAG_INDEX() }; for (String index : indexes) { - if (elasticConfig.getSearchClient().indices().exists(b -> b.index(index)).value()) { - elasticConfig.getSearchClient().indices().delete(b -> b.index(index)); + if (elasticConfig.getElasticsearchClient().indices().exists(b -> b.index(index)).value()) { + elasticConfig.getElasticsearchClient().indices().delete(b -> b.index(index)); } } } @@ -35,6 +35,6 @@ static void teardown(ElasticConfig elasticConfig) throws IOException { * @param elasticConfig Bean with configuration */ static void setUp(ElasticConfig elasticConfig) { - elasticConfig.elasticIndexValidation(elasticConfig.getSearchClient()); + elasticConfig.elasticIndexValidation(elasticConfig.getElasticsearchClient()); } }