From 7a77da0b11bd4eab359ee5ed0b05fea684661d0d Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Tue, 13 Oct 2026 23:06:05 +0530 Subject: [PATCH 01/30] 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 00000000000..c5db3384d1a --- /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/30] 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 baaf4a9f785..e9916b5ef2f 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 8ea4dd3ca40..c37d62c1342 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 806cd1b3971..3458cb9408d 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 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 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 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 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 b0fbbfa8035..d2c6d207660 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/30] 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 e9916b5ef2f..d583dea93de 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 c37d62c1342..9dbf9ed67ad 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 3458cb9408d..1ceb378fd8c 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 d2c6d207660..b0fbbfa8035 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/30] 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 c5db3384d1a..55521c3e9c8 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/30] 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 b0fbbfa8035..c573a50f6a5 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 49e6f131e2caa64bc1d65b66cca688f2d23f143b Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Thu, 15 Oct 2026 02:39:12 +0530 Subject: [PATCH 06/30] changes --- .../solr/client/api/endpoint/ConfigApi.java | 21 +++++ .../solr/handler/SolrConfigHandler.java | 89 +++---------------- .../solr/handler/admin/api/GetConfig.java | 55 ++++++++---- .../solr/handler/admin/api/GetConfigAPI.java | 1 - .../handler/admin/V2ConfigAPIMappingTest.java | 1 - 5 files changed, 70 insertions(+), 97 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 d583dea93de..6647a0250b3 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,6 +70,27 @@ ConfigZnodeVersionResponse getZnodeVersion( @Parameter(description = "Expected request-params znode version") int expectedParamsVersion); + // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS + @GET + @Path("/requestHandler") + @StoreApiParameters + @Operation( + summary = "Fetch request handlers (including implicit handler)", + tags = {"config"}) + ConfigInfoResponse getRequestHandler( + @QueryParam("expandParams") + @DefaultValue("false") + @Parameter(description = "Expand request handler useParams and effective parameters") + boolean expandParams, + @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); + + // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS @GET @Path( 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 9dbf9ed67ad..6f9f4f1b8eb 100644 --- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java @@ -21,10 +21,7 @@ import static org.apache.solr.core.ConfigOverlay.NOT_EDITABLE; import static org.apache.solr.core.ConfigOverlay.ZNODEVER; import static org.apache.solr.core.ConfigSetProperties.IMMUTABLE_CONFIGSET_ARG; -import static org.apache.solr.core.PluginInfo.APPENDS; import static org.apache.solr.core.PluginInfo.DEFAULTS; -import static org.apache.solr.core.PluginInfo.INVARIANTS; -import static org.apache.solr.core.RequestParams.USEPARAM; import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_CLASS; import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME; import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME_IN_OVERLAY; @@ -70,7 +67,6 @@ import org.apache.solr.common.cloud.Replica; import org.apache.solr.common.cloud.Slice; import org.apache.solr.common.params.CommonParams; -import org.apache.solr.common.params.MapSolrParams; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.CommandOperation; @@ -101,7 +97,6 @@ import org.apache.solr.security.AuthorizationContext; import org.apache.solr.security.PermissionNameProvider; import org.apache.solr.util.RTimer; -import org.apache.solr.util.SolrPluginUtils; import org.apache.solr.util.plugin.SolrCoreAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -225,6 +220,14 @@ private void handleGET() { .getZnodeVersion( req.getParams().getInt(ConfigOverlay.NAME, -1), req.getParams().getInt(RequestParams.NAME, -1))); + } else if (SolrRequestHandler.TYPE.equals(parts.get(1))) { + V2ApiUtils.squashIntoSolrResponseWithoutHeader( + resp, + new GetConfig(req) + .getRequestHandler( + req.getParams().getBool("expandParams", false), + req.getParams().get("componentName"), + req.getParams().getBool("meta", false))); } else if (GetConfig.MIGRATED_CONFIG_COMPONENTS.contains(parts.get(1))) { V2ApiUtils.squashIntoSolrResponseWithoutHeader( resp, @@ -234,7 +237,8 @@ private void handleGET() { req.getParams().get("componentName"), req.getParams().getBool("meta", false))); } else { - Map m = getConfigDetails(parts.get(1), req); + Map m = + Utils.convertToMap(req.getCore().getSolrConfig(), new LinkedHashMap<>()); Map val = new LinkedHashMap<>(); val.put(parts.get(1), m.get(parts.get(1))); String componentName = req.getParams().get("componentName"); @@ -292,79 +296,6 @@ private Map getComponentInfo( 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); - Map map = - Utils.convertToMap(this.req.getCore().getSolrConfig(), new LinkedHashMap<>()); - if (componentType != null && !SolrRequestHandler.TYPE.equals(componentType)) return map; - - @SuppressWarnings({"unchecked"}) - Map reqHandlers = - (Map) - map.computeIfAbsent(SolrRequestHandler.TYPE, k -> new LinkedHashMap<>()); - List plugins = this.req.getCore().getImplicitHandlers(); - for (PluginInfo plugin : plugins) { - if (SolrRequestHandler.TYPE.equals(plugin.type)) { - if (!reqHandlers.containsKey(plugin.name)) { - reqHandlers.put(plugin.name, plugin); - } - } - } - if (!showParams) return map; - for (Map.Entry e : reqHandlers.entrySet()) { - if (componentName == null || e.getKey().equals(componentName)) { - Map m = expandUseParams(req, e.getValue()); - e.setValue(m); - } - } - - return map; - } - - @SuppressWarnings({"unchecked"}) - private Map expandUseParams(SolrQueryRequest req, Object plugin) { - - Map pluginInfo = null; - if (plugin instanceof Map) { - pluginInfo = (Map) plugin; - } else if (plugin instanceof PluginInfo) { - pluginInfo = Utils.convertToMap((PluginInfo) plugin, new LinkedHashMap<>()); - } - String useParams = (String) pluginInfo.get(USEPARAM); - String useParamsInReq = req.getOriginalParams().get(USEPARAM); - if (useParams != null || useParamsInReq != null) { - Map m = new LinkedHashMap<>(); - pluginInfo.put("_useParamsExpanded_", m); - List params = new ArrayList<>(); - if (useParams != null) params.addAll(StrUtils.splitSmart(useParams, ',')); - if (useParamsInReq != null) params.addAll(StrUtils.splitSmart(useParamsInReq, ',')); - for (String param : params) { - RequestParams.ParamSet p = - this.req.getCore().getSolrConfig().getRequestParams().getParams(param); - if (p != null) { - m.put(param, p); - } else { - m.put(param, "[NOT AVAILABLE]"); - } - } - - SolrQueryRequest r = req.subRequest(req.getOriginalParams()); - r.getContext().put(USEPARAM, useParams); - NamedList nl = new PluginInfo(SolrRequestHandler.TYPE, pluginInfo).initArgs; - SolrPluginUtils.setDefaults( - r, - getSolrParamsFromNamedList(nl, DEFAULTS), - getSolrParamsFromNamedList(nl, APPENDS), - getSolrParamsFromNamedList(nl, INVARIANTS)); - // SolrParams.wrapDefaults(maskUseParams, req.getParams()) - - MapSolrParams mask = new MapSolrParams(Map.of("componentName", "", "expandParams", "")); - pluginInfo.put("_effectiveParams_", SolrParams.wrapDefaults(mask, r.getParams())); - } - return pluginInfo; - } - private void handlePOST() throws IOException { List ops = ApiBag.readCommands(req.getContentStreams(), resp.getValues()); if (ops == null) return; 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 1ceb378fd8c..584542b9606 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 @@ -76,7 +76,7 @@ public GetConfig(SolrQueryRequest solrQueryRequest) { @PermissionName(CONFIG_READ_PERM) public ConfigInfoResponse getConfig(boolean expandParams) { final var response = instantiateJerseyResponse(ConfigInfoResponse.class); - response.config = buildConfigMap(solrQueryRequest, expandParams); + response.config = buildConfigMap(expandParams); return response; } @@ -108,15 +108,36 @@ public ConfigZnodeVersionResponse getZnodeVersion( @Override @PermissionName(CONFIG_READ_PERM) - @SuppressWarnings({"unchecked", "rawtypes"}) + public ConfigInfoResponse getRequestHandler( + boolean expandParams, String componentName, boolean meta) { + final var response = instantiateJerseyResponse(ConfigInfoResponse.class); + Map configMap = solrConfigAsMap(); + configMap.put( + SolrRequestHandler.TYPE, + requestHandlersWithImplicits(configMap, expandParams, componentName)); + applyNamedComponentFilter(configMap, SolrRequestHandler.TYPE, componentName, meta); + + response.config = jsonSafeCopy(configMap); + return response; + } + + @Override + @PermissionName(CONFIG_READ_PERM) 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)); + applyNamedComponentFilter(section, component, componentName, meta); + + response.config = jsonSafeCopy(section); + return response; + } + @SuppressWarnings({"unchecked", "rawtypes"}) + private void applyNamedComponentFilter( + Map section, String component, String componentName, boolean meta) { if (componentName != null) { - @SuppressWarnings({"rawtypes"}) Map pluginNameVsPluginInfo = (Map) section.get(component); if (pluginNameVsPluginInfo != null) { Object o = @@ -150,9 +171,6 @@ public ConfigInfoResponse getConfigComponent( } } } - - response.config = jsonSafeCopy(section); - return response; } @SuppressWarnings({"unchecked"}) @@ -176,29 +194,34 @@ private NullKeyTolerantMap jsonSafeCopy(Map map) { return jsonSafeCopy; } - @SuppressWarnings({"unchecked"}) - private Map buildConfigMap( - SolrQueryRequest solrQueryRequest, boolean expandParams) { - + private Map buildConfigMap(boolean expandParams) { Map configMap = solrConfigAsMap(); + requestHandlersWithImplicits(configMap, expandParams, null); + return jsonSafeCopy(configMap); + } - Map reqHandlers = + @SuppressWarnings({"unchecked"}) + private Map requestHandlersWithImplicits(Map configMap, boolean expandParams, String expandOnlyComponentName) { + Map reqHandlersMap = (Map) configMap.computeIfAbsent(SolrRequestHandler.TYPE, k -> new LinkedHashMap<>()); + List plugins = solrQueryRequest.getCore().getImplicitHandlers(); for (PluginInfo plugin : plugins) { - if (SolrRequestHandler.TYPE.equals(plugin.type) && !reqHandlers.containsKey(plugin.name)) { - reqHandlers.put(plugin.name, plugin); + if (SolrRequestHandler.TYPE.equals(plugin.type) && !reqHandlersMap.containsKey(plugin.name)) { + reqHandlersMap.put(plugin.name, plugin); } } if (expandParams) { - for (Map.Entry entry : reqHandlers.entrySet()) { - entry.setValue(expandUseParams(solrQueryRequest, entry.getValue())); + for (Map.Entry entry : reqHandlersMap.entrySet()) { + if (expandOnlyComponentName == null || !expandOnlyComponentName.equals(entry.getKey())) { + entry.setValue(expandUseParams(solrQueryRequest, entry.getValue())); + } } } - return jsonSafeCopy(configMap); + return reqHandlersMap; } @SuppressWarnings({"unchecked", "rawtypes"}) 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 808bdf545f4..6cd43791100 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,7 +61,6 @@ public void getSingleParamSet(SolrQueryRequest req, SolrQueryResponse rsp) { @EndPoint( path = { "/config/luceneMatchVersion", - "/config/requestHandler", "/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 6e0a2aa6e27..e7912afa8fa 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,6 @@ public void populateApiBag() { // the API lookup works for a handful of the valid config "components". @Test public void testGetSingleComponentConfig() { - assertAnnotatedApiExistsFor("GET", "/config/requestHandler"); assertAnnotatedApiExistsFor("GET", "/config/jmx"); assertAnnotatedApiExistsFor("GET", "/config/requestDispatcher"); assertAnnotatedApiExistsFor("GET", "/config/luceneMatchVersion"); From 35f7b3d26e26cb2e552de54abf789e661e54961e Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Thu, 15 Oct 2026 02:44:42 +0530 Subject: [PATCH 07/30] tidy --- .../solr/client/api/endpoint/ConfigApi.java | 18 ++++++++---------- .../solr/handler/admin/api/GetConfig.java | 3 ++- 2 files changed, 10 insertions(+), 11 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 6647a0250b3..2419632f002 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,7 +70,6 @@ ConfigZnodeVersionResponse getZnodeVersion( @Parameter(description = "Expected request-params znode version") int expectedParamsVersion); - // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS @GET @Path("/requestHandler") @StoreApiParameters @@ -79,17 +78,16 @@ ConfigZnodeVersionResponse getZnodeVersion( tags = {"config"}) ConfigInfoResponse getRequestHandler( @QueryParam("expandParams") - @DefaultValue("false") - @Parameter(description = "Expand request handler useParams and effective parameters") - boolean expandParams, + @DefaultValue("false") + @Parameter(description = "Expand request handler useParams and effective parameters") + boolean expandParams, @QueryParam("componentName") - @Parameter(description = "Restrict to a named entry within the section") - String 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); - + @DefaultValue("false") + @Parameter(description = "Include package metadata (_packageinfo_)") + boolean meta); // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS @GET 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 584542b9606..70268cf87c3 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 @@ -201,7 +201,8 @@ private Map buildConfigMap(boolean expandParams) { } @SuppressWarnings({"unchecked"}) - private Map requestHandlersWithImplicits(Map configMap, boolean expandParams, String expandOnlyComponentName) { + private Map requestHandlersWithImplicits( + Map configMap, boolean expandParams, String expandOnlyComponentName) { Map reqHandlersMap = (Map) configMap.computeIfAbsent(SolrRequestHandler.TYPE, k -> new LinkedHashMap<>()); From a05946a80b23330749548ddf7f811fe45bc77686 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 00:36:49 +0530 Subject: [PATCH 08/30] rebased with main --- .../solr/client/api/endpoint/ConfigApi.java | 20 ++++- .../solr/handler/SolrConfigHandler.java | 89 +++---------------- .../solr/handler/admin/api/GetConfig.java | 68 +++++++++----- .../solr/handler/admin/api/GetConfigAPI.java | 1 - .../handler/admin/V2ConfigAPIMappingTest.java | 1 - 5 files changed, 76 insertions(+), 103 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 49a4b018cc9..896142f24aa 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,7 +70,25 @@ ConfigZnodeVersionResponse getZnodeVersion( @Parameter(description = "Expected request-params znode version") int expectedParamsVersion); - // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS ("searchComponents" + @GET + @Path("/requestHandlers") + @StoreApiParameters + @Operation( + summary = "Fetch request handlers (including implicit handler)", + tags = {"config"}) + ConfigInfoResponse getRequestHandler( + @PathParam("componentName") @Parameter(description = "Name of the specific request handler") + String component, + @QueryParam("expandParams") + @DefaultValue("false") + @Parameter(description = "Expand request handler useParams and effective parameters") + boolean expandParams, + @QueryParam("meta") + @DefaultValue("false") + @Parameter(description = "Include package metadata (_packageinfo_)") + boolean meta); + + // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS // and "queryResponseWriters" here are the external, pluralized names for the internal // "searchComponent"/"queryResponseWriter" sections; see GetConfig#toExternalComponentName) @GET 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 08614a0a4bc..5d7432ca34c 100644 --- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java @@ -21,10 +21,7 @@ import static org.apache.solr.core.ConfigOverlay.NOT_EDITABLE; import static org.apache.solr.core.ConfigOverlay.ZNODEVER; import static org.apache.solr.core.ConfigSetProperties.IMMUTABLE_CONFIGSET_ARG; -import static org.apache.solr.core.PluginInfo.APPENDS; import static org.apache.solr.core.PluginInfo.DEFAULTS; -import static org.apache.solr.core.PluginInfo.INVARIANTS; -import static org.apache.solr.core.RequestParams.USEPARAM; import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_CLASS; import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME; import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME_IN_OVERLAY; @@ -69,7 +66,6 @@ import org.apache.solr.common.cloud.Replica; import org.apache.solr.common.cloud.Slice; import org.apache.solr.common.params.CommonParams; -import org.apache.solr.common.params.MapSolrParams; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.CommandOperation; @@ -97,7 +93,6 @@ import org.apache.solr.security.AuthorizationContext; import org.apache.solr.security.PermissionNameProvider; import org.apache.solr.util.RTimer; -import org.apache.solr.util.SolrPluginUtils; import org.apache.solr.util.plugin.SolrCoreAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -221,6 +216,14 @@ private void handleGET() { .getZnodeVersion( req.getParams().getInt(ConfigOverlay.NAME, -1), req.getParams().getInt(RequestParams.NAME, -1))); + } else if (SolrRequestHandler.TYPE.equals(parts.get(1))) { + V2ApiUtils.squashIntoSolrResponseWithoutHeader( + resp, + new GetConfig(req) + .getRequestHandler( + req.getParams().get("componentName"), + req.getParams().getBool("expandParams", false), + req.getParams().getBool("meta", false))); } else if (GetConfig.MIGRATED_CONFIG_COMPONENTS.contains(parts.get(1))) { String componentName = req.getParams().get("componentName"); GetConfig getConfig = new GetConfig(req); @@ -231,7 +234,8 @@ private void handleGET() { : getConfig.getConfigComponentByName( parts.get(1), componentName, req.getParams().getBool("meta", false))); } else { - Map m = getConfigDetails(parts.get(1), req); + Map m = + Utils.convertToMap(req.getCore().getSolrConfig(), new LinkedHashMap<>()); Map val = new LinkedHashMap<>(); val.put(parts.get(1), m.get(parts.get(1))); String componentName = req.getParams().get("componentName"); @@ -245,79 +249,6 @@ private void handleGET() { } } - private Map getConfigDetails(String componentType, SolrQueryRequest req) { - String componentName = componentType == null ? null : req.getParams().get("componentName"); - boolean showParams = req.getParams().getBool("expandParams", false); - Map map = - Utils.convertToMap(this.req.getCore().getSolrConfig(), new LinkedHashMap<>()); - if (componentType != null && !SolrRequestHandler.TYPE.equals(componentType)) return map; - - @SuppressWarnings({"unchecked"}) - Map reqHandlers = - (Map) - map.computeIfAbsent(SolrRequestHandler.TYPE, k -> new LinkedHashMap<>()); - List plugins = this.req.getCore().getImplicitHandlers(); - for (PluginInfo plugin : plugins) { - if (SolrRequestHandler.TYPE.equals(plugin.type)) { - if (!reqHandlers.containsKey(plugin.name)) { - reqHandlers.put(plugin.name, plugin); - } - } - } - if (!showParams) return map; - for (Map.Entry e : reqHandlers.entrySet()) { - if (componentName == null || e.getKey().equals(componentName)) { - Map m = expandUseParams(req, e.getValue()); - e.setValue(m); - } - } - - return map; - } - - @SuppressWarnings({"unchecked"}) - private Map expandUseParams(SolrQueryRequest req, Object plugin) { - - Map pluginInfo = null; - if (plugin instanceof Map) { - pluginInfo = (Map) plugin; - } else if (plugin instanceof PluginInfo) { - pluginInfo = Utils.convertToMap((PluginInfo) plugin, new LinkedHashMap<>()); - } - String useParams = (String) pluginInfo.get(USEPARAM); - String useParamsInReq = req.getOriginalParams().get(USEPARAM); - if (useParams != null || useParamsInReq != null) { - Map m = new LinkedHashMap<>(); - pluginInfo.put("_useParamsExpanded_", m); - List params = new ArrayList<>(); - if (useParams != null) params.addAll(StrUtils.splitSmart(useParams, ',')); - if (useParamsInReq != null) params.addAll(StrUtils.splitSmart(useParamsInReq, ',')); - for (String param : params) { - RequestParams.ParamSet p = - this.req.getCore().getSolrConfig().getRequestParams().getParams(param); - if (p != null) { - m.put(param, p); - } else { - m.put(param, "[NOT AVAILABLE]"); - } - } - - SolrQueryRequest r = req.subRequest(req.getOriginalParams()); - r.getContext().put(USEPARAM, useParams); - NamedList nl = new PluginInfo(SolrRequestHandler.TYPE, pluginInfo).initArgs; - SolrPluginUtils.setDefaults( - r, - getSolrParamsFromNamedList(nl, DEFAULTS), - getSolrParamsFromNamedList(nl, APPENDS), - getSolrParamsFromNamedList(nl, INVARIANTS)); - // SolrParams.wrapDefaults(maskUseParams, req.getParams()) - - MapSolrParams mask = new MapSolrParams(Map.of("componentName", "", "expandParams", "")); - pluginInfo.put("_effectiveParams_", SolrParams.wrapDefaults(mask, r.getParams())); - } - return pluginInfo; - } - private void handlePOST() throws IOException { List ops = ApiBag.readCommands(req.getContentStreams(), resp.getValues()); if (ops == null) return; 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 1bc7db362bd..9bff454fbe1 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 @@ -99,7 +99,7 @@ public GetConfig(SolrQueryRequest solrQueryRequest) { @PermissionName(CONFIG_READ_PERM) public ConfigInfoResponse getConfig(boolean expandParams) { final var response = instantiateJerseyResponse(ConfigInfoResponse.class); - response.config = buildConfigMap(solrQueryRequest, expandParams); + response.config = buildConfigMap(expandParams); return response; } @@ -129,13 +129,32 @@ public ConfigZnodeVersionResponse getZnodeVersion( return response; } + @Override + @PermissionName(CONFIG_READ_PERM) + public ConfigInfoResponse getRequestHandler( + String componentName, boolean expandParams, boolean meta) { + final var response = instantiateJerseyResponse(ConfigInfoResponse.class); + + Map configMap = solrConfigAsMap(); + configMap.put( + SolrRequestHandler.TYPE, + requestHandlersWithImplicits(configMap, expandParams, componentName)); + filterByComponentName( + configMap, SolrRequestHandler.TYPE, componentName, meta, solrQueryRequest); + + response.config = jsonSafeCopy(configMap); + return response; + } + @Override @PermissionName(CONFIG_READ_PERM) public ConfigInfoResponse getConfigComponent(String component) { final var response = instantiateJerseyResponse(ConfigInfoResponse.class); - Map section = new LinkedHashMap<>(); - section.put(component, solrConfigAsMap().get(toInternalComponentName(component))); - response.config = jsonSafeCopy(section); + + Map configMap = new LinkedHashMap<>(); + configMap.put(component, solrConfigAsMap().get(toInternalComponentName(component))); + + response.config = jsonSafeCopy(configMap); return response; } @@ -144,10 +163,11 @@ public ConfigInfoResponse getConfigComponent(String component) { public ConfigInfoResponse getConfigComponentByName( String component, String componentName, boolean meta) { final var response = instantiateJerseyResponse(ConfigInfoResponse.class); - Map section = new LinkedHashMap<>(); - section.put(component, solrConfigAsMap().get(toInternalComponentName(component))); - filterByComponentName(section, component, componentName, meta, solrQueryRequest); - response.config = jsonSafeCopy(section); + + Map configMap = new LinkedHashMap<>(); + configMap.put(component, solrConfigAsMap().get(toInternalComponentName(component))); + filterByComponentName(configMap, component, componentName, meta, solrQueryRequest); + response.config = jsonSafeCopy(configMap); return response; } @@ -161,12 +181,12 @@ public ConfigInfoResponse getConfigComponentByName( */ @SuppressWarnings({"unchecked", "rawtypes"}) public static void filterByComponentName( - Map section, + Map configMap, String component, String componentName, boolean meta, SolrQueryRequest req) { - Map pluginNameVsPluginInfo = (Map) section.get(component); + Map pluginNameVsPluginInfo = (Map) configMap.get(component); if (pluginNameVsPluginInfo == null) { return; } @@ -183,7 +203,7 @@ public static void filterByComponentName( Map pluginInfo = o instanceof MapWriter ? new SimpleOrderedMap<>((MapWriter) o) : (Map) o; - section.put( + configMap.put( component, pluginNameVsPluginInfo instanceof MapWriter ? pluginInfo @@ -227,29 +247,35 @@ private NullKeyTolerantMap jsonSafeCopy(Map map) { return jsonSafeCopy; } - @SuppressWarnings({"unchecked"}) - private Map buildConfigMap( - SolrQueryRequest solrQueryRequest, boolean expandParams) { - + private Map buildConfigMap(boolean expandParams) { Map configMap = solrConfigAsMap(); + requestHandlersWithImplicits(configMap, expandParams, null); + return jsonSafeCopy(configMap); + } - Map reqHandlers = + @SuppressWarnings({"unchecked"}) + private Map requestHandlersWithImplicits( + Map configMap, boolean expandParams, String expandOnlyComponentName) { + Map reqHandlersMap = (Map) configMap.computeIfAbsent(SolrRequestHandler.TYPE, k -> new LinkedHashMap<>()); + List plugins = solrQueryRequest.getCore().getImplicitHandlers(); for (PluginInfo plugin : plugins) { - if (SolrRequestHandler.TYPE.equals(plugin.type) && !reqHandlers.containsKey(plugin.name)) { - reqHandlers.put(plugin.name, plugin); + if (SolrRequestHandler.TYPE.equals(plugin.type) && !reqHandlersMap.containsKey(plugin.name)) { + reqHandlersMap.put(plugin.name, plugin); } } if (expandParams) { - for (Map.Entry entry : reqHandlers.entrySet()) { - entry.setValue(expandUseParams(solrQueryRequest, entry.getValue())); + for (Map.Entry entry : reqHandlersMap.entrySet()) { + if (expandOnlyComponentName == null || !expandOnlyComponentName.equals(entry.getKey())) { + entry.setValue(expandUseParams(solrQueryRequest, entry.getValue())); + } } } - return jsonSafeCopy(configMap); + return reqHandlersMap; } @SuppressWarnings({"unchecked", "rawtypes"}) 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 808bdf545f4..6cd43791100 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,7 +61,6 @@ public void getSingleParamSet(SolrQueryRequest req, SolrQueryResponse rsp) { @EndPoint( path = { "/config/luceneMatchVersion", - "/config/requestHandler", "/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 6e0a2aa6e27..e7912afa8fa 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,6 @@ public void populateApiBag() { // the API lookup works for a handful of the valid config "components". @Test public void testGetSingleComponentConfig() { - assertAnnotatedApiExistsFor("GET", "/config/requestHandler"); assertAnnotatedApiExistsFor("GET", "/config/jmx"); assertAnnotatedApiExistsFor("GET", "/config/requestDispatcher"); assertAnnotatedApiExistsFor("GET", "/config/luceneMatchVersion"); From 1efb6026f4e237217a1d27f2b20b8461f341732e Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 01:34:37 +0530 Subject: [PATCH 09/30] queryparam --- .../java/org/apache/solr/client/api/endpoint/ConfigApi.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 896142f24aa..e134fa25b5e 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 @@ -77,7 +77,8 @@ ConfigZnodeVersionResponse getZnodeVersion( summary = "Fetch request handlers (including implicit handler)", tags = {"config"}) ConfigInfoResponse getRequestHandler( - @PathParam("componentName") @Parameter(description = "Name of the specific request handler") + @QueryParam("componentName") + @Parameter(description = "Name of the specific request handler") String component, @QueryParam("expandParams") @DefaultValue("false") From 5baa8ab3b81d8028402f25926ab7e1bcfc3bc2c0 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 01:37:09 +0530 Subject: [PATCH 10/30] minor --- .../src/java/org/apache/solr/client/api/endpoint/ConfigApi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e134fa25b5e..76787c45cca 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,7 +71,7 @@ ConfigZnodeVersionResponse getZnodeVersion( int expectedParamsVersion); @GET - @Path("/requestHandlers") + @Path("/requestHandler") @StoreApiParameters @Operation( summary = "Fetch request handlers (including implicit handler)", From 6c10a1450e5066a09bcccfdb140b9b7cad167684 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 01:43:38 +0530 Subject: [PATCH 11/30] corrections --- .../org/apache/solr/handler/admin/api/GetConfig.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 9bff454fbe1..560137eaaec 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 @@ -135,12 +135,14 @@ public ConfigInfoResponse getRequestHandler( String componentName, boolean expandParams, boolean meta) { final var response = instantiateJerseyResponse(ConfigInfoResponse.class); - Map configMap = solrConfigAsMap(); + Map configMap = new LinkedHashMap<>(); configMap.put( SolrRequestHandler.TYPE, - requestHandlersWithImplicits(configMap, expandParams, componentName)); - filterByComponentName( - configMap, SolrRequestHandler.TYPE, componentName, meta, solrQueryRequest); + requestHandlersWithImplicits(solrConfigAsMap(), expandParams, componentName)); + if (componentName != null) { + filterByComponentName( + configMap, SolrRequestHandler.TYPE, componentName, meta, solrQueryRequest); + } response.config = jsonSafeCopy(configMap); return response; From b02cf83dfeb48da83aed98fa82c7f9671e543af0 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 01:52:23 +0530 Subject: [PATCH 12/30] minor fix --- .../src/java/org/apache/solr/handler/admin/api/GetConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 560137eaaec..cddbec5cdc5 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 @@ -271,7 +271,7 @@ private Map requestHandlersWithImplicits( if (expandParams) { for (Map.Entry entry : reqHandlersMap.entrySet()) { - if (expandOnlyComponentName == null || !expandOnlyComponentName.equals(entry.getKey())) { + if (expandOnlyComponentName == null || expandOnlyComponentName.equals(entry.getKey())) { entry.setValue(expandUseParams(solrQueryRequest, entry.getValue())); } } From 4e159cda07f8ad7e3895e483a64c10a8196ecb74 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 01:56:14 +0530 Subject: [PATCH 13/30] added cl --- .../unreleased/SOLR-16396-config-requestHandler.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 changelog/unreleased/SOLR-16396-config-requestHandler.yml diff --git a/changelog/unreleased/SOLR-16396-config-requestHandler.yml b/changelog/unreleased/SOLR-16396-config-requestHandler.yml new file mode 100644 index 00000000000..53f2b8f1b79 --- /dev/null +++ b/changelog/unreleased/SOLR-16396-config-requestHandler.yml @@ -0,0 +1,9 @@ +title: > + Migrate GET /config/requestHandler 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 05d0a9bb646f52f1b2b53063670bcdfa28e7d99c Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 02:32:54 +0530 Subject: [PATCH 14/30] added tests --- .../solr/client/api/endpoint/ConfigApi.java | 2 +- .../solr/handler/admin/api/GetConfigTest.java | 85 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) 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 76787c45cca..bb4b6a1cbc7 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 @@ -89,7 +89,7 @@ ConfigInfoResponse getRequestHandler( @Parameter(description = "Include package metadata (_packageinfo_)") boolean meta); - // 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 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 504fe7ecb5e..d0415fc21cf 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 @@ -23,6 +23,7 @@ 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.request.SolrRequestHandler; import org.apache.solr.util.ExternalPaths; import org.apache.solr.util.SolrJettyTestRule; import org.junit.BeforeClass; @@ -83,6 +84,90 @@ public void testGetZnodeVersionFromCore() throws Exception { assertTrue(response.znodeVersion.params >= -1); } + @Test + @SuppressWarnings("unchecked") + public void testGetRequestHandlersFromCore() throws Exception { + var request = new ConfigApi.GetRequestHandler(IndexType.CORE, CORE_NAME); + var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); + + assertNotNull(response); + assertNull(response.error); + assertNotNull(response.config); + assertEquals(1, response.config.size()); + + Map configSection = + (Map) response.config.get(SolrRequestHandler.TYPE); + assertNotNull(configSection); + assertTrue(configSection.containsKey("/select")); + assertTrue(configSection.containsKey("/admin/ping")); + } + + @Test + @SuppressWarnings("unchecked") + public void testGetRequestHandlersFilteredByNameFromCore() throws Exception { + var request = new ConfigApi.GetRequestHandler(IndexType.CORE, CORE_NAME); + request.setComponentName("/select"); + var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); + + assertNotNull(response); + assertNull(response.error); + assertEquals(1, response.config.size()); + + Map configSection = + (Map) response.config.get(SolrRequestHandler.TYPE); + assertNotNull(configSection); + assertTrue(configSection.containsKey("/select")); + + Map select = (Map) configSection.get("/select"); + assertEquals("solr.SearchHandler", select.get("class")); + } + + @Test + @SuppressWarnings("unchecked") + public void testGetRequestHandlersExpandParamsExpandsNamedHandler() throws Exception { + var request = new ConfigApi.GetRequestHandler(IndexType.CORE, CORE_NAME); + request.setComponentName("/admin/ping"); + request.setExpandParams(true); + var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); + + assertNotNull(response); + assertNull(response.error); + assertEquals(1, response.config.size()); + + Map configSection = + (Map) response.config.get(SolrRequestHandler.TYPE); + + Map ping = (Map) configSection.get("/admin/ping"); + assertNotNull(ping.get("_useParamsExpanded_")); + Map expanded = (Map) ping.get("_useParamsExpanded_"); + assertEquals("[NOT AVAILABLE]", expanded.get("_ADMIN_PING")); + } + + @Test + @SuppressWarnings("unchecked") + public void testGetRequestHandlersOmitsExpandedParamsByDefault() throws Exception { + var request = new ConfigApi.GetRequestHandler(IndexType.CORE, CORE_NAME); + request.setComponentName("/admin/ping"); + var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); + + Map configSection = + (Map) response.config.get(SolrRequestHandler.TYPE); + + Map ping = (Map) configSection.get("/admin/ping"); + assertFalse(ping.containsKey("_useParamsExpanded_")); + } + + @Test + public void testGetRequestHandlersByUnknownNameReturnsNotFound() throws Exception { + var request = new ConfigApi.GetRequestHandler(IndexType.CORE, CORE_NAME); + request.setComponentName("/randomHandler"); + final RemoteSolrException exception = + assertThrows( + RemoteSolrException.class, + () -> request.process(solrTestRule.getSolrClient(CORE_NAME))); + assertEquals(404, exception.code()); + } + @Test public void testGetConfigComponentFromCore() throws Exception { for (String component : GetConfig.MIGRATED_CONFIG_COMPONENTS) { From a7618b82549544a4793512c85257c3eff48eb908 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 12:26:09 +0530 Subject: [PATCH 15/30] cl minor --- changelog/unreleased/SOLR-16396-config-requestHandler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/unreleased/SOLR-16396-config-requestHandler.yml b/changelog/unreleased/SOLR-16396-config-requestHandler.yml index 53f2b8f1b79..98857191726 100644 --- a/changelog/unreleased/SOLR-16396-config-requestHandler.yml +++ b/changelog/unreleased/SOLR-16396-config-requestHandler.yml @@ -1,4 +1,4 @@ -title: > +title: Migrate GET /config/requestHandler to JAX-RS type: changed authors: From e6b2f0734e0bc9dc441d907c10cc876bf9058ac3 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 18:17:08 +0530 Subject: [PATCH 16/30] changes --- .../apache/solr/client/api/endpoint/ConfigApi.java | 2 +- .../org/apache/solr/handler/SolrConfigHandler.java | 3 ++- .../apache/solr/handler/admin/api/GetConfig.java | 7 ++++--- .../test/org/apache/solr/cli/PackageToolTest.java | 4 ++-- .../solr/handler/admin/api/GetConfigTest.java | 14 +++++--------- 5 files changed, 14 insertions(+), 16 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 bb4b6a1cbc7..0f3f64868b8 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,7 +71,7 @@ ConfigZnodeVersionResponse getZnodeVersion( int expectedParamsVersion); @GET - @Path("/requestHandler") + @Path("/requestHandlers") @StoreApiParameters @Operation( summary = "Fetch request handlers (including implicit handler)", 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 5d7432ca34c..1aeed97cd59 100644 --- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java @@ -25,6 +25,7 @@ import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_CLASS; import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME; import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME_IN_OVERLAY; +import static org.apache.solr.handler.admin.api.GetConfig.REQUEST_HANDLERS; import static org.apache.solr.schema.FieldType.CLASS_NAME; import java.io.IOException; @@ -216,7 +217,7 @@ private void handleGET() { .getZnodeVersion( req.getParams().getInt(ConfigOverlay.NAME, -1), req.getParams().getInt(RequestParams.NAME, -1))); - } else if (SolrRequestHandler.TYPE.equals(parts.get(1))) { + } else if (REQUEST_HANDLERS.equals(parts.get(1))) { V2ApiUtils.squashIntoSolrResponseWithoutHeader( resp, new GetConfig(req) 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 cddbec5cdc5..c444563c4f6 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 @@ -68,6 +68,8 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get { "searchComponent", "queryResponseWriter"); + public static final String REQUEST_HANDLERS = "requestHandlers"; + // 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 EXTERNAL_TO_INTERNAL_COMPONENT_NAME = @@ -137,11 +139,10 @@ public ConfigInfoResponse getRequestHandler( Map configMap = new LinkedHashMap<>(); configMap.put( - SolrRequestHandler.TYPE, + REQUEST_HANDLERS, requestHandlersWithImplicits(solrConfigAsMap(), expandParams, componentName)); if (componentName != null) { - filterByComponentName( - configMap, SolrRequestHandler.TYPE, componentName, meta, solrQueryRequest); + filterByComponentName(configMap, REQUEST_HANDLERS, componentName, meta, solrQueryRequest); } response.config = jsonSafeCopy(configMap); diff --git a/solr/core/src/test/org/apache/solr/cli/PackageToolTest.java b/solr/core/src/test/org/apache/solr/cli/PackageToolTest.java index a919b30555b..31250974691 100644 --- a/solr/core/src/test/org/apache/solr/cli/PackageToolTest.java +++ b/solr/core/src/test/org/apache/solr/cli/PackageToolTest.java @@ -320,9 +320,9 @@ void assertPackageVersion( testForResponseElement( cluster.getJettySolrRunner(0).getBaseUrl().toString() + "/" + collection, - "/config/requestHandler?componentName=" + component + "&meta=true", + "/config/requestHandlers?componentName=" + component + "&meta=true", credentials, - Arrays.asList("config", "requestHandler", component, "_packageinfo_", "version"), + Arrays.asList("config", "requestHandlers", component, "_packageinfo_", "version"), componentVersion); } 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 d0415fc21cf..ed11ee4c217 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 @@ -17,13 +17,13 @@ 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.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.request.SolrRequestHandler; import org.apache.solr.util.ExternalPaths; import org.apache.solr.util.SolrJettyTestRule; import org.junit.BeforeClass; @@ -95,8 +95,7 @@ public void testGetRequestHandlersFromCore() throws Exception { assertNotNull(response.config); assertEquals(1, response.config.size()); - Map configSection = - (Map) response.config.get(SolrRequestHandler.TYPE); + Map configSection = (Map) response.config.get(REQUEST_HANDLERS); assertNotNull(configSection); assertTrue(configSection.containsKey("/select")); assertTrue(configSection.containsKey("/admin/ping")); @@ -113,8 +112,7 @@ public void testGetRequestHandlersFilteredByNameFromCore() throws Exception { assertNull(response.error); assertEquals(1, response.config.size()); - Map configSection = - (Map) response.config.get(SolrRequestHandler.TYPE); + Map configSection = (Map) response.config.get(REQUEST_HANDLERS); assertNotNull(configSection); assertTrue(configSection.containsKey("/select")); @@ -134,8 +132,7 @@ public void testGetRequestHandlersExpandParamsExpandsNamedHandler() throws Excep assertNull(response.error); assertEquals(1, response.config.size()); - Map configSection = - (Map) response.config.get(SolrRequestHandler.TYPE); + Map configSection = (Map) response.config.get(REQUEST_HANDLERS); Map ping = (Map) configSection.get("/admin/ping"); assertNotNull(ping.get("_useParamsExpanded_")); @@ -150,8 +147,7 @@ public void testGetRequestHandlersOmitsExpandedParamsByDefault() throws Exceptio request.setComponentName("/admin/ping"); var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); - Map configSection = - (Map) response.config.get(SolrRequestHandler.TYPE); + Map configSection = (Map) response.config.get(REQUEST_HANDLERS); Map ping = (Map) configSection.get("/admin/ping"); assertFalse(ping.containsKey("_useParamsExpanded_")); From 061c96605767d56771fc30003a0e487f32285450 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 18:30:18 +0530 Subject: [PATCH 17/30] fix --- .../src/java/org/apache/solr/handler/SolrConfigHandler.java | 4 ++-- .../src/java/org/apache/solr/handler/admin/api/GetConfig.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) 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 1aeed97cd59..2309f409a9c 100644 --- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java @@ -25,7 +25,7 @@ import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_CLASS; import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME; import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME_IN_OVERLAY; -import static org.apache.solr.handler.admin.api.GetConfig.REQUEST_HANDLERS; +import static org.apache.solr.handler.admin.api.GetConfig.REQUEST_HANDLER_APIs; import static org.apache.solr.schema.FieldType.CLASS_NAME; import java.io.IOException; @@ -217,7 +217,7 @@ private void handleGET() { .getZnodeVersion( req.getParams().getInt(ConfigOverlay.NAME, -1), req.getParams().getInt(RequestParams.NAME, -1))); - } else if (REQUEST_HANDLERS.equals(parts.get(1))) { + } else if (REQUEST_HANDLER_APIs.contains(parts.get(1))) { V2ApiUtils.squashIntoSolrResponseWithoutHeader( resp, new GetConfig(req) 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 c444563c4f6..13a2be1cf68 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 @@ -69,6 +69,7 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get { "queryResponseWriter"); public static final String REQUEST_HANDLERS = "requestHandlers"; + public static final Set REQUEST_HANDLER_APIs = Set.of(SolrRequestHandler.TYPE, REQUEST_HANDLERS); // v2-only renames: external (URL/response-key) component name -> internal SolrConfig section // name. v1's GET /config/{component} keeps using the internal names unchanged. From 13dd067db62862fc8e2289cf91837785a8d8aeb6 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 18:45:37 +0530 Subject: [PATCH 18/30] fix for v1 consistency --- .../org/apache/solr/handler/SolrConfigHandler.java | 1 + .../apache/solr/handler/admin/api/GetConfig.java | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) 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 2309f409a9c..83f76e771df 100644 --- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java @@ -222,6 +222,7 @@ private void handleGET() { resp, new GetConfig(req) .getRequestHandler( + parts.get(1), req.getParams().get("componentName"), req.getParams().getBool("expandParams", false), 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 13a2be1cf68..5b10765e4c0 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 @@ -69,7 +69,8 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get { "queryResponseWriter"); public static final String REQUEST_HANDLERS = "requestHandlers"; - public static final Set REQUEST_HANDLER_APIs = Set.of(SolrRequestHandler.TYPE, REQUEST_HANDLERS); + public static final Set REQUEST_HANDLER_APIs = + Set.of(SolrRequestHandler.TYPE, REQUEST_HANDLERS); // v2-only renames: external (URL/response-key) component name -> internal SolrConfig section // name. v1's GET /config/{component} keeps using the internal names unchanged. @@ -136,14 +137,18 @@ public ConfigZnodeVersionResponse getZnodeVersion( @PermissionName(CONFIG_READ_PERM) public ConfigInfoResponse getRequestHandler( String componentName, boolean expandParams, boolean meta) { + return getRequestHandler(REQUEST_HANDLERS, componentName, expandParams, meta); + } + + public ConfigInfoResponse getRequestHandler( + String sectionName, String componentName, boolean expandParams, boolean meta) { final var response = instantiateJerseyResponse(ConfigInfoResponse.class); Map configMap = new LinkedHashMap<>(); configMap.put( - REQUEST_HANDLERS, - requestHandlersWithImplicits(solrConfigAsMap(), expandParams, componentName)); + sectionName, requestHandlersWithImplicits(solrConfigAsMap(), expandParams, componentName)); if (componentName != null) { - filterByComponentName(configMap, REQUEST_HANDLERS, componentName, meta, solrQueryRequest); + filterByComponentName(configMap, sectionName, componentName, meta, solrQueryRequest); } response.config = jsonSafeCopy(configMap); From 1291fff8c574cc7ec4d33576024d450be0225bda Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 21:12:28 +0530 Subject: [PATCH 19/30] test fixes --- changelog/unreleased/SOLR-16396-config-requestHandler.yml | 5 +++-- .../solr/question-answer-repository/repository.json | 4 ++-- .../solr/cli/packagemanager/TestPackageManager.java | 2 +- .../modules/configuration-guide/pages/config-api.adoc | 8 ++++---- .../pages/implicit-requesthandlers.adoc | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/changelog/unreleased/SOLR-16396-config-requestHandler.yml b/changelog/unreleased/SOLR-16396-config-requestHandler.yml index 98857191726..a860843da61 100644 --- a/changelog/unreleased/SOLR-16396-config-requestHandler.yml +++ b/changelog/unreleased/SOLR-16396-config-requestHandler.yml @@ -1,5 +1,6 @@ -title: - Migrate GET /config/requestHandler to JAX-RS +title: > + Migrate GET /config/requestHandler to JAX-RS; V2 path and response key + are now plural requestHandlers (V1 unchanged) type: changed authors: - name: Jalaz Kumar diff --git a/solr/core/src/test-files/solr/question-answer-repository/repository.json b/solr/core/src/test-files/solr/question-answer-repository/repository.json index 05fad905978..bcfe7779ca3 100644 --- a/solr/core/src/test-files/solr/question-answer-repository/repository.json +++ b/solr/core/src/test-files/solr/question-answer-repository/repository.json @@ -28,9 +28,9 @@ "method": "POST" }, "verify-command": { - "path": "/api/collections/${collection}/config/requestHandler?componentName=${RH-HANDLER-PATH}&meta=true", + "path": "/api/collections/${collection}/config/requestHandlers?componentName=${RH-HANDLER-PATH}&meta=true", "method": "GET", - "condition": "$['config'].['requestHandler'].['${RH-HANDLER-PATH}'].['_packageinfo_'].['version']", + "condition": "$['config'].['requestHandlers'].['${RH-HANDLER-PATH}'].['_packageinfo_'].['version']", "expected": "${package-version}" } } diff --git a/solr/core/src/test/org/apache/solr/cli/packagemanager/TestPackageManager.java b/solr/core/src/test/org/apache/solr/cli/packagemanager/TestPackageManager.java index 2793af1641d..825bd3b5a3c 100644 --- a/solr/core/src/test/org/apache/solr/cli/packagemanager/TestPackageManager.java +++ b/solr/core/src/test/org/apache/solr/cli/packagemanager/TestPackageManager.java @@ -60,7 +60,7 @@ public void testWrongVerificationJPathIsThrown() throws IOException { plugin.verifyCommand = new SolrPackage.Command(); plugin.verifyCommand.method = "GET"; plugin.verifyCommand.path = /*"/api*/ - "/collections/" + COLLECTION_NAME + "/config/requestHandler"; + "/collections/" + COLLECTION_NAME + "/config/requestHandlers"; plugin.verifyCommand.condition = "&[no_quotes_error_jpath]"; manager.verify( new SolrPackageInstance( 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 38981e7caf6..01c043ae6a6 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 @@ -109,7 +109,7 @@ V2 API:: ==== [source,bash] ---- -http://localhost:8983/api/collections/techproducts/config/requestHandler +http://localhost:8983/api/collections/techproducts/config/requestHandlers ---- ==== ====== @@ -119,7 +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. +In the V2 API, `requestHandler`, `searchComponent` and `queryResponseWriter` are exposed under the pluralized names `requestHandlers`, `searchComponents` and `queryResponseWriters` instead (including the response JSON key). To further restrict the request to a single component within a top-level section, use the `componentName` request parameter. @@ -140,12 +140,12 @@ V2 API:: ==== [source,bash] ---- -http://localhost:8983/api/collections/techproducts/config/requestHandler?componentName=/select +http://localhost:8983/api/collections/techproducts/config/requestHandlers?componentName=/select ---- ==== ====== -The output of this command will look similar to: +The output of this command will look similar to(V1 nests under `requestHandler`; V2 under `requestHandlers`): [source,json] ---- diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/implicit-requesthandlers.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/implicit-requesthandlers.adoc index 0aad010ddc9..67948d8f144 100644 --- a/solr/solr-ref-guide/modules/configuration-guide/pages/implicit-requesthandlers.adoc +++ b/solr/solr-ref-guide/modules/configuration-guide/pages/implicit-requesthandlers.adoc @@ -693,12 +693,12 @@ V2 API:: ==== [source,bash] ---- -http://localhost:8983/api/collections/gettingstarted/config/requestHandler?componentName=/export&expandParams=true +http://localhost:8983/api/collections/gettingstarted/config/requestHandlers?componentName=/export&expandParams=true ---- ==== ====== -The response will look similar to: +The response will look similar to (V1 nests under `requestHandler`; V2 nests under `requestHandlers`): [source,json] ---- From 3372ab79ed3556660b50d24790d3e24fa45c6e0d Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 22:06:31 +0530 Subject: [PATCH 20/30] minor cleanup --- .../src/java/org/apache/solr/handler/SolrConfigHandler.java | 3 +-- .../src/java/org/apache/solr/handler/admin/api/GetConfig.java | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) 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 83f76e771df..2b2080a0673 100644 --- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java @@ -25,7 +25,6 @@ import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_CLASS; import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME; import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME_IN_OVERLAY; -import static org.apache.solr.handler.admin.api.GetConfig.REQUEST_HANDLER_APIs; import static org.apache.solr.schema.FieldType.CLASS_NAME; import java.io.IOException; @@ -217,7 +216,7 @@ private void handleGET() { .getZnodeVersion( req.getParams().getInt(ConfigOverlay.NAME, -1), req.getParams().getInt(RequestParams.NAME, -1))); - } else if (REQUEST_HANDLER_APIs.contains(parts.get(1))) { + } else if (SolrRequestHandler.TYPE.equals(parts.get(1))) { V2ApiUtils.squashIntoSolrResponseWithoutHeader( resp, new GetConfig(req) 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 5b10765e4c0..3f0466d86af 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 @@ -69,8 +69,6 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get { "queryResponseWriter"); public static final String REQUEST_HANDLERS = "requestHandlers"; - public static final Set REQUEST_HANDLER_APIs = - Set.of(SolrRequestHandler.TYPE, REQUEST_HANDLERS); // v2-only renames: external (URL/response-key) component name -> internal SolrConfig section // name. v1's GET /config/{component} keeps using the internal names unchanged. From ba2c8a956396db56d55b11274c86cd015168ee45 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 22:49:38 +0530 Subject: [PATCH 21/30] todo --- .../src/java/org/apache/solr/client/api/endpoint/ConfigApi.java | 2 ++ 1 file changed, 2 insertions(+) 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 0f3f64868b8..f10026be924 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,6 +70,8 @@ ConfigZnodeVersionResponse getZnodeVersion( @Parameter(description = "Expected request-params znode version") int expectedParamsVersion); + // TODO: RH names contain '/' (e.g. /select, /update/json), so they cannot be a @PathParam in + // the current design; Keeping filtering via @QueryParam for now. @GET @Path("/requestHandlers") @StoreApiParameters From e84d371856f2f5eed231f58f0a61e71f9f544f48 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 23:10:40 +0530 Subject: [PATCH 22/30] more tests coverage & strictness --- .../admin/api/GetConfigSolrCloudTest.java | 89 +++++++++++++++++++ .../solr/handler/admin/api/GetConfigTest.java | 25 ++++-- 2 files changed, 105 insertions(+), 9 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 19b29380afd..e480e660c2c 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 @@ -17,14 +17,17 @@ 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 org.apache.solr.client.api.model.IndexType; +import org.apache.solr.client.solrj.RemoteSolrException; import org.apache.solr.client.solrj.request.CollectionAdminRequest; import org.apache.solr.client.solrj.request.ConfigApi; import org.apache.solr.cloud.SolrCloudTestCase; import org.apache.solr.util.ExternalPaths; import org.junit.BeforeClass; import org.junit.Test; +import java.util.Map; /** HTTP tests for fetching the full config via the collection-scoped v2 path. */ public class GetConfigSolrCloudTest extends SolrCloudTestCase { @@ -77,6 +80,92 @@ public void testGetZnodeVersionFromCollection() throws Exception { assertTrue(response.znodeVersion.params >= -1); } + @Test + @SuppressWarnings("unchecked") + public void testGetRequestHandlersFromCollection() throws Exception { + var request = new ConfigApi.GetRequestHandler(IndexType.COLLECTION, COLLECTION_NAME); + var response = request.process(cluster.getSolrClient()); + + assertNotNull(response); + assertNull(response.error); + assertNotNull(response.config); + assertEquals(1, response.config.size()); + assertTrue(response.config.containsKey(REQUEST_HANDLERS)); + + Map configSection = (Map) response.config.get(REQUEST_HANDLERS); + assertNotNull(configSection); + assertTrue(configSection.containsKey("/select")); + assertTrue(configSection.containsKey("/admin/ping")); + } + + @Test + @SuppressWarnings("unchecked") + public void testGetRequestHandlersFilteredByNameFromCollection() throws Exception { + var request = new ConfigApi.GetRequestHandler(IndexType.COLLECTION, COLLECTION_NAME); + request.setComponentName("/select"); + var response = request.process(cluster.getSolrClient()); + + assertNotNull(response); + assertNull(response.error); + assertEquals(1, response.config.size()); + assertTrue(response.config.containsKey(REQUEST_HANDLERS)); + + Map configSection = (Map) response.config.get(REQUEST_HANDLERS); + assertNotNull(configSection); + assertEquals(1, configSection.size()); + assertTrue(configSection.containsKey("/select")); + + Map selectRHSection = (Map) configSection.get("/select"); + assertEquals("/select", selectRHSection.get("name")); + assertEquals("solr.SearchHandler", selectRHSection.get("class")); + } + + @Test + @SuppressWarnings("unchecked") + public void testGetRequestHandlersExpandParamsTrue() throws Exception { + var request = new ConfigApi.GetRequestHandler(IndexType.COLLECTION, COLLECTION_NAME); + request.setComponentName("/admin/ping"); + request.setExpandParams(true); + var response = request.process(cluster.getSolrClient()); + + assertNotNull(response); + assertNull(response.error); + assertEquals(1, response.config.size()); + assertTrue(response.config.containsKey(REQUEST_HANDLERS)); + + Map configSection = (Map) response.config.get(REQUEST_HANDLERS); + + Map pingRHSection = (Map) configSection.get("/admin/ping"); + assertTrue(pingRHSection.containsKey("_useParamsExpanded_")); + assertNotNull(pingRHSection.get("_useParamsExpanded_")); + Map expandedPingRHSection = (Map) pingRHSection.get("_useParamsExpanded_"); + assertEquals("[NOT AVAILABLE]", expandedPingRHSection.get("_ADMIN_PING")); + } + + @Test + @SuppressWarnings("unchecked") + public void testGetRequestHandlersOmitsExpandedParamsByDefault() throws Exception { + var request = new ConfigApi.GetRequestHandler(IndexType.COLLECTION, COLLECTION_NAME); + request.setComponentName("/admin/ping"); + var response = request.process(cluster.getSolrClient()); + + Map configSection = (Map) response.config.get(REQUEST_HANDLERS); + + Map pingRHSection = (Map) configSection.get("/admin/ping"); + assertFalse(pingRHSection.containsKey("_useParamsExpanded_")); + } + + @Test + public void testGetRequestHandlersByUnknownNameReturnsNotFound() throws Exception { + var request = new ConfigApi.GetRequestHandler(IndexType.COLLECTION, COLLECTION_NAME); + request.setComponentName("/randomHandler"); + final RemoteSolrException exception = + assertThrows( + RemoteSolrException.class, + () -> request.process(cluster.getSolrClient())); + assertEquals(404, exception.code()); + } + @Test public void testGetConfigComponentFromCollection() throws Exception { for (String component : GetConfig.MIGRATED_CONFIG_COMPONENTS) { 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 ed11ee4c217..5c0563cc713 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,11 +94,13 @@ public void testGetRequestHandlersFromCore() throws Exception { assertNull(response.error); assertNotNull(response.config); assertEquals(1, response.config.size()); + assertTrue(response.config.containsKey(REQUEST_HANDLERS)); Map configSection = (Map) response.config.get(REQUEST_HANDLERS); assertNotNull(configSection); assertTrue(configSection.containsKey("/select")); assertTrue(configSection.containsKey("/admin/ping")); + assertTrue(configSection.containsKey("/spell")); } @Test @@ -111,18 +113,21 @@ public void testGetRequestHandlersFilteredByNameFromCore() throws Exception { assertNotNull(response); assertNull(response.error); assertEquals(1, response.config.size()); + assertTrue(response.config.containsKey(REQUEST_HANDLERS)); Map configSection = (Map) response.config.get(REQUEST_HANDLERS); assertNotNull(configSection); + assertEquals(1, configSection.size()); assertTrue(configSection.containsKey("/select")); - Map select = (Map) configSection.get("/select"); - assertEquals("solr.SearchHandler", select.get("class")); + Map selectRHSection = (Map) configSection.get("/select"); + assertEquals("/select", selectRHSection.get("name")); + assertEquals("solr.SearchHandler", selectRHSection.get("class")); } @Test @SuppressWarnings("unchecked") - public void testGetRequestHandlersExpandParamsExpandsNamedHandler() throws Exception { + public void testGetRequestHandlersExpandParamsTrue() throws Exception { var request = new ConfigApi.GetRequestHandler(IndexType.CORE, CORE_NAME); request.setComponentName("/admin/ping"); request.setExpandParams(true); @@ -131,13 +136,15 @@ public void testGetRequestHandlersExpandParamsExpandsNamedHandler() throws Excep assertNotNull(response); assertNull(response.error); assertEquals(1, response.config.size()); + assertTrue(response.config.containsKey(REQUEST_HANDLERS)); Map configSection = (Map) response.config.get(REQUEST_HANDLERS); - Map ping = (Map) configSection.get("/admin/ping"); - assertNotNull(ping.get("_useParamsExpanded_")); - Map expanded = (Map) ping.get("_useParamsExpanded_"); - assertEquals("[NOT AVAILABLE]", expanded.get("_ADMIN_PING")); + Map pingRHSection = (Map) configSection.get("/admin/ping"); + assertTrue(pingRHSection.containsKey("_useParamsExpanded_")); + assertNotNull(pingRHSection.get("_useParamsExpanded_")); + Map expandedPingRHSection = (Map) pingRHSection.get("_useParamsExpanded_"); + assertEquals("[NOT AVAILABLE]", expandedPingRHSection.get("_ADMIN_PING")); } @Test @@ -149,8 +156,8 @@ public void testGetRequestHandlersOmitsExpandedParamsByDefault() throws Exceptio Map configSection = (Map) response.config.get(REQUEST_HANDLERS); - Map ping = (Map) configSection.get("/admin/ping"); - assertFalse(ping.containsKey("_useParamsExpanded_")); + Map pingRHSection = (Map) configSection.get("/admin/ping"); + assertFalse(pingRHSection.containsKey("_useParamsExpanded_")); } @Test From 6fede4cf402781576f36443d977b2e3e1edfa260 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Fri, 16 Oct 2026 23:15:04 +0530 Subject: [PATCH 23/30] minor --- .../apache/solr/handler/admin/api/GetConfigSolrCloudTest.java | 1 + 1 file changed, 1 insertion(+) 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 e480e660c2c..a706f4088f8 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 @@ -96,6 +96,7 @@ public void testGetRequestHandlersFromCollection() throws Exception { assertNotNull(configSection); assertTrue(configSection.containsKey("/select")); assertTrue(configSection.containsKey("/admin/ping")); + assertTrue(configSection.containsKey("/graph")); } @Test From c045334e9d43934f07a695af7cbad6b6be504f01 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Sat, 17 Oct 2026 19:14:04 +0530 Subject: [PATCH 24/30] tidying --- .../solr/handler/admin/api/GetConfigSolrCloudTest.java | 9 ++++----- .../org/apache/solr/handler/admin/api/GetConfigTest.java | 3 ++- 2 files changed, 6 insertions(+), 6 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 a706f4088f8..29c10ec2a96 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 @@ -19,6 +19,7 @@ import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; import static org.apache.solr.handler.admin.api.GetConfig.REQUEST_HANDLERS; +import java.util.Map; import org.apache.solr.client.api.model.IndexType; import org.apache.solr.client.solrj.RemoteSolrException; import org.apache.solr.client.solrj.request.CollectionAdminRequest; @@ -27,7 +28,6 @@ import org.apache.solr.util.ExternalPaths; import org.junit.BeforeClass; import org.junit.Test; -import java.util.Map; /** HTTP tests for fetching the full config via the collection-scoped v2 path. */ public class GetConfigSolrCloudTest extends SolrCloudTestCase { @@ -139,7 +139,8 @@ public void testGetRequestHandlersExpandParamsTrue() throws Exception { Map pingRHSection = (Map) configSection.get("/admin/ping"); assertTrue(pingRHSection.containsKey("_useParamsExpanded_")); assertNotNull(pingRHSection.get("_useParamsExpanded_")); - Map expandedPingRHSection = (Map) pingRHSection.get("_useParamsExpanded_"); + Map expandedPingRHSection = + (Map) pingRHSection.get("_useParamsExpanded_"); assertEquals("[NOT AVAILABLE]", expandedPingRHSection.get("_ADMIN_PING")); } @@ -161,9 +162,7 @@ public void testGetRequestHandlersByUnknownNameReturnsNotFound() throws Exceptio var request = new ConfigApi.GetRequestHandler(IndexType.COLLECTION, COLLECTION_NAME); request.setComponentName("/randomHandler"); final RemoteSolrException exception = - assertThrows( - RemoteSolrException.class, - () -> request.process(cluster.getSolrClient())); + assertThrows(RemoteSolrException.class, () -> request.process(cluster.getSolrClient())); assertEquals(404, exception.code()); } 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 5c0563cc713..4407845d155 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 @@ -143,7 +143,8 @@ public void testGetRequestHandlersExpandParamsTrue() throws Exception { Map pingRHSection = (Map) configSection.get("/admin/ping"); assertTrue(pingRHSection.containsKey("_useParamsExpanded_")); assertNotNull(pingRHSection.get("_useParamsExpanded_")); - Map expandedPingRHSection = (Map) pingRHSection.get("_useParamsExpanded_"); + Map expandedPingRHSection = + (Map) pingRHSection.get("_useParamsExpanded_"); assertEquals("[NOT AVAILABLE]", expandedPingRHSection.get("_ADMIN_PING")); } From bc37b68e292c1434e032b69fa9dea97b662ce4c0 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 17 Oct 2026 10:01:01 -0400 Subject: [PATCH 25/30] Fix v2 API 404 when a /config request is proxied to another node Solr forwards a v2 request to the node that actually hosts the target collection using the short "/c/{collection}/..." path alias (see HttpSolrCall#sendRemoteProxy), but Constants.INDEX_PATH_PREFIX's JAX-RS @Path regex only recognized "cores" or "collections", not "c". Any JAX-RS resource built on that prefix therefore 404'd with "Cannot find API for the path" whenever a request needed to be proxied cross-node. Reproduced with a new 2-node test in GetConfigSolrCloudTest that deliberately sends a GET /config/overlay request to the node that does NOT host the collection's only replica, then fixed by accepting "c" as an alias for "collections" in the path regex. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01RzzUgiebQ8ZhhgE2E7ZZU5 --- .../solr/client/api/util/Constants.java | 12 ++++++- .../admin/api/GetConfigSolrCloudTest.java | 34 ++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/solr/api/src/java/org/apache/solr/client/api/util/Constants.java b/solr/api/src/java/org/apache/solr/client/api/util/Constants.java index 3e3c7443c18..13f8009fbb4 100644 --- a/solr/api/src/java/org/apache/solr/client/api/util/Constants.java +++ b/solr/api/src/java/org/apache/solr/client/api/util/Constants.java @@ -24,8 +24,18 @@ private Constants() { public static final String INDEX_TYPE_PATH_PARAMETER = "indexType"; public static final String INDEX_NAME_PATH_PARAMETER = "indexName"; + + // "c" is accepted alongside "collections" because Solr internally forwards a v2 request to the + // node actually hosting a collection using the short "/c/{collection}/..." alias (see + // HttpSolrCall#sendRemoteProxy and V2HttpCall's "c"/"collections" handling); without it, any + // JAX-RS resource built on this prefix 404s with "Cannot find API for the path" whenever the + // request has to be proxied to another node. public static final String INDEX_PATH_PREFIX = - "/{" + INDEX_TYPE_PATH_PARAMETER + ":cores|collections}/{" + INDEX_NAME_PATH_PARAMETER + "}"; + "/{" + + INDEX_TYPE_PATH_PARAMETER + + ":cores|collections|c}/{" + + INDEX_NAME_PATH_PARAMETER + + "}"; public static final String CORE_NAME_PATH_PARAMETER = "coreName"; 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 19b29380afd..8a23c749e1d 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 @@ -19,9 +19,12 @@ import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; import org.apache.solr.client.api.model.IndexType; +import org.apache.solr.client.solrj.jetty.HttpJettySolrClient; import org.apache.solr.client.solrj.request.CollectionAdminRequest; import org.apache.solr.client.solrj.request.ConfigApi; import org.apache.solr.cloud.SolrCloudTestCase; +import org.apache.solr.common.cloud.Replica; +import org.apache.solr.embedded.JettySolrRunner; import org.apache.solr.util.ExternalPaths; import org.junit.BeforeClass; import org.junit.Test; @@ -34,7 +37,9 @@ public class GetConfigSolrCloudTest extends SolrCloudTestCase { @BeforeClass public static void setupCluster() throws Exception { System.setProperty(ALLOW_PATHS_SYSPROP, ExternalPaths.SERVER_HOME.toAbsolutePath().toString()); - configureCluster(1).addConfig("conf", configset("cloud-minimal")).configure(); + // 2 nodes so that a request can land on a node that does *not* host the collection's only + // replica, forcing Solr to internally proxy the request to the node that does. + configureCluster(2).addConfig("conf", configset("cloud-minimal")).configure(); CollectionAdminRequest.createCollection(COLLECTION_NAME, "conf", 1, 1) .process(cluster.getSolrClient()); } @@ -77,6 +82,33 @@ public void testGetZnodeVersionFromCollection() throws Exception { assertTrue(response.znodeVersion.params >= -1); } + /** + * Reproduces a bug where a v2 "/config" request proxied internally to the node actually hosting + * the collection fails with "Cannot find API for the path", because Solr forwards such requests + * using the short "/c/{collection}/..." alias, but {@code Constants.INDEX_PATH_PREFIX}'s JAX-RS + * {@code @Path} regex only recognizes "cores" or "collections", not "c". + */ + @Test + public void testGetOverlayFromCollectionViaNonHostingNode() throws Exception { + Replica replica = getCollectionState(COLLECTION_NAME).replicaStream().findFirst().orElseThrow(); + JettySolrRunner hostingJetty = cluster.getReplicaJetty(replica); + JettySolrRunner otherJetty = + cluster.getJettySolrRunners().stream() + .filter(jetty -> jetty != hostingJetty) + .findFirst() + .orElseThrow(); + + try (var client = new HttpJettySolrClient.Builder(otherJetty.getBaseUrl().toString()).build()) { + var request = new ConfigApi.GetOverlay(IndexType.COLLECTION, COLLECTION_NAME); + var response = request.process(client); + + assertNotNull(response); + assertNull(response.error); + assertNotNull(response.overlay); + assertTrue(response.overlay.containsKey("znodeVersion")); + } + } + @Test public void testGetConfigComponentFromCollection() throws Exception { for (String component : GetConfig.MIGRATED_CONFIG_COMPONENTS) { From 5b168595944fb182ecd2a67716e53ce4fce23c86 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 17 Oct 2026 11:13:33 -0400 Subject: [PATCH 26/30] Fix ErrorProne ReferenceEquality failure in the new regression test CI builds with -Werror, and errorprone flags comparing JettySolrRunner instances with != instead of .equals(). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01RzzUgiebQ8ZhhgE2E7ZZU5 --- .../apache/solr/handler/admin/api/GetConfigSolrCloudTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8a23c749e1d..51ff1504293 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 @@ -94,7 +94,7 @@ public void testGetOverlayFromCollectionViaNonHostingNode() throws Exception { JettySolrRunner hostingJetty = cluster.getReplicaJetty(replica); JettySolrRunner otherJetty = cluster.getJettySolrRunners().stream() - .filter(jetty -> jetty != hostingJetty) + .filter(jetty -> !jetty.equals(hostingJetty)) .findFirst() .orElseThrow(); From 54d11e2c9015464ee7ef62f8b5b561e3087ce19b Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Mon, 19 Oct 2026 22:18:43 +0530 Subject: [PATCH 27/30] fixed packagetooltest --- .../repository.json | 29 ++++++++++++++++++- .../org/apache/solr/cli/PackageToolTest.java | 4 +-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/solr/core/src/test-files/solr/question-answer-repository/repository.json b/solr/core/src/test-files/solr/question-answer-repository/repository.json index bcfe7779ca3..626361d25d5 100644 --- a/solr/core/src/test-files/solr/question-answer-repository/repository.json +++ b/solr/core/src/test-files/solr/question-answer-repository/repository.json @@ -48,7 +48,34 @@ "url": "question-answer-request-handler-1.1.jar.tmp", "sig": "2PBHV2xBqd+teFpSY322lviCrr6EXB7g1Q7UAZcNsr2qzR+w+sPSyfV4YYeHKHYbMkyw1YBz11Gw/Fdgd1VIOQ==" } - ] + ], + "manifest": { + "version-constraint": ">8", + "plugins": [ + { + "name": "request-handler", + "setup-command": { + "path": "/api/collections/${collection}/config", + "payload": {"add-requesthandler": {"name": "${RH-HANDLER-PATH}", "class": "question-answer:fullstory.QARequestHandler"}}, + "method": "POST" + }, + "uninstall-command": { + "path": "/api/collections/${collection}/config", + "payload": {"delete-requesthandler": "${RH-HANDLER-PATH}"}, + "method": "POST" + }, + "verify-command": { + "path": "/api/collections/${collection}/config/requestHandlers?componentName=${RH-HANDLER-PATH}&meta=true", + "method": "GET", + "condition": "$['config'].['requestHandlers'].['${RH-HANDLER-PATH}'].['_packageinfo_'].['version']", + "expected": "${package-version}" + } + } + ], + "parameter-defaults": { + "RH-HANDLER-PATH": "/mypath" + } + } } ] } diff --git a/solr/core/src/test/org/apache/solr/cli/PackageToolTest.java b/solr/core/src/test/org/apache/solr/cli/PackageToolTest.java index 31250974691..a919b30555b 100644 --- a/solr/core/src/test/org/apache/solr/cli/PackageToolTest.java +++ b/solr/core/src/test/org/apache/solr/cli/PackageToolTest.java @@ -320,9 +320,9 @@ void assertPackageVersion( testForResponseElement( cluster.getJettySolrRunner(0).getBaseUrl().toString() + "/" + collection, - "/config/requestHandlers?componentName=" + component + "&meta=true", + "/config/requestHandler?componentName=" + component + "&meta=true", credentials, - Arrays.asList("config", "requestHandlers", component, "_packageinfo_", "version"), + Arrays.asList("config", "requestHandler", component, "_packageinfo_", "version"), componentVersion); } From dc409474c575e274bdef2ed7cd677c719c58f6ef Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Mon, 19 Oct 2026 22:28:09 +0530 Subject: [PATCH 28/30] cl --- changelog/unreleased/SOLR-16396-config-requestHandler.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog/unreleased/SOLR-16396-config-requestHandler.yml b/changelog/unreleased/SOLR-16396-config-requestHandler.yml index a860843da61..44164b792c5 100644 --- a/changelog/unreleased/SOLR-16396-config-requestHandler.yml +++ b/changelog/unreleased/SOLR-16396-config-requestHandler.yml @@ -5,6 +5,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 e0fd810eb28926b4c621adf6b900ac3b4307f3de Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 20 Oct 2026 07:45:33 -0400 Subject: [PATCH 29/30] Fix a long standing bug that v1 hid but v2 stricter mapping rules on "boolean" values exposed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. PackageManager.verify() builds its verify-command URL by taking the path straight from repository.json's template (e.g. /api/collections/${collection}/config/requestHandlers?componentName=${RH-HANDLER-PATH}&meta=true) and passing that whole string, including the embedded ?..., as the path argument to GenericSolrRequest, with an empty separate params object. 2. HttpJettySolrClient.makeRequest() builds GET requests as url + wparams.toQueryString() — and toQueryString() always starts with a literal ?. Since url already contains its own ?..., this produces a malformed URL with two ? characters: ...requestHandlers?componentName=/mypath2&meta=true?wt=json. 3. Per the URL spec, everything after the first ? is one query string. Splitting on & gives meta = "true?wt=json" — a garbled value, not "true". 4. V1's parameter binding (SolrParams.getBool → StrUtils.parseBool) uses startsWith("true"), so it's lenient enough to still resolve "true?wt=json" to true — masking the bug for years on the old /config/requestHandler endpoint. 5. V2's JAX-RS @QueryParam boolean meta binding does an exact match, so "true?wt=json" fails to parse as true and silently resolves to false — the test fixture's upgrade to the new plural /config/requestHandlers endpoint is what exposed this for the first time. --- .../solr/cli/packagemanager/PackageUtils.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/cli/packagemanager/PackageUtils.java b/solr/core/src/java/org/apache/solr/cli/packagemanager/PackageUtils.java index 35fdc24d97d..56ad2fa20a4 100644 --- a/solr/core/src/java/org/apache/solr/cli/packagemanager/PackageUtils.java +++ b/solr/core/src/java/org/apache/solr/cli/packagemanager/PackageUtils.java @@ -160,12 +160,26 @@ public static String getJsonStringFromNonCollectionApi( return getJsonStringFromUrl(client, path, params, false); } - /** Returns JSON string from a given Solr URL */ + /** + * repository.json's verify/setup command templates embed their query string directly in {@code + * path} (e.g. {@code .../requestHandlers?componentName=x&meta=true}). The HTTP client always + * appends its own query string afterward with a leading {@code ?}, and a URL with two {@code ?} + * characters is parsed as a single query string on the wire -- so the last templated parameter + * silently absorbs the client's params as part of its own value instead of being read correctly. + */ private static String getJsonStringFromUrl( SolrClient client, String path, SolrParams params, boolean isCollectionApi) { try { + String resolvedPath = path; + int queryIndex = path.indexOf('?'); + if (queryIndex >= 0) { + ModifiableSolrParams merged = new ModifiableSolrParams(params); + merged.add(parseQueryString(path.substring(queryIndex + 1))); + params = merged; + resolvedPath = path.substring(0, queryIndex); + } GenericSolrRequest request = - new GenericSolrRequest(SolrRequest.METHOD.GET, path, params) + new GenericSolrRequest(SolrRequest.METHOD.GET, resolvedPath, params) .setRequiresCollection(isCollectionApi); request.setResponseParser(new InputStreamResponseParser("json")); var response = client.request(request); @@ -177,6 +191,16 @@ private static String getJsonStringFromUrl( } } + private static ModifiableSolrParams parseQueryString(String queryString) { + ModifiableSolrParams params = new ModifiableSolrParams(); + for (String pair : queryString.split("&")) { + if (pair.isEmpty()) continue; + String[] kv = pair.split("=", 2); + params.add(kv[0], kv.length > 1 ? kv[1] : ""); + } + return params; + } + /** * Fetches a manifest file from the File Store / Package Store. A SHA512 check is enforced after * fetching. From 3f84d10bb98089c2409fd14c97c6632d9d8e84e2 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 20 Oct 2026 08:05:59 -0400 Subject: [PATCH 30/30] Simplify PackageUtils query-string parsing and GetConfig's handler-map plumbing - PackageUtils.getJsonStringFromUrl now reuses the existing, more correct SolrRequestParsers.parseQueryString (which %-decodes values) instead of a hand-rolled split("&")/split("=") parser that didn't. - GetConfig.requestHandlersWithImplicits had two incompatible calling conventions: buildConfigMap relied on it mutating a full config-map wrapper in place, while getRequestHandler only used its return value and threw the wrapper away. Both callers now extract the requestHandler submap themselves before calling it, so the shared method has a single, consistent contract: take a handler map, merge+expand it in place, return it. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01RzzUgiebQ8ZhhgE2E7ZZU5 --- .../solr/cli/packagemanager/PackageUtils.java | 18 ++++---------- .../solr/handler/admin/api/GetConfig.java | 24 ++++++++++++------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/cli/packagemanager/PackageUtils.java b/solr/core/src/java/org/apache/solr/cli/packagemanager/PackageUtils.java index 56ad2fa20a4..72d76ddf844 100644 --- a/solr/core/src/java/org/apache/solr/cli/packagemanager/PackageUtils.java +++ b/solr/core/src/java/org/apache/solr/cli/packagemanager/PackageUtils.java @@ -51,6 +51,7 @@ import org.apache.solr.filestore.ClusterFileStore; import org.apache.solr.filestore.DistribFileStore; import org.apache.solr.filestore.FileStoreAPI; +import org.apache.solr.servlet.SolrRequestParsers; import org.apache.solr.util.SolrJacksonAnnotationInspector; import org.apache.zookeeper.server.ByteBufferInputStream; @@ -170,16 +171,15 @@ public static String getJsonStringFromNonCollectionApi( private static String getJsonStringFromUrl( SolrClient client, String path, SolrParams params, boolean isCollectionApi) { try { - String resolvedPath = path; int queryIndex = path.indexOf('?'); if (queryIndex >= 0) { ModifiableSolrParams merged = new ModifiableSolrParams(params); - merged.add(parseQueryString(path.substring(queryIndex + 1))); + merged.add(SolrRequestParsers.parseQueryString(path.substring(queryIndex + 1))); params = merged; - resolvedPath = path.substring(0, queryIndex); + path = path.substring(0, queryIndex); } GenericSolrRequest request = - new GenericSolrRequest(SolrRequest.METHOD.GET, resolvedPath, params) + new GenericSolrRequest(SolrRequest.METHOD.GET, path, params) .setRequiresCollection(isCollectionApi); request.setResponseParser(new InputStreamResponseParser("json")); var response = client.request(request); @@ -191,16 +191,6 @@ private static String getJsonStringFromUrl( } } - private static ModifiableSolrParams parseQueryString(String queryString) { - ModifiableSolrParams params = new ModifiableSolrParams(); - for (String pair : queryString.split("&")) { - if (pair.isEmpty()) continue; - String[] kv = pair.split("=", 2); - params.add(kv[0], kv.length > 1 ? kv[1] : ""); - } - return params; - } - /** * Fetches a manifest file from the File Store / Package Store. A SHA512 check is enforced after * fetching. 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 3f0466d86af..b8efea0a11c 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 @@ -138,13 +138,17 @@ public ConfigInfoResponse getRequestHandler( return getRequestHandler(REQUEST_HANDLERS, componentName, expandParams, meta); } + @SuppressWarnings("unchecked") public ConfigInfoResponse getRequestHandler( String sectionName, String componentName, boolean expandParams, boolean meta) { final var response = instantiateJerseyResponse(ConfigInfoResponse.class); + Map reqHandlersMap = + (Map) + solrConfigAsMap().computeIfAbsent(SolrRequestHandler.TYPE, k -> new LinkedHashMap<>()); Map configMap = new LinkedHashMap<>(); configMap.put( - sectionName, requestHandlersWithImplicits(solrConfigAsMap(), expandParams, componentName)); + sectionName, requestHandlersWithImplicits(reqHandlersMap, expandParams, componentName)); if (componentName != null) { filterByComponentName(configMap, sectionName, componentName, meta, solrQueryRequest); } @@ -254,19 +258,23 @@ private NullKeyTolerantMap jsonSafeCopy(Map map) { return jsonSafeCopy; } + @SuppressWarnings("unchecked") private Map buildConfigMap(boolean expandParams) { Map configMap = solrConfigAsMap(); - requestHandlersWithImplicits(configMap, expandParams, null); - return jsonSafeCopy(configMap); - } - - @SuppressWarnings({"unchecked"}) - private Map requestHandlersWithImplicits( - Map configMap, boolean expandParams, String expandOnlyComponentName) { Map reqHandlersMap = (Map) configMap.computeIfAbsent(SolrRequestHandler.TYPE, k -> new LinkedHashMap<>()); + requestHandlersWithImplicits(reqHandlersMap, expandParams, null); + return jsonSafeCopy(configMap); + } + /** + * Merges implicit handlers into {@code reqHandlersMap} (mutated in place and returned), expanding + * useParams for {@code expandOnlyComponentName} (or all entries if null) when {@code + * expandParams}. + */ + private Map requestHandlersWithImplicits( + Map reqHandlersMap, boolean expandParams, String expandOnlyComponentName) { List plugins = solrQueryRequest.getCore().getImplicitHandlers(); for (PluginInfo plugin : plugins) { if (SolrRequestHandler.TYPE.equals(plugin.type) && !reqHandlersMap.containsKey(plugin.name)) {