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 714d6ceff85 SOLR-16396 | Migrate GET config sections query, 
searchComponent & queryResponseWriter to JAX-RS (#5125)
714d6ceff85 is described below

commit 714d6ceff852e6dc743ef51ae094e4eaf8f3b3f9
Author: Jalaz Kumar <[email protected]>
AuthorDate: Thu Oct 15 23:03:39 2026 +0530

    SOLR-16396 | Migrate GET config sections query, searchComponent & 
queryResponseWriter to JAX-RS (#5125)
    
    Co-authored-by: Eric Pugh <[email protected]>
---
 changelog/unreleased/SOLR-16396-config-query.yml   |  11 ++
 .../apache/solr/client/api/endpoint/ConfigApi.java |  27 ++++-
 .../org/apache/solr/handler/SolrConfigHandler.java |  60 ++---------
 .../apache/solr/handler/admin/api/GetConfig.java   | 119 ++++++++++++++++++++-
 .../solr/handler/admin/api/GetConfigAPI.java       |   3 -
 .../solr/handler/admin/V2ConfigAPIMappingTest.java |   2 +-
 .../handler/admin/api/GetConfigSolrCloudTest.java  |   5 +-
 .../solr/handler/admin/api/GetConfigTest.java      |  82 +++++++++++++-
 .../configuration-guide/pages/config-api.adoc      |  49 +++++++++
 9 files changed, 293 insertions(+), 65 deletions(-)

diff --git a/changelog/unreleased/SOLR-16396-config-query.yml 
b/changelog/unreleased/SOLR-16396-config-query.yml
new file mode 100644
index 00000000000..b51ffafa9a2
--- /dev/null
+++ b/changelog/unreleased/SOLR-16396-config-query.yml
@@ -0,0 +1,11 @@
+title:
+  Migrate GET config sections query, searchComponent & queryResponseWriter to 
JAX-RS
+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 baaf4a9f785..49a4b018cc9 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,9 +70,12 @@ public interface ConfigApi {
             @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"
+    // and "queryResponseWriters" here are the external, pluralized names for 
the internal
+    // "searchComponent"/"queryResponseWriter" sections; see 
GetConfig#toExternalComponentName)
     @GET
-    
@Path("/{component:updateHandler|directoryFactory|indexConfig|codecFactory}")
+    @Path(
+        
"/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriters}")
     @StoreApiParameters
     @Operation(
         summary = "Fetch a config section",
@@ -80,5 +83,25 @@ public interface ConfigApi {
     ConfigInfoResponse getConfigComponent(
         @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)
+    @GET
+    @Path(
+        
"/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriters}/{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,
+        @PathParam("componentName") @Parameter(description = "Name of the 
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 8ea4dd3ca40..08614a0a4bc 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.request.CollectionRequiringSolrRequest;
 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.CommandOperation;
 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.GetConfigAPI;
 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,50 +222,22 @@ public class SolrConfigHandler extends RequestHandlerBase
                         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)));
+                resp,
+                componentName == null
+                    ? getConfig.getConfigComponent(parts.get(1))
+                    : getConfig.getConfigComponentByName(
+                        parts.get(1), componentName, 
req.getParams().getBool("meta", false)));
           } else {
             Map<String, Object> m = getConfigDetails(parts.get(1), req);
             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) {
-              @SuppressWarnings({"rawtypes"})
-              Map pluginNameVsPluginInfo = (Map) val.get(parts.get(1));
-              if (pluginNameVsPluginInfo != null) {
-                Object o =
-                    pluginNameVsPluginInfo instanceof MapWriter
-                        ? pluginNameVsPluginInfo
-                        : pluginNameVsPluginInfo.get(componentName);
-                Map<String, Object> pluginInfo =
-                    o instanceof MapWriter
-                        ? new SimpleOrderedMap<>((MapWriter) o)
-                        : (Map<String, Object>) 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<PackageListeners.Listener> listeners =
-                      req.getCore().getPackageListeners().getListeners();
-                  for (PackageListeners.Listener listener : listeners) {
-                    Map<String, PackageAPI.PkgVersion> infos = 
listener.packageDetails();
-                    if (infos == null || infos.isEmpty()) continue;
-                    infos.forEach(
-                        (s, mapWriter) -> {
-                          Map<String, Object> 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);
           }
@@ -277,16 +245,6 @@ public class SolrConfigHandler extends RequestHandlerBase
       }
     }
 
-    @SuppressWarnings({"unchecked"})
-    private Map<String, Object> getComponentInfo(
-        Map<String, Object> pluginInfo, String componentName) {
-      if (pluginInfo.containsKey("class")) {
-        return pluginInfo;
-      }
-      return (Map<String, Object>)
-          pluginInfo.computeIfAbsent(componentName, k -> new 
LinkedHashMap<>());
-    }
-
     private Map<String, Object> 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 806cd1b3971..1bc7db362bd 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,12 @@ import org.apache.solr.client.api.endpoint.ConfigApi;
 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.SolrException;
 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,16 +46,47 @@ import org.apache.solr.handler.RequestHandlerBase;
 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;
 
 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_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<String> MIGRATED_CONFIG_COMPONENTS =
-      Set.of("updateHandler", "directoryFactory", "indexConfig", 
"codecFactory");
+      Set.of(
+          "updateHandler",
+          "directoryFactory",
+          "indexConfig",
+          "codecFactory",
+          "query",
+          "searchComponent",
+          "queryResponseWriter");
+
+  // v2-only renames: external (URL/response-key) component name -> internal 
SolrConfig section
+  // name. v1's GET /config/{component} keeps using the internal names 
unchanged.
+  private static final Map<String, String> EXTERNAL_TO_INTERNAL_COMPONENT_NAME 
=
+      Map.of(
+          "searchComponents", "searchComponent",
+          "queryResponseWriters", "queryResponseWriter");
+
+  private static String toInternalComponentName(String externalName) {
+    return EXTERNAL_TO_INTERNAL_COMPONENT_NAME.getOrDefault(externalName, 
externalName);
+  }
+
+  public static String toExternalComponentName(String internalName) {
+    for (Map.Entry<String, String> e : 
EXTERNAL_TO_INTERNAL_COMPONENT_NAME.entrySet()) {
+      if (e.getValue().equals(internalName)) {
+        return e.getKey();
+      }
+    }
+    return internalName;
+  }
 
   private final SolrQueryRequest solrQueryRequest;
 
@@ -97,14 +131,91 @@ public class GetConfig extends JerseyResource implements 
ConfigApi.Get {
 
   @Override
   @PermissionName(CONFIG_READ_PERM)
-  public ConfigInfoResponse getConfigComponent(String componentName) {
+  public ConfigInfoResponse getConfigComponent(String component) {
+    final var response = instantiateJerseyResponse(ConfigInfoResponse.class);
+    Map<String, Object> section = new LinkedHashMap<>();
+    section.put(component, 
solrConfigAsMap().get(toInternalComponentName(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<String, Object> section = new LinkedHashMap<>();
-    section.put(componentName, solrConfigAsMap().get(componentName));
+    section.put(component, 
solrConfigAsMap().get(toInternalComponentName(component)));
+    filterByComponentName(section, component, componentName, meta, 
solrQueryRequest);
     response.config = jsonSafeCopy(section);
     return response;
   }
 
+  /**
+   * 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<String, Object> section,
+      String component,
+      String componentName,
+      boolean meta,
+      SolrQueryRequest req) {
+    Map pluginNameVsPluginInfo = (Map) section.get(component);
+    if (pluginNameVsPluginInfo == null) {
+      return;
+    }
+
+    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<String, Object> pluginInfo =
+        o instanceof MapWriter ? new SimpleOrderedMap<>((MapWriter) o) : 
(Map<String, Object>) o;
+    section.put(
+        component,
+        pluginNameVsPluginInfo instanceof MapWriter
+            ? pluginInfo
+            : Map.of(componentName, pluginInfo));
+
+    if (meta) {
+      List<PackageListeners.Listener> listeners =
+          req.getCore().getPackageListeners().getListeners();
+      for (PackageListeners.Listener listener : listeners) {
+        Map<String, PackageAPI.PkgVersion> infos = listener.packageDetails();
+        if (infos == null || infos.isEmpty()) continue;
+        infos.forEach(
+            (s, mapWriter) -> {
+              Map<String, Object> componentInfo = getComponentInfo(pluginInfo, 
componentName);
+              if (s.equals(componentInfo.get("class"))) {
+                (componentInfo).put("_packageinfo_", mapWriter);
+              }
+            });
+      }
+    }
+  }
+
+  @SuppressWarnings({"unchecked"})
+  private static Map<String, Object> getComponentInfo(
+      Map<String, Object> pluginInfo, String componentName) {
+    if (pluginInfo.containsKey("class")) {
+      return pluginInfo;
+    }
+    return (Map<String, Object>)
+        pluginInfo.computeIfAbsent(componentName, k -> new LinkedHashMap<>());
+  }
+
   private Map<String, Object> 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 1e67cb09c50..808bdf545f4 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 class GetConfigAPI {
   @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 252946a5971..6e0a2aa6e27 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 class V2ConfigAPIMappingTest extends 
V2ApiMappingTest<SolrConfigHandler>
   // 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/GetConfigSolrCloudTest.java
 
b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java
index 7b34a37bbb1..19b29380afd 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 class GetConfigSolrCloudTest extends 
SolrCloudTestCase {
   @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());
     }
   }
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 b0fbbfa8035..504fe7ecb5e 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,8 +18,10 @@ package org.apache.solr.handler.admin.api;
 
 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.RemoteSolrException;
 import org.apache.solr.client.solrj.request.ConfigApi;
 import org.apache.solr.util.ExternalPaths;
 import org.apache.solr.util.SolrJettyTestRule;
@@ -84,14 +86,90 @@ public class GetConfigTest extends SolrTestCase {
   @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());
     }
   }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void testGetConfigComponentFilteredByNameFromCore() throws Exception {
+    var request =
+        new ConfigApi.GetConfigComponentByName(
+            IndexType.CORE, CORE_NAME, "searchComponents", "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<String, Object> section = (Map<String, Object>) 
response.config.get("searchComponents");
+    assertNotNull(section);
+    assertTrue(section.containsKey("spellcheck"));
+
+    Map<String, Object> spellcheck = (Map<String, Object>) 
section.get("spellcheck");
+    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, "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<String, Object> section = (Map<String, Object>) 
response.config.get("searchComponents");
+    Map<String, Object> spellcheck = (Map<String, Object>) 
section.get("spellcheck");
+    assertFalse(spellcheck.containsKey("_packageinfo_"));
+  }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void testGetConfigComponentFilteredByNameForOtherMigratedComponents() 
throws Exception {
+    // filterCache is one of the few <query> 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<String, Object> querySection = (Map<String, Object>) 
queryResponse.config.get("query");
+    assertNotNull(querySection);
+    assertTrue(querySection.containsKey("filterCache"));
+  }
+
+  @Test
+  public void testGetConfigComponentByUnknownNameReturnsNotFound() {
+    var request =
+        new ConfigApi.GetConfigComponentByName(
+            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,
+            () -> 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 f01f882a0d0..38981e7caf6 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`.

Reply via email to