This is an automated email from the ASF dual-hosted git repository.
epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new 14c7aac0d15 SOLR-16396 | Migration of Get Config V2 API to JAX-RS
construct (#4951)
14c7aac0d15 is described below
commit 14c7aac0d151402b00259e2fb9bf5eed7049ec5d
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]>
---
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 8655774fc76..d943272657f 100644
--- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
@@ -53,6 +53,7 @@ import java.util.concurrent.locks.ReentrantLock;
import org.apache.solr.api.AnnotatedApi;
import org.apache.solr.api.Api;
import org.apache.solr.api.ApiBag;
+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;
@@ -86,9 +87,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;
@@ -195,7 +198,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 e4681b0e4c4..f01f882a0d0 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 6e10997f743..2f635c17f1e 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
@@ -379,12 +385,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