This is an automated email from the ASF dual-hosted git repository.
epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new 8576dc8974a SOLR-16396: Migrate remaining GET /config/{component}
sections to JAX-RS (#5174)
8576dc8974a is described below
commit 8576dc8974a8e4ceef57a3ce6ec6685c86c8bac8
Author: Jalaz Kumar <[email protected]>
AuthorDate: Wed Oct 21 02:46:16 2026 +0530
SOLR-16396: Migrate remaining GET /config/{component} sections to JAX-RS
(#5174)
---
.../SOLR-16396-config-remaining-get-sections.yml | 13 ++++
.../apache/solr/client/api/endpoint/ConfigApi.java | 17 ++---
.../org/apache/solr/handler/SolrConfigHandler.java | 13 +---
.../apache/solr/handler/admin/api/GetConfig.java | 45 +++++++++++--
.../solr/handler/admin/api/GetConfigAPI.java | 38 +----------
.../solr/handler/admin/V2ConfigAPIMappingTest.java | 12 ----
.../solr/handler/admin/api/GetConfigTest.java | 76 +++++++++++++++++++---
.../configuration-guide/pages/config-api.adoc | 19 ++++--
8 files changed, 148 insertions(+), 85 deletions(-)
diff --git a/changelog/unreleased/SOLR-16396-config-remaining-get-sections.yml
b/changelog/unreleased/SOLR-16396-config-remaining-get-sections.yml
new file mode 100644
index 00000000000..b2b23a0cf94
--- /dev/null
+++ b/changelog/unreleased/SOLR-16396-config-remaining-get-sections.yml
@@ -0,0 +1,13 @@
+title: >
+ Migrate remaining GET /config/{component} sections to JAX-RS.
+ V2 renames listener, queryParser, valueSourceParser, cache, transformer,
updateProcessor, queryConverter and expressible to
+ listeners, queryParsers, valueSourceParsers, caches, transformers,
updateProcessors, queryConverters and expressibles respectively. (V1 unchanged)
+type: changed
+authors:
+ - name: Jalaz Kumar
+ nick: jaykay12
+ - name: Eric Pugh
+ nick: epugh
+links:
+ - name: SOLR-16396
+ url: https://issues.apache.org/jira/browse/SOLR-16396
diff --git
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ConfigApi.java
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ConfigApi.java
index f10026be924..70aaf6c6b19 100644
--- a/solr/api/src/java/org/apache/solr/client/api/endpoint/ConfigApi.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ConfigApi.java
@@ -91,12 +91,10 @@ public interface ConfigApi {
@Parameter(description = "Include package metadata
(_packageinfo_)")
boolean meta);
- // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS
("searchComponents"
- // and "queryResponseWriters" here are the external, pluralized names for
the internal
- // "searchComponent"/"queryResponseWriter" sections; see
GetConfig#toExternalComponentName)
+ // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS
@GET
@Path(
-
"/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriters}")
+
"/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriters|luceneMatchVersion|initParams|listeners|requestDispatcher|jmx|queryParsers|valueSourceParsers|transformers|updateProcessors|updateRequestProcessorChain|queryConverters|caches|circuitBreaker|expressibles|schemaFactory|restManager|statsCache|recoveryStrategy|indexReaderFactory|indexConfigdeletionPolicy|updateHandlerupdateLog}")
@StoreApiParameters
@Operation(
summary = "Fetch a config section",
@@ -105,12 +103,15 @@ public interface ConfigApi {
@PathParam("component") @Parameter(description = "Name of the config
component")
String component);
- // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS
("searchComponents"
- // and "queryResponseWriters" here are the external, pluralized names for
the internal
- // "searchComponent"/"queryResponseWriter" sections; see
GetConfig#toExternalComponentName)
+ // TODO: Keep this regex in sync with
GetConfig.MIGRATED_CONFIG_COMPONENTS. Sections without
+ // REQUIRE_NAME in SolrConfig's plugin registration (initParams, listeners,
+ // updateRequestProcessorChain, circuitBreaker) are deliberately excluded
here: they're
+ // serialized as a List rather than a name-keyed Map, so by-name lookup
isn't meaningful for
+ // them (see GetConfig.filterByComponentName).
+ // Scalars / non-plugin sections (luceneMatchVersion, jmx etc) are also
omitted.
@GET
@Path(
-
"/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriters}/{componentName}")
+
"/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriters|queryParsers|valueSourceParsers|transformers|updateProcessors|queryConverters|caches|expressibles|schemaFactory|restManager|statsCache|recoveryStrategy|indexReaderFactory}/{componentName}")
@StoreApiParameters
@Operation(
summary = "Fetch a single named entry within a config section",
diff --git a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
index 341ecc2f096..d44e7fe9c53 100644
--- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
@@ -237,16 +237,9 @@ public class SolrConfigHandler extends RequestHandlerBase
: getConfig.getConfigComponentByName(
parts.get(1), componentName,
req.getParams().getBool("meta", false)));
} else {
- Map<String, Object> m =
- Utils.convertToMap(req.getCore().getSolrConfig(), new
LinkedHashMap<>());
- Map<String, Object> val = new LinkedHashMap<>();
- val.put(parts.get(1), m.get(parts.get(1)));
- String componentName = req.getParams().get("componentName");
- if (componentName != null) {
- GetConfig.filterByComponentName(
- val, parts.get(1), componentName,
req.getParams().getBool("meta", false), req);
- }
- resp.add("config", val);
+ throw new SolrException(
+ SolrException.ErrorCode.NOT_FOUND,
+ "No config API for path: /config/" + parts.get(1));
}
}
}
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java
b/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java
index 5978cb8e0f2..cd89b1d3e9e 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java
@@ -67,7 +67,28 @@ public class GetConfig extends JerseyResource implements
ConfigApi.Get {
"codecFactory",
"query",
"searchComponent",
- "queryResponseWriter");
+ "queryResponseWriter",
+ "luceneMatchVersion",
+ "initParams",
+ "listener",
+ "requestDispatcher",
+ "jmx",
+ "queryParser",
+ "valueSourceParser",
+ "transformer",
+ "updateProcessor",
+ "updateRequestProcessorChain",
+ "queryConverter",
+ "cache",
+ "circuitBreaker",
+ "expressible",
+ "schemaFactory",
+ "restManager",
+ "statsCache",
+ "recoveryStrategy",
+ "indexReaderFactory",
+ "indexConfigdeletionPolicy",
+ "updateHandlerupdateLog");
public static final String REQUEST_HANDLERS = "requestHandlers";
@@ -77,7 +98,15 @@ public class GetConfig extends JerseyResource implements
ConfigApi.Get {
private static final Map<String, String> EXTERNAL_TO_INTERNAL_COMPONENT_NAME
=
Map.of(
"searchComponents", "searchComponent",
- "queryResponseWriters", "queryResponseWriter");
+ "queryResponseWriters", "queryResponseWriter",
+ "listeners", "listener",
+ "queryParsers", "queryParser",
+ "valueSourceParsers", "valueSourceParser",
+ "transformers", "transformer",
+ "updateProcessors", "updateProcessor",
+ "queryConverters", "queryConverter",
+ "expressibles", "expressible",
+ "caches", "cache");
// Same, but for the full /config dump's top-level keys, which also renames
requestHandler.
// Derived from the map above so the two can't drift apart.
@@ -239,10 +268,16 @@ public class GetConfig extends JerseyResource implements
ConfigApi.Get {
String componentName,
boolean meta,
SolrQueryRequest req) {
- Map pluginNameVsPluginInfo = (Map) configMap.get(component);
- if (pluginNameVsPluginInfo == null) {
+ Object sectionValue = configMap.get(component);
+ if (sectionValue == null) {
return;
}
+ if (!(sectionValue instanceof Map) && !(sectionValue instanceof
MapWriter)) {
+ throw new SolrException(
+ SolrException.ErrorCode.NOT_FOUND,
+ component + " does not support filtering by componentName");
+ }
+ Map pluginNameVsPluginInfo = (Map) sectionValue;
Object o =
pluginNameVsPluginInfo instanceof MapWriter
@@ -335,7 +370,7 @@ public class GetConfig extends JerseyResource implements
ConfigApi.Get {
return reqHandlersMap;
}
- @SuppressWarnings({"unchecked", "rawtypes"})
+ @SuppressWarnings("unchecked")
private Map<String, Object> expandUseParams(SolrQueryRequest req, Object
plugin) {
Map<String, Object> pluginInfo;
if (plugin instanceof Map) {
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfigAPI.java
b/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfigAPI.java
index 6cd43791100..110974eec68 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfigAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfigAPI.java
@@ -28,9 +28,8 @@ import org.apache.solr.response.SolrQueryResponse;
/**
* V2 APIs for retrieving some or all configuration relevant to a particular
collection (or core).
*
- * <p>This class covers legacy AnnotatedApi paths under GET /config: /params,
/params{paramset}, and
- * remaining {@code /config/{component}} sections not yet migrated to {@link
- * org.apache.solr.client.api.endpoint.ConfigApi.Get}
+ * <p>This class only covers legacy AnnotatedApi paths under GET /config:
/params and
+ * /params{paramset}
*/
public class GetConfigAPI {
private final SolrConfigHandler configHandler;
@@ -54,37 +53,4 @@ public class GetConfigAPI {
public void getSingleParamSet(SolrQueryRequest req, SolrQueryResponse rsp) {
configHandler.handleRequest(req, rsp);
}
-
- // GET /config/... sections still via SolrConfigHandler.
- // Every name SolrConfig.writeMap can emit.
- // Delete a path from this list when that section is migrated to Jersey
- @EndPoint(
- path = {
- "/config/luceneMatchVersion",
- "/config/initParams",
- "/config/listener",
- "/config/requestDispatcher",
- "/config/jmx",
- "/config/queryParser",
- "/config/valueSourceParser",
- "/config/transformer",
- "/config/updateProcessor",
- "/config/updateRequestProcessorChain",
- "/config/queryConverter",
- "/config/cache",
- "/config/circuitBreaker",
- "/config/expressible",
- "/config/schemaFactory",
- "/config/restManager",
- "/config/statsCache",
- "/config/recoveryStrategy",
- "/config/indexReaderFactory",
- "/config/indexConfigdeletionPolicy",
- "/config/updateHandlerupdateLog"
- },
- method = GET,
- permission = CONFIG_READ_PERM)
- public void getComponentConfig(SolrQueryRequest req, SolrQueryResponse rsp) {
- configHandler.handleRequest(req, rsp);
- }
}
diff --git
a/solr/core/src/test/org/apache/solr/handler/admin/V2ConfigAPIMappingTest.java
b/solr/core/src/test/org/apache/solr/handler/admin/V2ConfigAPIMappingTest.java
index e7912afa8fa..a671cfce9f2 100644
---
a/solr/core/src/test/org/apache/solr/handler/admin/V2ConfigAPIMappingTest.java
+++
b/solr/core/src/test/org/apache/solr/handler/admin/V2ConfigAPIMappingTest.java
@@ -44,18 +44,6 @@ public class V2ConfigAPIMappingTest extends
V2ApiMappingTest<SolrConfigHandler>
apiBag.registerObject(new ModifyParamSetAPI(getRequestHandler()));
}
- // GET /v2/collectionName/config/<component> is a pure pass-through to the
underlying request
- // handler. Just check
- // the API lookup works for a handful of the valid config "components".
- @Test
- public void testGetSingleComponentConfig() {
- assertAnnotatedApiExistsFor("GET", "/config/jmx");
- assertAnnotatedApiExistsFor("GET", "/config/requestDispatcher");
- assertAnnotatedApiExistsFor("GET", "/config/luceneMatchVersion");
- assertAnnotatedApiExistsFor("GET", "/config/indexConfigdeletionPolicy");
- assertAnnotatedApiExistsFor("GET", "/config/updateHandlerupdateLog");
- }
-
@Test
public void testGetParamsetsConfig() {
assertAnnotatedApiExistsFor("GET", "/config/params");
diff --git
a/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java
b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java
index c9c836405f8..059f452f0b2 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java
@@ -19,6 +19,7 @@ package org.apache.solr.handler.admin.api;
import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP;
import static org.apache.solr.handler.admin.api.GetConfig.REQUEST_HANDLERS;
+import java.util.List;
import java.util.Map;
import org.apache.solr.SolrTestCase;
import org.apache.solr.client.api.model.IndexType;
@@ -242,6 +243,37 @@ public class GetConfigTest extends SolrTestCase {
assertTrue(querySection.containsKey("filterCache"));
}
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testGetConfigComponentSectionShapes() throws Exception {
+ var queryResponse =
+ new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME,
"luceneMatchVersion")
+ .process(solrTestRule.getSolrClient(CORE_NAME));
+ assertNull(queryResponse.error);
+ assertTrue(queryResponse.config.get("luceneMatchVersion") instanceof
String);
+
+ queryResponse =
+ new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME,
"initParams")
+ .process(solrTestRule.getSolrClient(CORE_NAME));
+ assertNull(queryResponse.error);
+ assertTrue(queryResponse.config.get("initParams") instanceof List);
+
+ queryResponse =
+ new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME,
"requestDispatcher")
+ .process(solrTestRule.getSolrClient(CORE_NAME));
+ assertNull(queryResponse.error);
+ assertTrue(queryResponse.config.get("requestDispatcher") instanceof Map);
+
+ queryResponse =
+ new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME,
"updateProcessors")
+ .process(solrTestRule.getSolrClient(CORE_NAME));
+ assertNull(queryResponse.error);
+ Map<String, Object> querySection =
+ (Map<String, Object>) queryResponse.config.get("updateProcessors");
+ assertNotNull(querySection);
+ assertTrue(querySection.containsKey("uuid"));
+ }
+
@Test
public void testGetConfigComponentByUnknownNameReturnsNotFound() {
var request =
@@ -255,14 +287,42 @@ public class GetConfigTest extends SolrTestCase {
}
@Test
- public void testGetConfigComponentRejectsOldSingularSearchComponentPath() {
- // v2 intentionally renamed this one section to the plural
"searchComponents" in the URL;
+ public void testGetConfigComponentByNameOnListSectionReturnsNotFound() {
+ // List-shaped sections are excluded from the by-name @Path; Jersey should
return 404.
+ for (String component :
+ List.of("listeners", "initParams", "updateRequestProcessorChain",
"circuitBreaker")) {
+ var request =
+ new ConfigApi.GetConfigComponentByName(IndexType.CORE, CORE_NAME,
component, "random");
+ final RemoteSolrException ex =
+ expectThrows(
+ RemoteSolrException.class,
+ () -> request.process(solrTestRule.getSolrClient(CORE_NAME)));
+ assertEquals(404, ex.code());
+ }
+ }
+
+ @Test
+ public void testGetConfigComponentRejectsOldSingularPaths() {
+ // v2-only plurals;
// the old singular form (still used by v1) shouldn't match the v2 route
at all.
- var request = new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME,
"searchComponent");
- final RemoteSolrException ex =
- expectThrows(
- RemoteSolrException.class,
- () -> request.process(solrTestRule.getSolrClient(CORE_NAME)));
- assertEquals(404, ex.code());
+ for (String component :
+ List.of(
+ "searchComponent",
+ "queryResponseWriter",
+ "listener",
+ "queryParser",
+ "valueSourceParser",
+ "transformer",
+ "updateProcessor",
+ "queryConverter",
+ "expressible",
+ "cache")) {
+ var request = new ConfigApi.GetConfigComponent(IndexType.CORE,
CORE_NAME, component);
+ final RemoteSolrException ex =
+ expectThrows(
+ RemoteSolrException.class,
+ () -> request.process(solrTestRule.getSolrClient(CORE_NAME)));
+ assertEquals(404, ex.code());
+ }
}
}
diff --git
a/solr/solr-ref-guide/modules/configuration-guide/pages/config-api.adoc
b/solr/solr-ref-guide/modules/configuration-guide/pages/config-api.adoc
index cbf7836a4f2..42e1f77c794 100644
--- a/solr/solr-ref-guide/modules/configuration-guide/pages/config-api.adoc
+++ b/solr/solr-ref-guide/modules/configuration-guide/pages/config-api.adoc
@@ -124,8 +124,12 @@ The output will be details of each request handler defined
in `solrconfig.xml`,
To see the configuration for implicit request handlers, add
`expandParams=true` to the request.
See the documentation for implicit request handlers linked above for examples
using this command.
-The available top-level sections that can be added as path parameters are:
`query`, `requestHandler`, `searchComponent`, `updateHandler`,
`queryResponseWriter`, `initParams`, `znodeVersion`, `listener`,
`directoryFactory`, `indexConfig`, and `codecFactory`.
-In the V2 API, `requestHandler`, `searchComponent` and `queryResponseWriter`
are exposed under the pluralized names `requestHandlers`, `searchComponents`
and `queryResponseWriters` instead (including the response JSON key).
+The available top-level sections that can be added as path parameters include:
`query`, `requestHandler`, `searchComponent`, `updateHandler`,
`queryResponseWriter`, `initParams`, `znodeVersion`, `listener`,
`directoryFactory`, `indexConfig`, and `codecFactory`.
+(Other `solrconfig.xml` sections emitted by Solr may also be fetched the same
way)
+
+In the V2 API, several multi-valued sections use pluralized path names (and
the same plural as the response JSON key).
+
+V1 paths keep the singular / internal names unchanged.
To further restrict the request to a single component within a top-level
section, use the `componentName` request parameter.
@@ -166,9 +170,10 @@ The output of this command will look similar to(V1 nests
under `requestHandler`;
}
----
-The ability to restrict to objects within a top-level section is limited to
request handlers (`requestHandler`), search components (`searchComponent`), and
response writers (`queryResponseWriter`).
+By-name lookup applies to sections that are maps of named plugins, such as
request handlers, search components, response writers etc.
+List-shaped sections like initParams, listeners, updateRequestProcessorChain
etc cannot be filtered by name.
-For search components and response writers, the V2 API uses a path segment
instead of the `componentName` request parameter, under the pluralized section
names.
+For those name-keyed sections other than the request handler, the V2 API uses
a path segment instead of the `componentName` request parameter, under the
pluralized section names.
For example, to return configuration for the `elevator` search component:
[tabs#getnamedcomponent]
@@ -191,7 +196,7 @@
http://localhost:8983/api/collections/techproducts/config/searchComponents/eleva
====
======
-The same applies to response writers.
+The same pattern applies to other named-key sections.
For example, to return configuration for the `xslt` response writer:
[tabs#getnamedwriter]
@@ -202,6 +207,7 @@ V1 API::
[source,bash]
----
http://localhost:8983/solr/techproducts/config/queryResponseWriter?componentName=xslt
+http://localhost:8983/solr/techproducts/config/updateProcessor?componentName=uuid
----
====
V2 API::
@@ -210,11 +216,12 @@ V2 API::
[source,bash]
----
http://localhost:8983/api/collections/techproducts/config/queryResponseWriters/xslt
+http://localhost:8983/api/collections/techproducts/config/updateProcessors/uuid
----
====
======
-Request handlers are unaffected and continue to use `componentName` in both
APIs, as shown above.
+Request handlers are a special case and continue to use `componentName` in
both APIs, as shown above.
== Commands to Modify the Config