This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch branch_10_1 in repository https://gitbox.apache.org/repos/asf/solr.git
commit f3d5f420b9b205d894f6da57149f249582fc49ec Author: Jalaz Kumar <[email protected]> AuthorDate: Sat Oct 3 21:08:30 2026 +0530 SOLR-16396 | Migration of Get Config V2 API to JAX-RS construct (#4951) Co-authored-by: Eric Pugh <[email protected]> (cherry picked from commit 14c7aac0d151402b00259e2fb9bf5eed7049ec5d) (cherry picked from commit 33e3f25e51ede7097b4f706965df8ab723b462f7) --- changelog/unreleased/SOLR-16396-config-get.yml | 14 +++ .../apache/solr/client/api/endpoint/ConfigApi.java | 46 ++++++++ .../solr/client/api/model/ConfigInfoResponse.java | 25 +++++ .../org/apache/solr/handler/SolrConfigHandler.java | 11 +- .../apache/solr/handler/admin/api/GetConfig.java | 123 +++++++++++++++++++++ .../solr/handler/admin/api/GetConfigAPI.java | 12 +- .../org/apache/solr/jersey/NullKeyTolerantMap.java | 88 +++++++++++++++ .../solr/handler/admin/V2ConfigAPIMappingTest.java | 6 - .../handler/admin/api/GetConfigSolrCloudTest.java | 55 +++++++++ .../solr/handler/admin/api/GetConfigTest.java | 59 ++++++++++ .../configuration-guide/pages/config-api.adoc | 32 +++++- solr/webapp/web/js/angular/controllers/schema.js | 14 ++- solr/webapp/web/js/angular/services.js | 12 +- 13 files changed, 468 insertions(+), 29 deletions(-) diff --git a/changelog/unreleased/SOLR-16396-config-get.yml b/changelog/unreleased/SOLR-16396-config-get.yml new file mode 100644 index 00000000000..729eb3eaa30 --- /dev/null +++ b/changelog/unreleased/SOLR-16396-config-get.yml @@ -0,0 +1,14 @@ +title: + Migration of Get Config V2 API to JAX-RS construct. Note one response-shape difference from V1 - + a plugin's unnamed child elements (e.g. anonymous `processor` entries under an + `updateRequestProcessorChain`) are now grouped under a `children` key in the V2 JSON response, + instead of V1's legacy empty-string (`""`) key. +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 new file mode 100644 index 00000000000..3e0ab463af2 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ConfigApi.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.client.api.endpoint; + +import static org.apache.solr.client.api.util.Constants.INDEX_PATH_PREFIX; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; +import org.apache.solr.client.api.model.ConfigInfoResponse; +import org.apache.solr.client.api.util.StoreApiParameters; + +/** V2 API for reading and modifying a core/collection's config */ +public interface ConfigApi { + + @Path(INDEX_PATH_PREFIX + "/config") + interface Get { + @GET + @StoreApiParameters + @Operation( + summary = "Fetch the entire config of the specified core or collection", + tags = {"config"}) + ConfigInfoResponse getConfig( + @QueryParam("expandParams") + @DefaultValue("false") + @Parameter(description = "Expand request handler useParams and effective parameters") + boolean expandParams); + } +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ConfigInfoResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/ConfigInfoResponse.java new file mode 100644 index 00000000000..e57a59c726f --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/ConfigInfoResponse.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.client.api.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; + +public class ConfigInfoResponse extends SolrJerseyResponse { + @JsonProperty("config") + public Map<String, Object> config; +} 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 dc2fcc2b7fc..f8b96086fd7 100644 --- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java @@ -52,6 +52,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.solr.api.AnnotatedApi; import org.apache.solr.api.Api; +import org.apache.solr.api.JerseyResource; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.SolrResponse; import org.apache.solr.client.solrj.impl.HttpSolrClient; @@ -85,9 +86,11 @@ import org.apache.solr.core.RequestParams; import org.apache.solr.core.SolrConfig; import org.apache.solr.core.SolrCore; import org.apache.solr.core.SolrResourceLoader; +import org.apache.solr.handler.admin.api.GetConfig; import org.apache.solr.handler.admin.api.GetConfigAPI; import org.apache.solr.handler.admin.api.ModifyConfigComponentAPI; import org.apache.solr.handler.admin.api.ModifyParamSetAPI; +import org.apache.solr.handler.api.V2ApiUtils; import org.apache.solr.pkg.PackageAPI; import org.apache.solr.pkg.PackageListeners; import org.apache.solr.request.SolrQueryRequest; @@ -194,7 +197,8 @@ public class SolrConfigHandler extends RequestHandlerBase private void handleGET() { if (parts.size() == 1) { // this is the whole config. sent out the whole payload - resp.add("config", getConfigDetails(null, req)); + V2ApiUtils.squashIntoSolrResponseWithoutHeader( + resp, new GetConfig(req).getConfig(req.getParams().getBool("expandParams", false))); } else { if (ConfigOverlay.NAME.equals(parts.get(1))) { resp.add(ConfigOverlay.NAME, req.getCore().getSolrConfig().getOverlay()); @@ -1066,6 +1070,11 @@ public class SolrConfigHandler extends RequestHandlerBase return apis; } + @Override + public Collection<Class<? extends JerseyResource>> getJerseyResources() { + return List.of(GetConfig.class); + } + @Override public Boolean registerV2() { return Boolean.TRUE; 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 new file mode 100644 index 00000000000..d46738ea347 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.handler.admin.api; + +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.security.PermissionNameProvider.Name.CONFIG_READ_PERM; + +import jakarta.inject.Inject; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.apache.solr.api.JerseyResource; +import org.apache.solr.client.api.endpoint.ConfigApi; +import org.apache.solr.client.api.model.ConfigInfoResponse; +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.StrUtils; +import org.apache.solr.common.util.Utils; +import org.apache.solr.core.PluginInfo; +import org.apache.solr.handler.RequestHandlerBase; +import org.apache.solr.jersey.NullKeyTolerantMap; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrRequestHandler; +import org.apache.solr.util.SolrPluginUtils; + +public class GetConfig extends JerseyResource implements ConfigApi.Get { + + private final SolrQueryRequest solrQueryRequest; + + @Inject + public GetConfig(SolrQueryRequest solrQueryRequest) { + this.solrQueryRequest = solrQueryRequest; + } + + @Override + @PermissionName(CONFIG_READ_PERM) + public ConfigInfoResponse getConfig(boolean expandParams) { + final var response = instantiateJerseyResponse(ConfigInfoResponse.class); + response.config = buildConfigMap(solrQueryRequest, expandParams); + return response; + } + + @SuppressWarnings({"unchecked"}) + private Map<String, Object> buildConfigMap( + SolrQueryRequest solrQueryRequest, boolean expandParams) { + Map<String, Object> map = + Utils.convertToMap(solrQueryRequest.getCore().getSolrConfig(), new LinkedHashMap<>()); + + Map<String, Object> reqHandlers = + (Map<String, Object>) + map.computeIfAbsent(SolrRequestHandler.TYPE, k -> new LinkedHashMap<>()); + List<PluginInfo> plugins = solrQueryRequest.getCore().getImplicitHandlers(); + for (PluginInfo plugin : plugins) { + if (SolrRequestHandler.TYPE.equals(plugin.type) && !reqHandlers.containsKey(plugin.name)) { + reqHandlers.put(plugin.name, plugin); + } + } + + if (expandParams) { + for (Map.Entry<String, Object> entry : reqHandlers.entrySet()) { + entry.setValue(expandUseParams(solrQueryRequest, entry.getValue())); + } + } + + final var jsonSafeCopy = new NullKeyTolerantMap("children"); + jsonSafeCopy.putAll((Map<String, Object>) Utils.getDeepCopy(map, 20, true)); + return jsonSafeCopy; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private Map<String, Object> expandUseParams(SolrQueryRequest req, Object plugin) { + Map<String, Object> pluginInfo; + if (plugin instanceof Map) { + pluginInfo = (Map<String, Object>) plugin; + } else { + 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<String, Object> expanded = new LinkedHashMap<>(); + pluginInfo.put("_useParamsExpanded_", expanded); + List<String> params = new ArrayList<>(); + if (useParams != null) params.addAll(StrUtils.splitSmart(useParams, ',')); + if (useParamsInReq != null) params.addAll(StrUtils.splitSmart(useParamsInReq, ',')); + for (String param : params) { + var paramSet = req.getCore().getSolrConfig().getRequestParams().getParams(param); + expanded.put(param, paramSet != null ? paramSet : "[NOT AVAILABLE]"); + } + SolrQueryRequest subRequest = req.subRequest(req.getOriginalParams()); + subRequest.getContext().put(USEPARAM, useParams); + NamedList<?> initArgs = new PluginInfo(SolrRequestHandler.TYPE, pluginInfo).initArgs; + SolrPluginUtils.setDefaults( + subRequest, + RequestHandlerBase.getSolrParamsFromNamedList(initArgs, DEFAULTS), + RequestHandlerBase.getSolrParamsFromNamedList(initArgs, APPENDS), + RequestHandlerBase.getSolrParamsFromNamedList(initArgs, INVARIANTS)); + SolrParams mask = new MapSolrParams(Map.of("componentName", "", "expandParams", "")); + pluginInfo.put("_effectiveParams_", SolrParams.wrapDefaults(mask, subRequest.getParams())); + } + return pluginInfo; + } +} 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 8c8dcd25d53..4dab3ed7459 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,8 +28,8 @@ import org.apache.solr.response.SolrQueryResponse; /** * V2 APIs for retrieving some or all configuration relevant to a particular collection (or core). * - * <p>This class covers a handful of distinct paths under GET /v2/c/collectionName/config: /, - * /overlay, /params, /znodeVersion, etc. + * <p>This class covers legacy AnnotatedApi paths under GET /config: /params, /params{paramset}, + * /overlay, /znodeVersion, etc. */ public class GetConfigAPI { private final SolrConfigHandler configHandler; @@ -38,14 +38,6 @@ public class GetConfigAPI { this.configHandler = configHandler; } - @EndPoint( - path = {"/config"}, - method = GET, - permission = CONFIG_READ_PERM) - public void getAllConfig(SolrQueryRequest req, SolrQueryResponse rsp) { - configHandler.handleRequest(req, rsp); - } - @EndPoint( path = {"/config/params"}, method = GET, diff --git a/solr/core/src/java/org/apache/solr/jersey/NullKeyTolerantMap.java b/solr/core/src/java/org/apache/solr/jersey/NullKeyTolerantMap.java new file mode 100644 index 00000000000..90b87c2d625 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/jersey/NullKeyTolerantMap.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.jersey; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializable; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * A {@link Map} that renders identically to a plain {@link LinkedHashMap} everywhere except through + * Jackson. + * + * <p>{@code org.apache.solr.core.PluginInfo#writeMap} groups a plugin's unnamed children (e.g. + * anonymous {@code <processor>} entries under an {@code updateRequestProcessorChain}) under a + * {@code null} key. Solr's legacy (v1) JSON response writer tolerates that null key natively, + * rendering it as {@code ""}. Jackson, however, rejects null Map keys outright and (by default) + * omits null-valued entries entirely. + * + * <p>Wrap a config/plugin-derived map in this class to let the v2/JAX-RS (Jackson) response path + * render a self-describing key in place of the null key and keep null values, without changing what + * any non-Jackson consumer (e.g. a v1 handler reading the same map instance) sees. + */ +public class NullKeyTolerantMap extends LinkedHashMap<String, Object> implements JsonSerializable { + + private final String nullKeyReplacement; + + /** + * @param nullKeyReplacement the JSON field name to substitute for a {@code null} Map key, + * wherever one is found (at any nesting depth of Maps/Lists reachable from this instance). + */ + public NullKeyTolerantMap(String nullKeyReplacement) { + this.nullKeyReplacement = nullKeyReplacement; + } + + @Override + public void serialize(JsonGenerator gen, SerializerProvider serializers) throws IOException { + writeValue(this, gen, serializers); + } + + @Override + public void serializeWithType( + JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) + throws IOException { + serialize(gen, serializers); + } + + private void writeValue(Object value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + if (value instanceof Map<?, ?> m) { + gen.writeStartObject(); + for (Map.Entry<?, ?> e : m.entrySet()) { + Object key = e.getKey(); + gen.writeFieldName(key == null ? nullKeyReplacement : key.toString()); + writeValue(e.getValue(), gen, provider); + } + gen.writeEndObject(); + } else if (value instanceof List<?> l) { + gen.writeStartArray(); + for (Object item : l) { + writeValue(item, gen, provider); + } + gen.writeEndArray(); + } else if (value == null) { + gen.writeNull(); + } else { + provider.defaultSerializeValue(value, gen); + } + } +} 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 3912e52d281..0a5aaab1f88 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,12 +44,6 @@ public class V2ConfigAPIMappingTest extends V2ApiMappingTest<SolrConfigHandler> apiBag.registerObject(new ModifyParamSetAPI(getRequestHandler())); } - // GET /v2/c/collectionName/config is a pure pass-through to the underlying request handler - @Test - public void testGetAllConfig() { - assertAnnotatedApiExistsFor("GET", "/config"); - } - // GET /v2/collectionName/config/<component> is a pure pass-through to the underlying request // handler. Just check // the API lookup works for a handful of the valid config "components". 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 new file mode 100644 index 00000000000..297da96353d --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.handler.admin.api; + +import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; + +import org.apache.solr.client.api.model.IndexType; +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; + +/** HTTP tests for fetching the full config via the collection-scoped v2 path. */ +public class GetConfigSolrCloudTest extends SolrCloudTestCase { + + private static final String COLLECTION_NAME = "configApiTestCollection"; + + @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(); + CollectionAdminRequest.createCollection(COLLECTION_NAME, "conf", 1, 1) + .process(cluster.getSolrClient()); + } + + @Test + public void testGetConfigFromCore() throws Exception { + var request = new ConfigApi.GetConfig(IndexType.COLLECTION, COLLECTION_NAME); + var response = request.process(cluster.getSolrClient()); + + assertNotNull(response); + assertNull(response.error); + assertNotNull(response.config); + assertTrue(response.config.containsKey("luceneMatchVersion")); + assertTrue(response.config.containsKey("updateHandler")); + assertTrue(response.config.containsKey("query")); + assertTrue(response.config.containsKey("requestHandler")); + } +} 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 new file mode 100644 index 00000000000..c122e8d497a --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.handler.admin.api; + +import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; + +import org.apache.solr.SolrTestCase; +import org.apache.solr.client.api.model.IndexType; +import org.apache.solr.client.solrj.request.ConfigApi; +import org.apache.solr.util.ExternalPaths; +import org.apache.solr.util.SolrJettyTestRule; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +/** HTTP tests for fetching the full config through the generated SolrJ request. */ +public class GetConfigTest extends SolrTestCase { + + private static final String CORE_NAME = "configApiTestCore"; + + @ClassRule public static final SolrJettyTestRule solrTestRule = new SolrJettyTestRule(); + + @BeforeClass + public static void setupSolr() throws Exception { + System.setProperty(ALLOW_PATHS_SYSPROP, ExternalPaths.SERVER_HOME.toAbsolutePath().toString()); + solrTestRule.startSolr(createTempDir()); + solrTestRule.newCollection(CORE_NAME).withConfigSet(ExternalPaths.DEFAULT_CONFIGSET).create(); + } + + @Test + public void testGetConfigFromCore() throws Exception { + var request = new ConfigApi.GetConfig(IndexType.CORE, CORE_NAME); + var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); + + assertNotNull(response); + assertNull(response.error); + assertNotNull(response.config); + assertTrue(response.config.containsKey("luceneMatchVersion")); + assertTrue(response.config.containsKey("updateHandler")); + assertTrue(response.config.containsKey("query")); + assertTrue(response.config.containsKey("requestHandler")); + assertTrue(response.config.containsKey("searchComponent")); + assertTrue(response.config.containsKey("updateProcessor")); + } +} 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 93e94a69045..878e8dd0d6a 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 @@ -49,6 +49,21 @@ V1 API:: ---- http://localhost:8983/solr/techproducts/config ---- + +The response is the Solr configuration resulting from merging settings in `configoverlay.json` with those in `solrconfig.xml`. +A plugin's *unnamed* child elements - for example, the anonymous `<processor>` entries inside an `updateRequestProcessorChain` that has no `name` attribute of its own - are grouped under an empty-string (`""`) key, matching the legacy behavior of `solrconfig.xml -> JSON` conversion: + +[source,json] +---- +"updateRequestProcessorChain": [{ + "name": "add-unknown-fields-to-the-schema", + "": [ + {"class": "solr.LogUpdateProcessorFactory"}, + {"class": "solr.DistributedUpdateProcessorFactory"}, + {"class": "solr.RunUpdateProcessorFactory"} + ] +}] +---- ==== V2 API:: @@ -58,11 +73,22 @@ V2 API:: ---- http://localhost:8983/api/collections/techproducts/config ---- -==== -====== -The response will be the Solr configuration resulting from merging settings in `configoverlay.json` with those in `solrconfig.xml`. +The response is the same merged configuration as the V1 API, with one difference: a plugin's unnamed child elements are grouped under a self-describing `children` key instead of an empty string, since Solr's V2 JSON serialization library does not allow empty-string keys: +[source,json] +---- +"updateRequestProcessorChain": [{ + "name": "add-unknown-fields-to-the-schema", + "children": [ + {"class": "solr.LogUpdateProcessorFactory"}, + {"class": "solr.DistributedUpdateProcessorFactory"}, + {"class": "solr.RunUpdateProcessorFactory"} + ] +}] +---- +==== +====== It's possible to restrict the returned configuration to a top-level section, such as, `query`, `requestHandler` or `updateHandler`. To do this, append the name of the section to the `config` endpoint. diff --git a/solr/webapp/web/js/angular/controllers/schema.js b/solr/webapp/web/js/angular/controllers/schema.js index d70f60d538e..0e1741bab2b 100644 --- a/solr/webapp/web/js/angular/controllers/schema.js +++ b/solr/webapp/web/js/angular/controllers/schema.js @@ -49,7 +49,7 @@ function schemaApiErrorMessage(data, response) { } solrAdminApp.controller('SchemaController', - function($scope, $routeParams, $location, $cookies, $timeout, Luke, Constants, SchemaV2, Config, ApiErrorHandler) { + function($scope, $routeParams, $location, $cookies, $timeout, Luke, Constants, SchemaV2, ConfigV2, ApiErrorHandler) { $scope.resetMenu("schema", Constants.IS_COLLECTION_PAGE); $scope.refresh = function () { @@ -117,8 +117,16 @@ solrAdminApp.controller('SchemaController', $scope.types = Object.keys(schema.schema.types); }); }); - Config.get({core: $routeParams.core}, function(data) { - $scope.isSchemaUpdatable = (data.config.hasOwnProperty('schemaFactory') == false || data.config.schemaFactory.class == "ManagedIndexSchemaFactory"); + var configIndexType = $scope.isCloudEnabled ? "collections" : "cores"; + ConfigV2.getConfig(configIndexType, $routeParams.core, {}, function(error, data, response) { + $timeout(function() { + if (error || (data && data.error)) { + ApiErrorHandler.handle(response); + return; + } + var schemaFactory = data.config && data.config.schemaFactory; + $scope.isSchemaUpdatable = !schemaFactory || schemaFactory.class == "ManagedIndexSchemaFactory"; + }); }); }; $scope.refresh(); diff --git a/solr/webapp/web/js/angular/services.js b/solr/webapp/web/js/angular/services.js index e6fa6b37cfa..c0b525b1698 100644 --- a/solr/webapp/web/js/angular/services.js +++ b/solr/webapp/web/js/angular/services.js @@ -160,6 +160,12 @@ solrAdminServices.factory('Metrics', delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"]; return new solrApi.SchemaApi(); }) +.factory('ConfigV2', + function() { + solrApi.ApiClient.instance.basePath = '/api'; + delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"]; + return new solrApi.ConfigApi(); + }) .factory('Collections', ['$resource', function ($resource) { // v2 ClusterAPI (/api/cluster) delegates straight through to the same v1 CollectionsHandler @@ -381,12 +387,6 @@ solrAdminServices.factory('Metrics', } return resource; }]) -.factory('Config', - ['$resource', function($resource) { - return $resource(':core/config', {wt: 'json', core: '@core', _:Date.now()}, { - get: {method: "GET"} - }) -}]) .factory('SchemaDesigner', ['$resource', function($resource) { // Schema Designer's analyze (sample-doc upload/paste, dynamic content-type) and query
