diff --git a/changelog/unreleased/SOLR-16396-config-remaining-get-sections.yml b/changelog/unreleased/SOLR-16396-config-remaining-get-sections.yml new file mode 100644 index 000000000000..b2b23a0cf943 --- /dev/null +++ b/changelog/unreleased/SOLR-16396-config-remaining-get-sections.yml @@ -0,0 +1,13 @@ +title: > + Migrate remaining GET /config/{component} sections to JAX-RS. + V2 renames listener, queryParser, valueSourceParser, cache, transformer, updateProcessor, queryConverter and expressible to + listeners, queryParsers, valueSourceParsers, caches, transformers, updateProcessors, queryConverters and expressibles respectively. (V1 unchanged) +type: changed +authors: + - name: Jalaz Kumar + nick: jaykay12 + - name: Eric Pugh + nick: epugh +links: + - name: SOLR-16396 + url: https://issues.apache.org/jira/browse/SOLR-16396 diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ConfigApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ConfigApi.java index f10026be9249..70aaf6c6b191 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/ConfigApi.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ConfigApi.java @@ -91,12 +91,10 @@ ConfigInfoResponse getRequestHandler( @Parameter(description = "Include package metadata (_packageinfo_)") boolean meta); - // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS ("searchComponents" - // and "queryResponseWriters" here are the external, pluralized names for the internal - // "searchComponent"/"queryResponseWriter" sections; see GetConfig#toExternalComponentName) + // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS @GET @Path( - "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriters}") + "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriters|luceneMatchVersion|initParams|listeners|requestDispatcher|jmx|queryParsers|valueSourceParsers|transformers|updateProcessors|updateRequestProcessorChain|queryConverters|caches|circuitBreaker|expressibles|schemaFactory|restManager|statsCache|recoveryStrategy|indexReaderFactory|indexConfigdeletionPolicy|updateHandlerupdateLog}") @StoreApiParameters @Operation( summary = "Fetch a config section", @@ -105,12 +103,15 @@ ConfigInfoResponse getConfigComponent( @PathParam("component") @Parameter(description = "Name of the config component") String component); - // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS ("searchComponents" - // and "queryResponseWriters" here are the external, pluralized names for the internal - // "searchComponent"/"queryResponseWriter" sections; see GetConfig#toExternalComponentName) + // TODO: Keep this regex in sync with GetConfig.MIGRATED_CONFIG_COMPONENTS. Sections without + // REQUIRE_NAME in SolrConfig's plugin registration (initParams, listeners, + // updateRequestProcessorChain, circuitBreaker) are deliberately excluded here: they're + // serialized as a List rather than a name-keyed Map, so by-name lookup isn't meaningful for + // them (see GetConfig.filterByComponentName). + // Scalars / non-plugin sections (luceneMatchVersion, jmx etc) are also omitted. @GET @Path( - "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriters}/{componentName}") + "/{component:updateHandler|directoryFactory|indexConfig|codecFactory|query|searchComponents|queryResponseWriters|queryParsers|valueSourceParsers|transformers|updateProcessors|queryConverters|caches|expressibles|schemaFactory|restManager|statsCache|recoveryStrategy|indexReaderFactory}/{componentName}") @StoreApiParameters @Operation( summary = "Fetch a single named entry within a config section", diff --git a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java index 341ecc2f0961..d44e7fe9c53f 100644 --- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java @@ -237,16 +237,9 @@ private void handleGET() { : getConfig.getConfigComponentByName( parts.get(1), componentName, req.getParams().getBool("meta", false))); } else { - 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"); - if (componentName != null) { - GetConfig.filterByComponentName( - val, parts.get(1), componentName, req.getParams().getBool("meta", false), req); - } - resp.add("config", val); + throw new SolrException( + SolrException.ErrorCode.NOT_FOUND, + "No config API for path: /config/" + parts.get(1)); } } } diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java index 5978cb8e0f25..cd89b1d3e9ed 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java @@ -67,7 +67,28 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get { "codecFactory", "query", "searchComponent", - "queryResponseWriter"); + "queryResponseWriter", + "luceneMatchVersion", + "initParams", + "listener", + "requestDispatcher", + "jmx", + "queryParser", + "valueSourceParser", + "transformer", + "updateProcessor", + "updateRequestProcessorChain", + "queryConverter", + "cache", + "circuitBreaker", + "expressible", + "schemaFactory", + "restManager", + "statsCache", + "recoveryStrategy", + "indexReaderFactory", + "indexConfigdeletionPolicy", + "updateHandlerupdateLog"); public static final String REQUEST_HANDLERS = "requestHandlers"; @@ -77,7 +98,15 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get { private static final Map EXTERNAL_TO_INTERNAL_COMPONENT_NAME = Map.of( "searchComponents", "searchComponent", - "queryResponseWriters", "queryResponseWriter"); + "queryResponseWriters", "queryResponseWriter", + "listeners", "listener", + "queryParsers", "queryParser", + "valueSourceParsers", "valueSourceParser", + "transformers", "transformer", + "updateProcessors", "updateProcessor", + "queryConverters", "queryConverter", + "expressibles", "expressible", + "caches", "cache"); // Same, but for the full /config dump's top-level keys, which also renames requestHandler. // Derived from the map above so the two can't drift apart. @@ -239,10 +268,16 @@ public static void filterByComponentName( String componentName, boolean meta, SolrQueryRequest req) { - Map pluginNameVsPluginInfo = (Map) configMap.get(component); - if (pluginNameVsPluginInfo == null) { + Object sectionValue = configMap.get(component); + if (sectionValue == null) { return; } + if (!(sectionValue instanceof Map) && !(sectionValue instanceof MapWriter)) { + throw new SolrException( + SolrException.ErrorCode.NOT_FOUND, + component + " does not support filtering by componentName"); + } + Map pluginNameVsPluginInfo = (Map) sectionValue; Object o = pluginNameVsPluginInfo instanceof MapWriter @@ -335,7 +370,7 @@ private Map requestHandlersWithImplicits( return reqHandlersMap; } - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings("unchecked") private Map expandUseParams(SolrQueryRequest req, Object plugin) { Map pluginInfo; if (plugin instanceof Map) { diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfigAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfigAPI.java index 6cd437911006..110974eec68e 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfigAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfigAPI.java @@ -28,9 +28,8 @@ /** * V2 APIs for retrieving some or all configuration relevant to a particular collection (or core). * - *

