Detect search engine by reusing the elasticsearch subsystem client

This commit is contained in:
vivekkr0311
2026-08-05 14:15:08 +05:30
parent 8a420d24ba
commit 8a62e863d8
8 changed files with 223 additions and 142 deletions
@@ -1,5 +1,5 @@
# Set root logger level to error
rootLogger.level=error
rootLogger.level=info
rootLogger.appenderRef.stdout.ref=ConsoleAppender
rootLogger.appenderRef.rolling.ref=RollingAppender
@@ -473,4 +473,4 @@ logger.alfresco-rest-api-probes-ProbeEntityResource.level=info
# ActiveMQ
logger.apache-activemq-transport-failover.name=org.apache.activemq.transport.failover
logger.apache-activemq-transport-failover.level=warn
logger.apache-activemq-transport-failover.level=warn
@@ -1,83 +0,0 @@
/*-
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2026 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.search;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class DetectSearchEngine extends AbstractLifecycleBean {
private static final Log logger = LogFactory.getLog(DetectSearchEngine.class);
private static final String[] searchEngineUrls = {
"http://elasticsearch:9200",
"http://elasticsearch:9200/_license"
};
@Override
protected void onBootstrap(ApplicationEvent event) {
findSearchEngineInfos();
}
@Override
protected void onShutdown(ApplicationEvent event) {
// No-op
}
private static SearchEngineInfo searchEngineInfo(){
// make http request to find search engine info
return new SearchEngineInfo();
}
private static void findSearchEngineInfos(){
for(int i = 0; i < searchEngineUrls.length; i++){
HttpResponse<String> response = makeHttpRequest(searchEngineUrls[i]);
logger.info("Search Engine response: " + response);
}
}
private static HttpResponse<String> makeHttpRequest(String url) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.build();
try (HttpClient client = HttpClient.newHttpClient()){
return client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException e) {
throw new RuntimeException("HTTP request to get the search engine info failed", e);
} catch (InterruptedException e){
Thread.currentThread().interrupt();
throw new RuntimeException("HTTP request to get the search engine info was interrupted", e);
}
}
}
@@ -1,56 +0,0 @@
/*-
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2026 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.search;
public class SearchEngineInfo {
String searchEngineName;
String searchEngineVersion;
String searchEngineLuceneVersion;
public String getSearchEngineName() {
return searchEngineName;
}
public void setSearchEngineName(String searchEngineName) {
this.searchEngineName = searchEngineName;
}
public String getSearchEngineVersion() {
return searchEngineVersion;
}
public void setSearchEngineVersion(String searchEngineVersion) {
this.searchEngineVersion = searchEngineVersion;
}
public String getSearchEngineLuceneVersion() {
return searchEngineLuceneVersion;
}
public void setSearchEngineLuceneVersion(String searchEngineLuceneVersion) {
this.searchEngineLuceneVersion = searchEngineLuceneVersion;
}
}
@@ -5,7 +5,6 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="detectSearchEngine" class="org.alfresco.rest.api.search.DetectSearchEngine" />
<bean id="publicapi.webscripts.store" parent="webscripts.repoclasspathstore">
<property name="mustExist"><value>true</value></property>
<property name="classPath"><value>alfresco/templates/publicapi</value></property>
@@ -0,0 +1,96 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2026 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.search;
/**
* Holder for the search engine details detected when the search subsystem starts (ACS-12415).
* <p>
* The values are populated by {@link org.alfresco.repo.search.impl.elasticsearch.SearchEngineInfoDetector}
* which issues a single {@code GET /} against the configured engine using the elasticsearch
* subsystem's existing REST client. This bean is defined in the parent (repository) application
* context and exposed under the id {@code searchEngineInfo} so that the pagination API and other
* components can inject it directly:
* <ul>
* <li>{@code searchengine.name} &rarr; {@link #getSearchEngineName()} ("opensearch" or "elasticsearch")</li>
* <li>{@code searchengine.version} &rarr; {@link #getSearchEngineVersion()} (for example "2.11.1")</li>
* <li>{@code searchengine.lucene.version} &rarr; {@link #getSearchEngineLuceneVersion()} (for example "9.7.0")</li>
* </ul>
* The fields are {@code volatile} because they are written by the subsystem start-up thread and
* read by request-processing threads.
*/
public class SearchEngineInfo
{
/** Provider name reported for an Elasticsearch backend. */
public static final String PROVIDER_ELASTICSEARCH = "elasticsearch";
/** Provider name reported for an OpenSearch backend. */
public static final String PROVIDER_OPENSEARCH = "opensearch";
/** Value used when a detail has not been (or could not be) determined. */
public static final String UNKNOWN = "unknown";
private volatile String searchEngineName = UNKNOWN;
private volatile String searchEngineVersion = UNKNOWN;
private volatile String searchEngineLuceneVersion = UNKNOWN;
public String getSearchEngineName()
{
return searchEngineName;
}
public void setSearchEngineName(String searchEngineName)
{
this.searchEngineName = searchEngineName;
}
public String getSearchEngineVersion()
{
return searchEngineVersion;
}
public void setSearchEngineVersion(String searchEngineVersion)
{
this.searchEngineVersion = searchEngineVersion;
}
public String getSearchEngineLuceneVersion()
{
return searchEngineLuceneVersion;
}
public void setSearchEngineLuceneVersion(String searchEngineLuceneVersion)
{
this.searchEngineLuceneVersion = searchEngineLuceneVersion;
}
@Override
public String toString()
{
return "SearchEngineInfo{name=" + searchEngineName
+ ", version=" + searchEngineVersion
+ ", luceneVersion=" + searchEngineLuceneVersion + "}";
}
}
@@ -0,0 +1,108 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2026 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.search.impl.elasticsearch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.alfresco.repo.search.SearchEngineInfo;
import org.alfresco.repo.search.impl.elasticsearch.client.ElasticsearchHttpClientFactory;
/**
* Detects the configured search engine (Elasticsearch vs OpenSearch) and its version when the
* elasticsearch subsystem starts, storing the result in the shared {@link SearchEngineInfo} bean.
* <p>
* This replaces the previous standalone {@code DetectSearchEngine} lifecycle bean, which used a raw
* {@link java.net.http.HttpClient} with hardcoded {@code http://elasticsearch:9200} URLs. Instead,
* it reuses the subsystem's existing REST client ({@link ElasticsearchHttpClientFactory}, already
* configured via {@code elasticsearch.host}, {@code elasticsearch.port}, {@code elasticsearch.baseUrl}
* and the associated TLS / authentication properties) and issues a single {@code GET /} through
* {@link org.opensearch.client.opensearch.OpenSearchClient#info()}.
* <p>
* Detection rules (from the {@code version} object in the response):
* <ul>
* <li>{@code version.distribution == "opensearch"} &rarr; provider is OpenSearch</li>
* <li>{@code version.distribution} absent &rarr; provider is Elasticsearch</li>
* <li>{@code version.number} &rarr; engine version</li>
* <li>{@code version.lucene_version} &rarr; Lucene version</li>
* </ul>
* If detection fails (for example the engine is unreachable at start-up), a warning is logged and
* safe default values are used so that subsystem start-up is never blocked.
*/
public class SearchEngineInfoDetector
{
private static final Log logger = LogFactory.getLog(SearchEngineInfoDetector.class);
private final ElasticsearchHttpClientFactory httpClientFactory;
private final SearchEngineInfo searchEngineInfo;
public SearchEngineInfoDetector(ElasticsearchHttpClientFactory httpClientFactory, SearchEngineInfo searchEngineInfo)
{
this.httpClientFactory = httpClientFactory;
this.searchEngineInfo = searchEngineInfo;
}
/**
* Query the configured search engine and populate {@link SearchEngineInfo}. Invoked as the
* Spring {@code init-method} when the elasticsearch subsystem context starts.
*/
public void detect()
{
try
{
var version = httpClientFactory.getElasticsearchClient().info().version();
String distribution = version.distribution();
String provider = (distribution != null && distribution.equalsIgnoreCase(SearchEngineInfo.PROVIDER_OPENSEARCH))
? SearchEngineInfo.PROVIDER_OPENSEARCH
: SearchEngineInfo.PROVIDER_ELASTICSEARCH;
searchEngineInfo.setSearchEngineName(provider);
searchEngineInfo.setSearchEngineVersion(defaultIfBlank(version.number()));
searchEngineInfo.setSearchEngineLuceneVersion(defaultIfBlank(version.luceneVersion()));
logger.info("Detected search engine: name=" + searchEngineInfo.getSearchEngineName()
+ ", version=" + searchEngineInfo.getSearchEngineVersion()
+ ", luceneVersion=" + searchEngineInfo.getSearchEngineLuceneVersion());
}
catch (Exception e)
{
searchEngineInfo.setSearchEngineName(SearchEngineInfo.PROVIDER_ELASTICSEARCH);
searchEngineInfo.setSearchEngineVersion(SearchEngineInfo.UNKNOWN);
searchEngineInfo.setSearchEngineLuceneVersion(SearchEngineInfo.UNKNOWN);
logger.warn("Unable to detect the search engine via GET / using the elasticsearch subsystem client; "
+ "defaulting to name=" + SearchEngineInfo.PROVIDER_ELASTICSEARCH
+ ", version=" + SearchEngineInfo.UNKNOWN
+ ". Cause: " + e.getMessage(), e);
}
}
private static String defaultIfBlank(String value)
{
return (value == null || value.isBlank()) ? SearchEngineInfo.UNKNOWN : value;
}
}
@@ -1038,6 +1038,12 @@
</list>
</property>
</bean>
<!-- ACS-12415: holder for the search engine details detected at subsystem start-up. -->
<!-- It is populated by searchEngineInfoDetector, which lives in the elasticsearch subsystem -->
<!-- (child) context, and is declared here in the parent context so that remote-api components -->
<!-- (for example the pagination API) can inject it directly. -->
<bean id="searchEngineInfo" class="org.alfresco.repo.search.SearchEngineInfo" />
<bean id="solr" class="org.alfresco.repo.search.impl.solr.SolrChildApplicationContextFactory" parent="abstractPropertyBackedBean">
<property name="autoStart">
@@ -91,6 +91,17 @@
<constructor-arg value="${elasticsearch.createIndexIfNotExists}" />
</bean>
<!-- ACS-12415: detect the search engine (Elasticsearch vs OpenSearch) and its version at -->
<!-- subsystem start-up by reusing this subsystem's OpenSearch REST client (a single GET /). -->
<!-- The result is stored in the parent-context searchEngineInfo bean so that other components -->
<!-- (for example the pagination API) can inject it. -->
<bean id="searchEngineInfoDetector"
class="org.alfresco.repo.search.impl.elasticsearch.SearchEngineInfoDetector"
init-method="detect">
<constructor-arg ref="elasticsearchHttpClientFactory" />
<constructor-arg ref="searchEngineInfo" />
</bean>
<bean id="elasticsearchIndexService"
class="org.alfresco.repo.search.impl.elasticsearch.contentmodelsync.ElasticsearchIndexService">
<constructor-arg ref="elasticsearchHttpClientFactory" />