From 7a77da0b11bd4eab359ee5ed0b05fea684661d0d Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Tue, 13 Oct 2026 23:06:05 +0530 Subject: [PATCH 01/11] cl --- changelog/unreleased/SOLR-16396-config-query.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 changelog/unreleased/SOLR-16396-config-query.yml diff --git a/changelog/unreleased/SOLR-16396-config-query.yml b/changelog/unreleased/SOLR-16396-config-query.yml new file mode 100644 index 000000000000..c5db3384d1a4 --- /dev/null +++ b/changelog/unreleased/SOLR-16396-config-query.yml @@ -0,0 +1,9 @@ +title: + Migrate GET config sections query, searchComponent to JAX-RS +type: changed +authors: + - name: Jalaz Kumar + nick: jaykay12 +links: + - name: SOLR-16396 + url: https://issues.apache.org/jira/browse/SOLR-16396 From 0db25b240cdfcb95e11932e6b1e41a10cb447251 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Wed, 14 Oct 2026 01:16:35 +0530 Subject: [PATCH 02/11] changes --- .../solr/client/api/endpoint/ConfigApi.java | 18 +++++ .../solr/handler/SolrConfigHandler.java | 8 +++ .../solr/handler/admin/api/GetConfig.java | 66 +++++++++++++++++++ .../solr/handler/admin/api/GetConfigAPI.java | 3 - .../handler/admin/V2ConfigAPIMappingTest.java | 2 +- .../solr/handler/admin/api/GetConfigTest.java | 14 ++++ 6 files changed, 107 insertions(+), 4 deletions(-) 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 baaf4a9f785f..e9916b5ef2f6 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 @@ -80,5 +80,23 @@ ConfigZnodeVersionResponse getZnodeVersion( ConfigInfoResponse getConfigComponent( @PathParam("component") @Parameter(description = "Name of the config component") String component); + + // TODO: Keep this regex in sync with GetConfig.RICH_CONFIG_COMPONENTS + @GET + @Path("/{component:query|searchComponent|queryResponseWriter}") + @StoreApiParameters + @Operation( + summary = "Fetch a config section (with optional component filter)", + tags = {"config"}) + ConfigInfoResponse getConfigSection( + @PathParam("component") @Parameter(description = "Name of the config section") + String component, + @QueryParam("componentName") + @Parameter(description = "Restrict to a named entry within the section") + String componentName, + @QueryParam("meta") + @DefaultValue("false") + @Parameter(description = "Include package metadata (_packageinfo_)") + boolean meta); } } 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 8ea4dd3ca404..c37d62c1342c 100644 --- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java @@ -228,6 +228,14 @@ private void handleGET() { } else if (GetConfig.MIGRATED_CONFIG_COMPONENTS.contains(parts.get(1))) { V2ApiUtils.squashIntoSolrResponseWithoutHeader( resp, new GetConfig(req).getConfigComponent(parts.get(1))); + } else if (GetConfig.RICH_CONFIG_COMPONENTS.contains(parts.get(1))) { + V2ApiUtils.squashIntoSolrResponseWithoutHeader( + resp, + new GetConfig(req) + .getConfigSection( + parts.get(1), + req.getParams().get("componentName"), + req.getParams().getBool("meta", false))); } else { Map m = getConfigDetails(parts.get(1), req); Map val = new LinkedHashMap<>(); 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 806cd1b39718..3458cb9408da 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 @@ -33,9 +33,11 @@ import org.apache.solr.client.api.model.ConfigInfoResponse; import org.apache.solr.client.api.model.ConfigOverlayResponse; import org.apache.solr.client.api.model.ConfigZnodeVersionResponse; +import org.apache.solr.common.MapWriter; import org.apache.solr.common.params.MapSolrParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.NamedList; +import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.common.util.StrUtils; import org.apache.solr.common.util.Utils; import org.apache.solr.core.PluginInfo; @@ -43,6 +45,8 @@ import org.apache.solr.handler.SolrConfigHandler; import org.apache.solr.jersey.NullKeyTolerantMap; import org.apache.solr.jersey.PermissionName; +import org.apache.solr.pkg.PackageAPI; +import org.apache.solr.pkg.PackageListeners; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.util.SolrPluginUtils; @@ -54,6 +58,10 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get { public static final Set MIGRATED_CONFIG_COMPONENTS = Set.of("updateHandler", "directoryFactory", "indexConfig", "codecFactory"); + // TODO: Keep this in sync with @Path regex on ConfigApi.Get#getConfigSection. + public static final Set RICH_CONFIG_COMPONENTS = + Set.of("query", "searchComponent", "queryResponseWriter"); + private final SolrQueryRequest solrQueryRequest; @Inject @@ -105,6 +113,64 @@ public ConfigInfoResponse getConfigComponent(String componentName) { return response; } + @Override + @PermissionName(CONFIG_READ_PERM) + @SuppressWarnings({"unchecked", "rawtypes"}) + public ConfigInfoResponse getConfigSection(String component, String componentName, boolean meta) { + final var response = instantiateJerseyResponse(ConfigInfoResponse.class); + Map section = new LinkedHashMap<>(); + section.put(component, solrConfigAsMap().get(component)); + + if (componentName != null) { + @SuppressWarnings({"rawtypes"}) + Map pluginNameVsPluginInfo = (Map) section.get(component); + if (pluginNameVsPluginInfo != null) { + Object o = + pluginNameVsPluginInfo instanceof MapWriter + ? pluginNameVsPluginInfo + : pluginNameVsPluginInfo.get(componentName); + Map pluginInfo = + o instanceof MapWriter + ? new SimpleOrderedMap<>((MapWriter) o) + : (Map) o; + section.put( + component, + pluginNameVsPluginInfo instanceof MapWriter + ? pluginInfo + : Map.of(componentName, pluginInfo)); + + if (meta) { + List listeners = + solrQueryRequest.getCore().getPackageListeners().getListeners(); + for (PackageListeners.Listener listener : listeners) { + Map infos = listener.packageDetails(); + if (infos == null || infos.isEmpty()) continue; + infos.forEach( + (s, mapWriter) -> { + Map componentInfo = getComponentInfo(pluginInfo, componentName); + if (s.equals(componentInfo.get("class"))) { + (componentInfo).put("_packageinfo_", mapWriter); + } + }); + } + } + } + } + + response.config = jsonSafeCopy(section); + return response; + } + + @SuppressWarnings({"unchecked"}) + private static Map getComponentInfo( + Map pluginInfo, String componentName) { + if (pluginInfo.containsKey("class")) { + return pluginInfo; + } + return (Map) + pluginInfo.computeIfAbsent(componentName, k -> new LinkedHashMap<>()); + } + private Map solrConfigAsMap() { return Utils.convertToMap(solrQueryRequest.getCore().getSolrConfig(), new LinkedHashMap<>()); } 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 1e67cb09c503..808bdf545f42 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 @@ -61,10 +61,7 @@ public void getSingleParamSet(SolrQueryRequest req, SolrQueryResponse rsp) { @EndPoint( path = { "/config/luceneMatchVersion", - "/config/query", "/config/requestHandler", - "/config/searchComponent", - "/config/queryResponseWriter", "/config/initParams", "/config/listener", "/config/requestDispatcher", 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 252946a5971f..6e0a2aa6e27a 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 @@ -49,7 +49,7 @@ public void populateApiBag() { // the API lookup works for a handful of the valid config "components". @Test public void testGetSingleComponentConfig() { - assertAnnotatedApiExistsFor("GET", "/config/query"); + assertAnnotatedApiExistsFor("GET", "/config/requestHandler"); assertAnnotatedApiExistsFor("GET", "/config/jmx"); assertAnnotatedApiExistsFor("GET", "/config/requestDispatcher"); assertAnnotatedApiExistsFor("GET", "/config/luceneMatchVersion"); 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 b0fbbfa80351..d2c6d2076602 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 @@ -94,4 +94,18 @@ public void testGetConfigComponentFromCore() throws Exception { assertEquals(component, 1, response.config.size()); } } + + @Test + public void testGetConfigSectionFromCore() throws Exception { + for (String section : GetConfig.RICH_CONFIG_COMPONENTS) { + var request = new ConfigApi.GetConfigSection(IndexType.CORE, CORE_NAME, section); + var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); + + assertNotNull(section, response); + assertNull(section, response.error); + assertNotNull(section, response.config); + assertTrue(section, response.config.containsKey(section)); + assertEquals(section, 1, response.config.size()); + } + } } From 79e159d54353931ed8908a08b9f967c995b140f1 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Wed, 14 Oct 2026 03:14:53 +0530 Subject: [PATCH 03/11] changes --- .../solr/client/api/endpoint/ConfigApi.java | 16 +++--------- .../solr/handler/SolrConfigHandler.java | 5 +--- .../solr/handler/admin/api/GetConfig.java | 26 +++++++------------ .../solr/handler/admin/api/GetConfigTest.java | 14 ---------- 4 files changed, 14 insertions(+), 47 deletions(-) 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 e9916b5ef2f6..d583dea93de8 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 @@ -72,24 +72,14 @@ ConfigZnodeVersionResponse getZnodeVersion( // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS @GET - @Path("/{component:updateHandler|directoryFactory|indexConfig|codecFactory}") + @Path( + "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponent|queryResponseWriter}") @StoreApiParameters @Operation( - summary = "Fetch a config section", + summary = "Fetch a config section (with optional component filter)", tags = {"config"}) ConfigInfoResponse getConfigComponent( @PathParam("component") @Parameter(description = "Name of the config component") - String component); - - // TODO: Keep this regex in sync with GetConfig.RICH_CONFIG_COMPONENTS - @GET - @Path("/{component:query|searchComponent|queryResponseWriter}") - @StoreApiParameters - @Operation( - summary = "Fetch a config section (with optional component filter)", - tags = {"config"}) - ConfigInfoResponse getConfigSection( - @PathParam("component") @Parameter(description = "Name of the config section") String component, @QueryParam("componentName") @Parameter(description = "Restrict to a named entry within the 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 c37d62c1342c..9dbf9ed67ad2 100644 --- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java @@ -226,13 +226,10 @@ private void handleGET() { req.getParams().getInt(ConfigOverlay.NAME, -1), req.getParams().getInt(RequestParams.NAME, -1))); } else if (GetConfig.MIGRATED_CONFIG_COMPONENTS.contains(parts.get(1))) { - V2ApiUtils.squashIntoSolrResponseWithoutHeader( - resp, new GetConfig(req).getConfigComponent(parts.get(1))); - } else if (GetConfig.RICH_CONFIG_COMPONENTS.contains(parts.get(1))) { V2ApiUtils.squashIntoSolrResponseWithoutHeader( resp, new GetConfig(req) - .getConfigSection( + .getConfigComponent( parts.get(1), req.getParams().get("componentName"), req.getParams().getBool("meta", false))); 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 3458cb9408da..1ceb378fd8c8 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 @@ -56,11 +56,14 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get { // TODO: Keep this in sync with @Path regex on ConfigApi.Get#getConfigComponent. // TODO: Remove this set when remaining GET /config/{component} sections also migrate. public static final Set MIGRATED_CONFIG_COMPONENTS = - Set.of("updateHandler", "directoryFactory", "indexConfig", "codecFactory"); - - // TODO: Keep this in sync with @Path regex on ConfigApi.Get#getConfigSection. - public static final Set RICH_CONFIG_COMPONENTS = - Set.of("query", "searchComponent", "queryResponseWriter"); + Set.of( + "updateHandler", + "directoryFactory", + "indexConfig", + "codecFactory", + "query", + "searchComponent", + "queryResponseWriter"); private final SolrQueryRequest solrQueryRequest; @@ -103,20 +106,11 @@ public ConfigZnodeVersionResponse getZnodeVersion( return response; } - @Override - @PermissionName(CONFIG_READ_PERM) - public ConfigInfoResponse getConfigComponent(String componentName) { - final var response = instantiateJerseyResponse(ConfigInfoResponse.class); - Map section = new LinkedHashMap<>(); - section.put(componentName, solrConfigAsMap().get(componentName)); - response.config = jsonSafeCopy(section); - return response; - } - @Override @PermissionName(CONFIG_READ_PERM) @SuppressWarnings({"unchecked", "rawtypes"}) - public ConfigInfoResponse getConfigSection(String component, String componentName, boolean meta) { + public ConfigInfoResponse getConfigComponent( + String component, String componentName, boolean meta) { final var response = instantiateJerseyResponse(ConfigInfoResponse.class); Map section = new LinkedHashMap<>(); section.put(component, solrConfigAsMap().get(component)); 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 d2c6d2076602..b0fbbfa80351 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 @@ -94,18 +94,4 @@ public void testGetConfigComponentFromCore() throws Exception { assertEquals(component, 1, response.config.size()); } } - - @Test - public void testGetConfigSectionFromCore() throws Exception { - for (String section : GetConfig.RICH_CONFIG_COMPONENTS) { - var request = new ConfigApi.GetConfigSection(IndexType.CORE, CORE_NAME, section); - var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); - - assertNotNull(section, response); - assertNull(section, response.error); - assertNotNull(section, response.config); - assertTrue(section, response.config.containsKey(section)); - assertEquals(section, 1, response.config.size()); - } - } } From da938cddbaeee431b8b6bc74e675f12db1a3cb5d Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Wed, 14 Oct 2026 03:17:26 +0530 Subject: [PATCH 04/11] cl --- changelog/unreleased/SOLR-16396-config-query.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/unreleased/SOLR-16396-config-query.yml b/changelog/unreleased/SOLR-16396-config-query.yml index c5db3384d1a4..55521c3e9c8b 100644 --- a/changelog/unreleased/SOLR-16396-config-query.yml +++ b/changelog/unreleased/SOLR-16396-config-query.yml @@ -1,5 +1,5 @@ title: - Migrate GET config sections query, searchComponent to JAX-RS + Migrate GET config sections query, searchComponent & queryResponseWriter to JAX-RS type: changed authors: - name: Jalaz Kumar From 32ab27a37c09f2f8e6fa853bb626e96443e7c0b5 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Wed, 14 Oct 2026 03:43:19 +0530 Subject: [PATCH 05/11] deeper tests --- .../solr/handler/admin/api/GetConfigTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) 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 b0fbbfa80351..c573a50f6a54 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 @@ -18,6 +18,7 @@ import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; +import java.util.Map; import org.apache.solr.SolrTestCase; import org.apache.solr.client.api.model.IndexType; import org.apache.solr.client.solrj.request.ConfigApi; @@ -94,4 +95,26 @@ public void testGetConfigComponentFromCore() throws Exception { assertEquals(component, 1, response.config.size()); } } + + @Test + @SuppressWarnings("unchecked") + public void testGetConfigComponentFilteredByNameFromCore() throws Exception { + var request = new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME, "searchComponent"); + request.setComponentName("spellcheck"); + request.setMeta(true); + var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); + + assertNotNull(response); + assertNull(response.error); + assertNotNull(response.config); + assertEquals(1, response.config.size()); + + Map section = (Map) response.config.get("searchComponent"); + assertNotNull(section); + assertTrue(section.containsKey("spellcheck")); + + Map spellcheck = (Map) section.get("spellcheck"); + assertNotNull(spellcheck); + assertEquals("solr.SpellCheckComponent", spellcheck.get("class")); + } } From f95dbdd149329effc798f7aa9ba6dbe94ddd4a77 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Thu, 15 Oct 2026 13:28:24 +0100 Subject: [PATCH 06/11] After reviewing a bit against our v2 apis doc, making some changes. Bit one is hte query param to path param Switches GET /config/{component}?componentName= to GET /config/{component}/{componentName}, matching other v2 API conventions. Also fixes an NPE-on-unknown-componentName bug (now returns 404), and deduplicates the filtering logic that was copy-pasted between the new v2 code and the legacy v1 handler. Adds test coverage for all three. --- .../solr/client/api/endpoint/ConfigApi.java | 17 ++- .../solr/handler/SolrConfigHandler.java | 63 ++--------- .../solr/handler/admin/api/GetConfig.java | 105 +++++++++++------- .../solr/handler/admin/api/GetConfigTest.java | 46 +++++++- 4 files changed, 133 insertions(+), 98 deletions(-) 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 d583dea93de8..bd97b8685d09 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 @@ -76,13 +76,24 @@ ConfigZnodeVersionResponse getZnodeVersion( "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponent|queryResponseWriter}") @StoreApiParameters @Operation( - summary = "Fetch a config section (with optional component filter)", + summary = "Fetch a config section", tags = {"config"}) ConfigInfoResponse getConfigComponent( + @PathParam("component") @Parameter(description = "Name of the config component") + String component); + + // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS + @GET + @Path( + "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponent|queryResponseWriter}/{componentName}") + @StoreApiParameters + @Operation( + summary = "Fetch a single named entry within a config section", + tags = {"config"}) + ConfigInfoResponse getConfigComponentByName( @PathParam("component") @Parameter(description = "Name of the config component") String component, - @QueryParam("componentName") - @Parameter(description = "Restrict to a named entry within the section") + @PathParam("componentName") @Parameter(description = "Name of the entry within the section") String componentName, @QueryParam("meta") @DefaultValue("false") 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 9dbf9ed67ad2..08614a0a4bc6 100644 --- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java @@ -62,7 +62,6 @@ import org.apache.solr.client.solrj.response.SimpleSolrResponse; import org.apache.solr.cloud.ZkController; import org.apache.solr.cloud.ZkSolrResourceLoader; -import org.apache.solr.common.MapWriter; import org.apache.solr.common.SolrErrorWrappingException; import org.apache.solr.common.SolrException; import org.apache.solr.common.cloud.ClusterState; @@ -77,7 +76,6 @@ import org.apache.solr.common.util.EnvUtils; import org.apache.solr.common.util.ExecutorUtil; import org.apache.solr.common.util.NamedList; -import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.common.util.SolrNamedThreadFactory; import org.apache.solr.common.util.StrUtils; import org.apache.solr.common.util.Utils; @@ -92,8 +90,6 @@ import org.apache.solr.handler.admin.api.ModifyConfigComponentAPI; import org.apache.solr.handler.admin.api.ModifyParamSetAPI; import org.apache.solr.handler.api.V2ApiUtils; -import org.apache.solr.pkg.PackageAPI; -import org.apache.solr.pkg.PackageListeners; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.response.SolrQueryResponse; @@ -226,55 +222,22 @@ private void handleGET() { req.getParams().getInt(ConfigOverlay.NAME, -1), req.getParams().getInt(RequestParams.NAME, -1))); } else if (GetConfig.MIGRATED_CONFIG_COMPONENTS.contains(parts.get(1))) { + String componentName = req.getParams().get("componentName"); + GetConfig getConfig = new GetConfig(req); V2ApiUtils.squashIntoSolrResponseWithoutHeader( resp, - new GetConfig(req) - .getConfigComponent( - parts.get(1), - req.getParams().get("componentName"), - req.getParams().getBool("meta", false))); + componentName == null + ? getConfig.getConfigComponent(parts.get(1)) + : getConfig.getConfigComponentByName( + parts.get(1), componentName, req.getParams().getBool("meta", false))); } else { Map m = getConfigDetails(parts.get(1), req); Map val = new LinkedHashMap<>(); val.put(parts.get(1), m.get(parts.get(1))); String componentName = req.getParams().get("componentName"); if (componentName != null) { - @SuppressWarnings({"rawtypes"}) - Map pluginNameVsPluginInfo = (Map) val.get(parts.get(1)); - if (pluginNameVsPluginInfo != null) { - Object o = - pluginNameVsPluginInfo instanceof MapWriter - ? pluginNameVsPluginInfo - : pluginNameVsPluginInfo.get(componentName); - Map pluginInfo = - o instanceof MapWriter - ? new SimpleOrderedMap<>((MapWriter) o) - : (Map) o; - val.put( - parts.get(1), - pluginNameVsPluginInfo instanceof MapWriter - ? pluginInfo - : Map.of(componentName, pluginInfo)); - if (req.getParams().getBool("meta", false)) { - // meta=true is asking for the package info of the plugin - // We go through all the listeners and see if there is one registered for this - // plugin - List listeners = - req.getCore().getPackageListeners().getListeners(); - for (PackageListeners.Listener listener : listeners) { - Map infos = listener.packageDetails(); - if (infos == null || infos.isEmpty()) continue; - infos.forEach( - (s, mapWriter) -> { - Map componentInfo = - getComponentInfo(pluginInfo, componentName); - if (s.equals(componentInfo.get("class"))) { - (componentInfo).put("_packageinfo_", mapWriter); - } - }); - } - } - } + GetConfig.filterByComponentName( + val, parts.get(1), componentName, req.getParams().getBool("meta", false), req); } resp.add("config", val); } @@ -282,16 +245,6 @@ private void handleGET() { } } - @SuppressWarnings({"unchecked"}) - private Map getComponentInfo( - Map pluginInfo, String componentName) { - if (pluginInfo.containsKey("class")) { - return pluginInfo; - } - return (Map) - pluginInfo.computeIfAbsent(componentName, k -> new LinkedHashMap<>()); - } - private Map getConfigDetails(String componentType, SolrQueryRequest req) { String componentName = componentType == null ? null : req.getParams().get("componentName"); boolean showParams = req.getParams().getBool("expandParams", false); 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 1ceb378fd8c8..892e621fbb43 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 @@ -34,6 +34,7 @@ import org.apache.solr.client.api.model.ConfigOverlayResponse; import org.apache.solr.client.api.model.ConfigZnodeVersionResponse; import org.apache.solr.common.MapWriter; +import org.apache.solr.common.SolrException; import org.apache.solr.common.params.MapSolrParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.NamedList; @@ -108,51 +109,79 @@ public ConfigZnodeVersionResponse getZnodeVersion( @Override @PermissionName(CONFIG_READ_PERM) - @SuppressWarnings({"unchecked", "rawtypes"}) - public ConfigInfoResponse getConfigComponent( + public ConfigInfoResponse getConfigComponent(String component) { + final var response = instantiateJerseyResponse(ConfigInfoResponse.class); + Map section = new LinkedHashMap<>(); + section.put(component, solrConfigAsMap().get(component)); + response.config = jsonSafeCopy(section); + return response; + } + + @Override + @PermissionName(CONFIG_READ_PERM) + public ConfigInfoResponse getConfigComponentByName( String component, String componentName, boolean meta) { final var response = instantiateJerseyResponse(ConfigInfoResponse.class); Map section = new LinkedHashMap<>(); section.put(component, solrConfigAsMap().get(component)); + filterByComponentName(section, component, componentName, meta, solrQueryRequest); + response.config = jsonSafeCopy(section); + return response; + } - if (componentName != null) { - @SuppressWarnings({"rawtypes"}) - Map pluginNameVsPluginInfo = (Map) section.get(component); - if (pluginNameVsPluginInfo != null) { - Object o = - pluginNameVsPluginInfo instanceof MapWriter - ? pluginNameVsPluginInfo - : pluginNameVsPluginInfo.get(componentName); - Map pluginInfo = - o instanceof MapWriter - ? new SimpleOrderedMap<>((MapWriter) o) - : (Map) o; - section.put( - component, - pluginNameVsPluginInfo instanceof MapWriter - ? pluginInfo - : Map.of(componentName, pluginInfo)); - - if (meta) { - List listeners = - solrQueryRequest.getCore().getPackageListeners().getListeners(); - for (PackageListeners.Listener listener : listeners) { - Map infos = listener.packageDetails(); - if (infos == null || infos.isEmpty()) continue; - infos.forEach( - (s, mapWriter) -> { - Map componentInfo = getComponentInfo(pluginInfo, componentName); - if (s.equals(componentInfo.get("class"))) { - (componentInfo).put("_packageinfo_", mapWriter); - } - }); - } - } - } + /** + * Shared by the v2 {@code getConfigComponentByName} API and {@link + * org.apache.solr.handler.SolrConfigHandler}'s legacy v1 handling for components not yet migrated + * to v2, so the two don't carry separate copies of this logic. + * + * @throws SolrException NOT_FOUND if {@code componentName} does not exist within {@code + * component} + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void filterByComponentName( + Map section, + String component, + String componentName, + boolean meta, + SolrQueryRequest req) { + Map pluginNameVsPluginInfo = (Map) section.get(component); + if (pluginNameVsPluginInfo == null) { + return; } - response.config = jsonSafeCopy(section); - return response; + Object o = + pluginNameVsPluginInfo instanceof MapWriter + ? pluginNameVsPluginInfo + : pluginNameVsPluginInfo.get(componentName); + if (o == null) { + throw new SolrException( + SolrException.ErrorCode.NOT_FOUND, + componentName + " not found in config section " + component); + } + + Map pluginInfo = + o instanceof MapWriter ? new SimpleOrderedMap<>((MapWriter) o) : (Map) o; + section.put( + component, + pluginNameVsPluginInfo instanceof MapWriter + ? pluginInfo + : Map.of(componentName, pluginInfo)); + + if (meta) { + List listeners = + req.getCore().getPackageListeners().getListeners(); + for (PackageListeners.Listener listener : listeners) { + Map infos = listener.packageDetails(); + if (infos == null || infos.isEmpty()) continue; + infos.forEach( + (s, mapWriter) -> { + Map componentInfo = getComponentInfo(pluginInfo, componentName); + if (s.equals(componentInfo.get("class"))) { + (componentInfo).put("_packageinfo_", mapWriter); + } + }); + } + } } @SuppressWarnings({"unchecked"}) 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 c573a50f6a54..a86e8981cab8 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 @@ -21,6 +21,7 @@ import java.util.Map; import org.apache.solr.SolrTestCase; import org.apache.solr.client.api.model.IndexType; +import org.apache.solr.client.solrj.RemoteSolrException; import org.apache.solr.client.solrj.request.ConfigApi; import org.apache.solr.util.ExternalPaths; import org.apache.solr.util.SolrJettyTestRule; @@ -99,8 +100,9 @@ public void testGetConfigComponentFromCore() throws Exception { @Test @SuppressWarnings("unchecked") public void testGetConfigComponentFilteredByNameFromCore() throws Exception { - var request = new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME, "searchComponent"); - request.setComponentName("spellcheck"); + var request = + new ConfigApi.GetConfigComponentByName( + IndexType.CORE, CORE_NAME, "searchComponent", "spellcheck"); request.setMeta(true); var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); @@ -117,4 +119,44 @@ public void testGetConfigComponentFilteredByNameFromCore() throws Exception { assertNotNull(spellcheck); assertEquals("solr.SpellCheckComponent", spellcheck.get("class")); } + + @Test + @SuppressWarnings("unchecked") + public void testGetConfigComponentFilteredByNameOmitsPackageInfoByDefault() throws Exception { + var request = + new ConfigApi.GetConfigComponentByName( + IndexType.CORE, CORE_NAME, "searchComponent", "spellcheck"); + // meta is left unset, so it defaults to false and no package lookup should happen. + var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); + + Map section = (Map) response.config.get("searchComponent"); + Map spellcheck = (Map) section.get("spellcheck"); + assertFalse(spellcheck.containsKey("_packageinfo_")); + } + + @Test + @SuppressWarnings("unchecked") + public void testGetConfigComponentFilteredByNameForOtherMigratedComponents() throws Exception { + // filterCache is one of the few entries the default configset doesn't comment out. + var queryRequest = + new ConfigApi.GetConfigComponentByName(IndexType.CORE, CORE_NAME, "query", "filterCache"); + var queryResponse = queryRequest.process(solrTestRule.getSolrClient(CORE_NAME)); + assertNotNull(queryResponse); + assertNull(queryResponse.error); + Map querySection = (Map) queryResponse.config.get("query"); + assertNotNull(querySection); + assertTrue(querySection.containsKey("filterCache")); + } + + @Test + public void testGetConfigComponentByUnknownNameReturnsNotFound() { + var request = + new ConfigApi.GetConfigComponentByName( + IndexType.CORE, CORE_NAME, "searchComponent", "doesNotExist"); + final RemoteSolrException ex = + expectThrows( + RemoteSolrException.class, + () -> request.process(solrTestRule.getSolrClient(CORE_NAME))); + assertEquals(404, ex.code()); + } } From bbf00ba56e7cc5473593a28faf17b31027c8ec12 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Thu, 15 Oct 2026 15:04:50 +0100 Subject: [PATCH 07/11] In our v2 spreadsheet we proposed plural searchComponents. --- .../solr/client/api/endpoint/ConfigApi.java | 12 ++++++--- .../solr/handler/admin/api/GetConfig.java | 26 +++++++++++++++--- .../solr/handler/admin/api/GetConfigTest.java | 27 ++++++++++++++----- 3 files changed, 51 insertions(+), 14 deletions(-) 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 bd97b8685d09..1bc155f48d03 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 @@ -70,10 +70,12 @@ ConfigZnodeVersionResponse getZnodeVersion( @Parameter(description = "Expected request-params znode version") int expectedParamsVersion); - // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS + // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS ("searchComponents" + // here is the external name for the internal "searchComponent" section; see + // GetConfig#toExternalComponentName) @GET @Path( - "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponent|queryResponseWriter}") + "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriter}") @StoreApiParameters @Operation( summary = "Fetch a config section", @@ -82,10 +84,12 @@ ConfigInfoResponse getConfigComponent( @PathParam("component") @Parameter(description = "Name of the config component") String component); - // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS + // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS ("searchComponents" + // here is the external name for the internal "searchComponent" section; see + // GetConfig#toExternalComponentName) @GET @Path( - "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponent|queryResponseWriter}/{componentName}") + "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriter}/{componentName}") @StoreApiParameters @Operation( summary = "Fetch a single named entry within a config section", 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 892e621fbb43..7cafff661bda 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 @@ -54,7 +54,9 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get { - // TODO: Keep this in sync with @Path regex on ConfigApi.Get#getConfigComponent. + // TODO: Keep this in sync with @Path regex on ConfigApi.Get#getConfigComponent. These are the + // internal SolrConfig section names (also used by the legacy v1 GET /config/{component} path); + // see EXTERNAL_COMPONENT_NAME for the v2-only "searchComponent" -> "searchComponents" rename. // TODO: Remove this set when remaining GET /config/{component} sections also migrate. public static final Set MIGRATED_CONFIG_COMPONENTS = Set.of( @@ -66,6 +68,24 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get { "searchComponent", "queryResponseWriter"); + // v2-only renames: external (URL/response-key) component name -> internal SolrConfig section + // name. v1's GET /config/searchComponent keeps using the internal name unchanged. + private static final Map EXTERNAL_TO_INTERNAL_COMPONENT_NAME = + Map.of("searchComponents", "searchComponent"); + + private static String toInternalComponentName(String externalName) { + return EXTERNAL_TO_INTERNAL_COMPONENT_NAME.getOrDefault(externalName, externalName); + } + + public static String toExternalComponentName(String internalName) { + for (Map.Entry e : EXTERNAL_TO_INTERNAL_COMPONENT_NAME.entrySet()) { + if (e.getValue().equals(internalName)) { + return e.getKey(); + } + } + return internalName; + } + private final SolrQueryRequest solrQueryRequest; @Inject @@ -112,7 +132,7 @@ public ConfigZnodeVersionResponse getZnodeVersion( public ConfigInfoResponse getConfigComponent(String component) { final var response = instantiateJerseyResponse(ConfigInfoResponse.class); Map section = new LinkedHashMap<>(); - section.put(component, solrConfigAsMap().get(component)); + section.put(component, solrConfigAsMap().get(toInternalComponentName(component))); response.config = jsonSafeCopy(section); return response; } @@ -123,7 +143,7 @@ public ConfigInfoResponse getConfigComponentByName( String component, String componentName, boolean meta) { final var response = instantiateJerseyResponse(ConfigInfoResponse.class); Map section = new LinkedHashMap<>(); - section.put(component, solrConfigAsMap().get(component)); + section.put(component, solrConfigAsMap().get(toInternalComponentName(component))); filterByComponentName(section, component, componentName, meta, solrQueryRequest); response.config = jsonSafeCopy(section); return response; 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 a86e8981cab8..504fe7ecb5e7 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 @@ -86,13 +86,14 @@ public void testGetZnodeVersionFromCore() throws Exception { @Test public void testGetConfigComponentFromCore() throws Exception { for (String component : GetConfig.MIGRATED_CONFIG_COMPONENTS) { - var request = new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME, component); + String pathComponent = GetConfig.toExternalComponentName(component); + var request = new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME, pathComponent); var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); assertNotNull(component, response); assertNull(component, response.error); assertNotNull(component, response.config); - assertTrue(component, response.config.containsKey(component)); + assertTrue(component, response.config.containsKey(pathComponent)); assertEquals(component, 1, response.config.size()); } } @@ -102,7 +103,7 @@ public void testGetConfigComponentFromCore() throws Exception { public void testGetConfigComponentFilteredByNameFromCore() throws Exception { var request = new ConfigApi.GetConfigComponentByName( - IndexType.CORE, CORE_NAME, "searchComponent", "spellcheck"); + IndexType.CORE, CORE_NAME, "searchComponents", "spellcheck"); request.setMeta(true); var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); @@ -111,7 +112,7 @@ public void testGetConfigComponentFilteredByNameFromCore() throws Exception { assertNotNull(response.config); assertEquals(1, response.config.size()); - Map section = (Map) response.config.get("searchComponent"); + Map section = (Map) response.config.get("searchComponents"); assertNotNull(section); assertTrue(section.containsKey("spellcheck")); @@ -125,11 +126,11 @@ public void testGetConfigComponentFilteredByNameFromCore() throws Exception { public void testGetConfigComponentFilteredByNameOmitsPackageInfoByDefault() throws Exception { var request = new ConfigApi.GetConfigComponentByName( - IndexType.CORE, CORE_NAME, "searchComponent", "spellcheck"); + IndexType.CORE, CORE_NAME, "searchComponents", "spellcheck"); // meta is left unset, so it defaults to false and no package lookup should happen. var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); - Map section = (Map) response.config.get("searchComponent"); + Map section = (Map) response.config.get("searchComponents"); Map spellcheck = (Map) section.get("spellcheck"); assertFalse(spellcheck.containsKey("_packageinfo_")); } @@ -152,7 +153,19 @@ public void testGetConfigComponentFilteredByNameForOtherMigratedComponents() thr public void testGetConfigComponentByUnknownNameReturnsNotFound() { var request = new ConfigApi.GetConfigComponentByName( - IndexType.CORE, CORE_NAME, "searchComponent", "doesNotExist"); + IndexType.CORE, CORE_NAME, "searchComponents", "doesNotExist"); + final RemoteSolrException ex = + expectThrows( + RemoteSolrException.class, + () -> request.process(solrTestRule.getSolrClient(CORE_NAME))); + assertEquals(404, ex.code()); + } + + @Test + public void testGetConfigComponentRejectsOldSingularSearchComponentPath() { + // v2 intentionally renamed this one section to the plural "searchComponents" in the URL; + // 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, From d3fb66bd4ec477a342f713f294210c12563b0747 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Thu, 15 Oct 2026 15:13:12 +0100 Subject: [PATCH 08/11] now migrate queryResponseWriters --- .../apache/solr/client/api/endpoint/ConfigApi.java | 12 ++++++------ .../org/apache/solr/handler/admin/api/GetConfig.java | 8 +++++--- 2 files changed, 11 insertions(+), 9 deletions(-) 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 1bc155f48d03..49a4b018cc99 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 @@ -71,11 +71,11 @@ ConfigZnodeVersionResponse getZnodeVersion( int expectedParamsVersion); // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS ("searchComponents" - // here is the external name for the internal "searchComponent" section; see - // GetConfig#toExternalComponentName) + // and "queryResponseWriters" here are the external, pluralized names for the internal + // "searchComponent"/"queryResponseWriter" sections; see GetConfig#toExternalComponentName) @GET @Path( - "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriter}") + "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriters}") @StoreApiParameters @Operation( summary = "Fetch a config section", @@ -85,11 +85,11 @@ ConfigInfoResponse getConfigComponent( String component); // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS ("searchComponents" - // here is the external name for the internal "searchComponent" section; see - // GetConfig#toExternalComponentName) + // and "queryResponseWriters" here are the external, pluralized names for the internal + // "searchComponent"/"queryResponseWriter" sections; see GetConfig#toExternalComponentName) @GET @Path( - "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriter}/{componentName}") + "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriters}/{componentName}") @StoreApiParameters @Operation( summary = "Fetch a single named entry within a config section", 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 7cafff661bda..1bc7db362bd9 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 @@ -56,7 +56,7 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get { // TODO: Keep this in sync with @Path regex on ConfigApi.Get#getConfigComponent. These are the // internal SolrConfig section names (also used by the legacy v1 GET /config/{component} path); - // see EXTERNAL_COMPONENT_NAME for the v2-only "searchComponent" -> "searchComponents" rename. + // see EXTERNAL_TO_INTERNAL_COMPONENT_NAME for the v2-only pluralized renames. // TODO: Remove this set when remaining GET /config/{component} sections also migrate. public static final Set MIGRATED_CONFIG_COMPONENTS = Set.of( @@ -69,9 +69,11 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get { "queryResponseWriter"); // v2-only renames: external (URL/response-key) component name -> internal SolrConfig section - // name. v1's GET /config/searchComponent keeps using the internal name unchanged. + // name. v1's GET /config/{component} keeps using the internal names unchanged. private static final Map EXTERNAL_TO_INTERNAL_COMPONENT_NAME = - Map.of("searchComponents", "searchComponent"); + Map.of( + "searchComponents", "searchComponent", + "queryResponseWriters", "queryResponseWriter"); private static String toInternalComponentName(String externalName) { return EXTERNAL_TO_INTERNAL_COMPONENT_NAME.getOrDefault(externalName, externalName); From 42023aa31b8542f18b8880df7159fecfd7c0c696 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Thu, 15 Oct 2026 15:37:06 +0100 Subject: [PATCH 09/11] add expliti exmaples of v2 --- .../configuration-guide/pages/config-api.adoc | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) 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 f01f882a0d03..38981e7caf69 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 @@ -119,6 +119,7 @@ To see the configuration for implicit request handlers, add `expandParams=true` 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, `searchComponent` and `queryResponseWriter` are exposed under the pluralized names `searchComponents` and `queryResponseWriters` instead. To further restrict the request to a single component within a top-level section, use the `componentName` request parameter. @@ -161,6 +162,54 @@ The output of this command will look similar to: The ability to restrict to objects within a top-level section is limited to request handlers (`requestHandler`), search components (`searchComponent`), and response writers (`queryResponseWriter`). +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 example, to return configuration for the `elevator` search component: + +[tabs#getnamedcomponent] +====== +V1 API:: ++ +==== +[source,bash] +---- +http://localhost:8983/solr/techproducts/config/searchComponent?componentName=elevator +---- +==== +V2 API:: ++ +==== +[source,bash] +---- +http://localhost:8983/api/collections/techproducts/config/searchComponents/elevator +---- +==== +====== + +The same applies to response writers. +For example, to return configuration for the `xslt` response writer: + +[tabs#getnamedwriter] +====== +V1 API:: ++ +==== +[source,bash] +---- +http://localhost:8983/solr/techproducts/config/queryResponseWriter?componentName=xslt +---- +==== +V2 API:: ++ +==== +[source,bash] +---- +http://localhost:8983/api/collections/techproducts/config/queryResponseWriters/xslt +---- +==== +====== + +Request handlers are unaffected and continue to use `componentName` in both APIs, as shown above. + == Commands to Modify the Config This API uses specific commands with POST requests to tell Solr what property or type of property to add to or modify in `configoverlay.json`. From 7988dc6b4478a8a5e037ba4d07e7ca5bcf81070e Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Thu, 15 Oct 2026 21:22:07 +0530 Subject: [PATCH 10/11] cl --- changelog/unreleased/SOLR-16396-config-query.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog/unreleased/SOLR-16396-config-query.yml b/changelog/unreleased/SOLR-16396-config-query.yml index 55521c3e9c8b..b51ffafa9a2e 100644 --- a/changelog/unreleased/SOLR-16396-config-query.yml +++ b/changelog/unreleased/SOLR-16396-config-query.yml @@ -4,6 +4,8 @@ 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 From 72c2cbe6a2653ec3a46559ef8ad2ac10947069ac Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Thu, 15 Oct 2026 22:42:09 +0530 Subject: [PATCH 11/11] fixed test --- .../solr/handler/admin/api/GetConfigSolrCloudTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java index 7b34a37bbb12..19b29380afd8 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java @@ -80,14 +80,15 @@ public void testGetZnodeVersionFromCollection() throws Exception { @Test public void testGetConfigComponentFromCollection() throws Exception { for (String component : GetConfig.MIGRATED_CONFIG_COMPONENTS) { + String pathComponent = GetConfig.toExternalComponentName(component); var request = - new ConfigApi.GetConfigComponent(IndexType.COLLECTION, COLLECTION_NAME, component); + new ConfigApi.GetConfigComponent(IndexType.COLLECTION, COLLECTION_NAME, pathComponent); var response = request.process(cluster.getSolrClient()); assertNotNull(component, response); assertNull(component, response.error); assertNotNull(component, response.config); - assertTrue(component, response.config.containsKey(component)); + assertTrue(component, response.config.containsKey(pathComponent)); assertEquals(component, 1, response.config.size()); } }