This class covers legacy AnnotatedApi paths under GET /config: /params, /params{paramset}, and - * remaining {@code /config/{component}} sections not yet migrated to {@link - * org.apache.solr.client.api.endpoint.ConfigApi.Get} + *

This class only covers legacy AnnotatedApi paths under GET /config: /params and + * /params{paramset} */ public class GetConfigAPI { private final SolrConfigHandler configHandler; @@ -54,37 +53,4 @@ public void getAllParamSets(SolrQueryRequest req, SolrQueryResponse rsp) { public void getSingleParamSet(SolrQueryRequest req, SolrQueryResponse rsp) { configHandler.handleRequest(req, rsp); } - - // GET /config/... sections still via SolrConfigHandler. - // Every name SolrConfig.writeMap can emit. - // Delete a path from this list when that section is migrated to Jersey - @EndPoint( - path = { - "/config/luceneMatchVersion", - "/config/initParams", - "/config/listener", - "/config/requestDispatcher", - "/config/jmx", - "/config/queryParser", - "/config/valueSourceParser", - "/config/transformer", - "/config/updateProcessor", - "/config/updateRequestProcessorChain", - "/config/queryConverter", - "/config/cache", - "/config/circuitBreaker", - "/config/expressible", - "/config/schemaFactory", - "/config/restManager", - "/config/statsCache", - "/config/recoveryStrategy", - "/config/indexReaderFactory", - "/config/indexConfigdeletionPolicy", - "/config/updateHandlerupdateLog" - }, - method = GET, - permission = CONFIG_READ_PERM) - public void getComponentConfig(SolrQueryRequest req, SolrQueryResponse rsp) { - configHandler.handleRequest(req, rsp); - } } diff --git a/solr/core/src/test/org/apache/solr/handler/admin/V2ConfigAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/admin/V2ConfigAPIMappingTest.java index e7912afa8faa..a671cfce9f25 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/V2ConfigAPIMappingTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/V2ConfigAPIMappingTest.java @@ -44,18 +44,6 @@ public void populateApiBag() { apiBag.registerObject(new ModifyParamSetAPI(getRequestHandler())); } - // GET /v2/collectionName/config/ is a pure pass-through to the underlying request - // handler. Just check - // the API lookup works for a handful of the valid config "components". - @Test - public void testGetSingleComponentConfig() { - assertAnnotatedApiExistsFor("GET", "/config/jmx"); - assertAnnotatedApiExistsFor("GET", "/config/requestDispatcher"); - assertAnnotatedApiExistsFor("GET", "/config/luceneMatchVersion"); - assertAnnotatedApiExistsFor("GET", "/config/indexConfigdeletionPolicy"); - assertAnnotatedApiExistsFor("GET", "/config/updateHandlerupdateLog"); - } - @Test public void testGetParamsetsConfig() { assertAnnotatedApiExistsFor("GET", "/config/params"); diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java index c9c836405f8c..059f452f0b2f 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java @@ -19,6 +19,7 @@ import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; import static org.apache.solr.handler.admin.api.GetConfig.REQUEST_HANDLERS; +import java.util.List; import java.util.Map; import org.apache.solr.SolrTestCase; import org.apache.solr.client.api.model.IndexType; @@ -242,6 +243,37 @@ public void testGetConfigComponentFilteredByNameForOtherMigratedComponents() thr assertTrue(querySection.containsKey("filterCache")); } + @Test + @SuppressWarnings("unchecked") + public void testGetConfigComponentSectionShapes() throws Exception { + var queryResponse = + new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME, "luceneMatchVersion") + .process(solrTestRule.getSolrClient(CORE_NAME)); + assertNull(queryResponse.error); + assertTrue(queryResponse.config.get("luceneMatchVersion") instanceof String); + + queryResponse = + new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME, "initParams") + .process(solrTestRule.getSolrClient(CORE_NAME)); + assertNull(queryResponse.error); + assertTrue(queryResponse.config.get("initParams") instanceof List); + + queryResponse = + new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME, "requestDispatcher") + .process(solrTestRule.getSolrClient(CORE_NAME)); + assertNull(queryResponse.error); + assertTrue(queryResponse.config.get("requestDispatcher") instanceof Map); + + queryResponse = + new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME, "updateProcessors") + .process(solrTestRule.getSolrClient(CORE_NAME)); + assertNull(queryResponse.error); + Map querySection = + (Map) queryResponse.config.get("updateProcessors"); + assertNotNull(querySection); + assertTrue(querySection.containsKey("uuid")); + } + @Test public void testGetConfigComponentByUnknownNameReturnsNotFound() { var request = @@ -255,14 +287,42 @@ public void testGetConfigComponentByUnknownNameReturnsNotFound() { } @Test - public void testGetConfigComponentRejectsOldSingularSearchComponentPath() { - // v2 intentionally renamed this one section to the plural "searchComponents" in the URL; + public void testGetConfigComponentByNameOnListSectionReturnsNotFound() { + // List-shaped sections are excluded from the by-name @Path; Jersey should return 404. + for (String component : + List.of("listeners", "initParams", "updateRequestProcessorChain", "circuitBreaker")) { + var request = + new ConfigApi.GetConfigComponentByName(IndexType.CORE, CORE_NAME, component, "random"); + final RemoteSolrException ex = + expectThrows( + RemoteSolrException.class, + () -> request.process(solrTestRule.getSolrClient(CORE_NAME))); + assertEquals(404, ex.code()); + } + } + + @Test + public void testGetConfigComponentRejectsOldSingularPaths() { + // v2-only plurals; // the old singular form (still used by v1) shouldn't match the v2 route at all. - var request = new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME, "searchComponent"); - final RemoteSolrException ex = - expectThrows( - RemoteSolrException.class, - () -> request.process(solrTestRule.getSolrClient(CORE_NAME))); - assertEquals(404, ex.code()); + for (String component : + List.of( + "searchComponent", + "queryResponseWriter", + "listener", + "queryParser", + "valueSourceParser", + "transformer", + "updateProcessor", + "queryConverter", + "expressible", + "cache")) { + var request = new ConfigApi.GetConfigComponent(IndexType.CORE, CORE_NAME, component); + final RemoteSolrException ex = + expectThrows( + RemoteSolrException.class, + () -> request.process(solrTestRule.getSolrClient(CORE_NAME))); + assertEquals(404, ex.code()); + } } } diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/config-api.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/config-api.adoc index cbf7836a4f24..42e1f77c7945 100644 --- a/solr/solr-ref-guide/modules/configuration-guide/pages/config-api.adoc +++ b/solr/solr-ref-guide/modules/configuration-guide/pages/config-api.adoc @@ -124,8 +124,12 @@ The output will be details of each request handler defined in `solrconfig.xml`, To see the configuration for implicit request handlers, add `expandParams=true` to the request. See the documentation for implicit request handlers linked above for examples using this command. -The available top-level sections that can be added as path parameters are: `query`, `requestHandler`, `searchComponent`, `updateHandler`, `queryResponseWriter`, `initParams`, `znodeVersion`, `listener`, `directoryFactory`, `indexConfig`, and `codecFactory`. -In the V2 API, `requestHandler`, `searchComponent` and `queryResponseWriter` are exposed under the pluralized names `requestHandlers`, `searchComponents` and `queryResponseWriters` instead (including the response JSON key). +The available top-level sections that can be added as path parameters include: `query`, `requestHandler`, `searchComponent`, `updateHandler`, `queryResponseWriter`, `initParams`, `znodeVersion`, `listener`, `directoryFactory`, `indexConfig`, and `codecFactory`. +(Other `solrconfig.xml` sections emitted by Solr may also be fetched the same way) + +In the V2 API, several multi-valued sections use pluralized path names (and the same plural as the response JSON key). + +V1 paths keep the singular / internal names unchanged. To further restrict the request to a single component within a top-level section, use the `componentName` request parameter. @@ -166,9 +170,10 @@ The output of this command will look similar to(V1 nests under `requestHandler`; } ---- -The ability to restrict to objects within a top-level section is limited to request handlers (`requestHandler`), search components (`searchComponent`), and response writers (`queryResponseWriter`). +By-name lookup applies to sections that are maps of named plugins, such as request handlers, search components, response writers etc. +List-shaped sections like initParams, listeners, updateRequestProcessorChain etc cannot be filtered by name. -For search components and response writers, the V2 API uses a path segment instead of the `componentName` request parameter, under the pluralized section names. +For those name-keyed sections other than the request handler, the V2 API uses a path segment instead of the `componentName` request parameter, under the pluralized section names. For example, to return configuration for the `elevator` search component: [tabs#getnamedcomponent] @@ -191,7 +196,7 @@ http://localhost:8983/api/collections/techproducts/config/searchComponents/eleva ==== ====== -The same applies to response writers. +The same pattern applies to other named-key sections. For example, to return configuration for the `xslt` response writer: [tabs#getnamedwriter] @@ -202,6 +207,7 @@ V1 API:: [source,bash] ---- http://localhost:8983/solr/techproducts/config/queryResponseWriter?componentName=xslt +http://localhost:8983/solr/techproducts/config/updateProcessor?componentName=uuid ---- ==== V2 API:: @@ -210,11 +216,12 @@ V2 API:: [source,bash] ---- http://localhost:8983/api/collections/techproducts/config/queryResponseWriters/xslt +http://localhost:8983/api/collections/techproducts/config/updateProcessors/uuid ---- ==== ====== -Request handlers are unaffected and continue to use `componentName` in both APIs, as shown above. +Request handlers are a special case and continue to use `componentName` in both APIs, as shown above. == Commands to Modify the Config