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 69444fcbb9b SOLR-16396: Migrate GET paramsets v2 API to JAX-RS. (#5218)
69444fcbb9b is described below
commit 69444fcbb9ba33d98c919b5f3c69316f9879ee17
Author: Jalaz Kumar <[email protected]>
AuthorDate: Fri Oct 30 04:07:18 2026 +0530
SOLR-16396: Migrate GET paramsets v2 API to JAX-RS. (#5218)
Co-authored-by: Eric Pugh <[email protected]>
Co-authored-by: Claude Sonnet 5 <[email protected]>
---
.../unreleased/SOLR-16396-config-get-params.yml | 14 +++
.../solr/client/api/endpoint/ParamSetsApi.java | 50 ++++++++++
.../solr/client/api/model/ParamSetsResponse.java | 29 ++++++
.../solr/cli/packagemanager/PackageManager.java | 56 +++++++----
.../solr/cli/packagemanager/PackageUtils.java | 6 ++
.../org/apache/solr/handler/SolrConfigHandler.java | 32 ++++---
.../solr/handler/admin/api/GetConfigAPI.java | 56 -----------
.../solr/handler/admin/api/GetParamSets.java | 76 +++++++++++++++
.../apache/solr/core/TestSolrConfigHandler.java | 40 ++++++--
.../solr/handler/admin/V2ConfigAPIMappingTest.java | 21 -----
.../solr/handler/admin/api/GetParamSetsTest.java | 105 +++++++++++++++++++++
.../configuration-guide/pages/config-api.adoc | 3 +-
.../pages/request-parameters-api.adoc | 27 +++++-
.../solr/webapp/AdminUiParamsetsScreenTest.java | 2 +
.../webapp/web/js/angular/controllers/paramsets.js | 77 ++++++---------
solr/webapp/web/js/angular/controllers/query.js | 30 ++----
solr/webapp/web/js/angular/services.js | 17 ++--
17 files changed, 448 insertions(+), 193 deletions(-)
diff --git a/changelog/unreleased/SOLR-16396-config-get-params.yml
b/changelog/unreleased/SOLR-16396-config-get-params.yml
new file mode 100644
index 00000000000..ec229e1b94b
--- /dev/null
+++ b/changelog/unreleased/SOLR-16396-config-get-params.yml
@@ -0,0 +1,14 @@
+title:
+ Migrate GET paramsets v2 API to JAX-RS. v2 GET /config/params is now GET
/config/paramsets, and
+ returns a 404 for an unknown paramset name; v1's GET /config/params keeps
its existing 200
+ response shape. The Admin UI's paramsets and query screens now read
paramsets through the
+ generated v2 client too.
+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/ParamSetsApi.java
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ParamSetsApi.java
new file mode 100644
index 00000000000..04efd8c17a0
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ParamSetsApi.java
@@ -0,0 +1,50 @@
+/*
+ * 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.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import org.apache.solr.client.api.model.ParamSetsResponse;
+import org.apache.solr.client.api.util.StoreApiParameters;
+
+public interface ParamSetsApi {
+
+ @Path(INDEX_PATH_PREFIX + "/config/paramsets")
+ interface Get {
+ @GET
+ @StoreApiParameters
+ @Operation(
+ summary = "Fetch all request paramsets",
+ tags = {"param-sets"})
+ ParamSetsResponse listParamSets();
+
+ @GET
+ @Path("/{paramSetName}")
+ @StoreApiParameters
+ @Operation(
+ summary = "Fetch a single request paramset",
+ tags = {"param-sets"})
+ ParamSetsResponse getParamSet(
+ @PathParam("paramSetName") @Parameter(description = "Name of the
paramset")
+ String paramSetName);
+ }
+}
diff --git
a/solr/api/src/java/org/apache/solr/client/api/model/ParamSetsResponse.java
b/solr/api/src/java/org/apache/solr/client/api/model/ParamSetsResponse.java
new file mode 100644
index 00000000000..a3aab5f004d
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/ParamSetsResponse.java
@@ -0,0 +1,29 @@
+/*
+ * 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 ParamSetsResponse extends SolrJerseyResponse {
+
+ @JsonProperty("znodeVersion")
+ public int znodeVersion;
+
+ @JsonProperty("params")
+ public Map<String, Object> params;
+}
diff --git
a/solr/core/src/java/org/apache/solr/cli/packagemanager/PackageManager.java
b/solr/core/src/java/org/apache/solr/cli/packagemanager/PackageManager.java
index 7db659bbca1..ee1b5fc0ed7 100644
--- a/solr/core/src/java/org/apache/solr/cli/packagemanager/PackageManager.java
+++ b/solr/core/src/java/org/apache/solr/cli/packagemanager/PackageManager.java
@@ -244,15 +244,20 @@ public class PackageManager implements Closeable {
solrClient.request(
new GenericV2SolrRequest(
SolrRequest.METHOD.GET,
- PackageUtils.getCollectionParamsPath(collection) +
"/PKG_VERSIONS")
+ PackageUtils.getCollectionParamSetsPath(collection) +
"/PKG_VERSIONS")
.setRequiresCollection(
false) /* Making a collection request, but already baked
into path */);
packages =
(Map<String, String>)
-
Objects.requireNonNullElse(result._get("/response/params/PKG_VERSIONS"),
Map.of());
+ Objects.requireNonNullElse(result._get("/params/PKG_VERSIONS"),
Map.of());
} catch (PathNotFoundException ex) {
// Don't worry if PKG_VERSION wasn't found. It just means this
collection was never touched by
// the package manager.
+ } catch (RemoteSolrException ex) {
+ if (ex.code() != 404) {
+ throw new SolrException(ErrorCode.SERVER_ERROR, ex);
+ }
+ // No PKG_VERSIONS paramset defined yet for this collection.
} catch (SolrServerException | IOException ex) {
throw new SolrException(ErrorCode.SERVER_ERROR, ex);
}
@@ -421,15 +426,26 @@ public class PackageManager implements Closeable {
getCollectionParameterOverrides(packageInstance, isUpdate,
overrides, collection);
// Get package params
+ boolean packageParamsExist;
try {
NamedList<Object> collectionParams =
solrClient.request(
new GenericV2SolrRequest(
SolrRequest.METHOD.GET,
- PackageUtils.getCollectionParamsPath(collection) +
"/packages")
+ PackageUtils.getCollectionParamSetsPath(collection) +
"/packages")
.setRequiresCollection(
false) /* Making a collection-request, but already
baked into path */);
- boolean packageParamsExist = collectionParams.get("params") != null;
+ packageParamsExist = collectionParams.get("params") != null;
+ } catch (RemoteSolrException ex) {
+ if (ex.code() != 404) {
+ throw new SolrException(ErrorCode.SERVER_ERROR, ex);
+ }
+ // No "packages" paramset defined yet for this collection.
+ packageParamsExist = false;
+ } catch (SolrServerException | IOException ex) {
+ throw new SolrException(ErrorCode.SERVER_ERROR, ex);
+ }
+ try {
SolrCLI.postJsonToSolr(
solrClient,
PackageUtils.getCollectionParamsPath(collection),
@@ -728,12 +744,11 @@ public class PackageManager implements Closeable {
solrClient.request(
new GenericV2SolrRequest(
SolrRequest.METHOD.GET,
- PackageUtils.getCollectionParamsPath(collection) +
"/packages")
+ PackageUtils.getCollectionParamSetsPath(collection) +
"/packages")
.setRequiresCollection(
false) /* Making a collection-request, but already baked
into path */);
return (Map<String, String>)
- Objects.requireNonNullElse(
- response._get("/response/params/packages/" + packageName),
Map.of());
+ Objects.requireNonNullElse(response._get("/params/packages/" +
packageName), Map.of());
} catch (Exception ex) {
// This should be because there are no parameters. Be tolerant here.
@@ -1103,19 +1118,24 @@ public class PackageManager implements Closeable {
Map<String, String> deployed = new HashMap<>();
for (String collection : allCollections) {
// Check package version installed
- String paramsJson =
- PackageUtils.getJsonStringFromCollectionApi(
- solrClient,
- PackageUtils.getCollectionParamsPath(collection) +
"/PKG_VERSIONS",
- new ModifiableSolrParams().add("omitHeader", "true"));
String version = null;
try {
- version =
- jsonPathRead(
- paramsJson, "$['response'].['params'].['PKG_VERSIONS'].['" +
packageName + "']");
- } catch (PathNotFoundException ex) {
- // Don't worry if PKG_VERSION wasn't found. It just means this
collection was never touched
- // by the package manager.
+ NamedList<Object> response =
+ solrClient.request(
+ new GenericV2SolrRequest(
+ SolrRequest.METHOD.GET,
+ PackageUtils.getCollectionParamSetsPath(collection) +
"/PKG_VERSIONS")
+ .setRequiresCollection(
+ false) /* Making a collection-request, but already
baked into path */);
+ version = (String) response._get("/params/PKG_VERSIONS/" +
packageName);
+ } catch (RemoteSolrException ex) {
+ if (ex.code() != 404) {
+ throw new SolrException(ErrorCode.SERVER_ERROR, ex);
+ }
+ // No PKG_VERSIONS paramset defined yet. It just means this collection
was never touched by
+ // the package manager.
+ } catch (SolrServerException | IOException ex) {
+ throw new SolrException(ErrorCode.SERVER_ERROR, ex);
}
if (version != null) {
deployed.put(collection, version);
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 72d76ddf844..2cab7b99a18 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
@@ -275,10 +275,16 @@ public class PackageUtils {
return collections;
}
+ /** Path for setting/unsetting request param sets (POST), per {@code
ModifyParamSetAPI}. */
public static String getCollectionParamsPath(String collection) {
return "/api/collections/" + collection + "/config/params";
}
+ /** Path for reading request param sets (GET), per {@code ParamSetsApi.Get}.
*/
+ public static String getCollectionParamSetsPath(String collection) {
+ return "/api/collections/" + collection + "/config/paramsets";
+ }
+
public static void uploadKey(byte[] bytes, String path, Path home) throws
IOException {
FileStoreAPI.MetaData meta = ClusterFileStore._createJsonMetaData(bytes,
null);
DistribFileStore._persistToFile(
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 d44e7fe9c53..72c1eefe665 100644
--- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
@@ -35,7 +35,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -72,6 +71,7 @@ import org.apache.solr.common.util.CommandOperation;
import org.apache.solr.common.util.EnvUtils;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.SolrNamedThreadFactory;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
@@ -82,7 +82,7 @@ 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.GetParamSets;
import org.apache.solr.handler.admin.api.ModifyConfigComponentAPI;
import org.apache.solr.handler.admin.api.ModifyParamSetAPI;
import org.apache.solr.handler.api.V2ApiUtils;
@@ -197,19 +197,26 @@ public class SolrConfigHandler extends RequestHandlerBase
if (ConfigOverlay.NAME.equals(parts.get(1))) {
V2ApiUtils.squashIntoSolrResponseWithoutHeader(resp, new
GetConfig(req).getOverlay());
} else if (RequestParams.NAME.equals(parts.get(1))) {
+ NamedList<Object> paramSets = new SimpleOrderedMap<>();
if (parts.size() == 3) {
- RequestParams params =
req.getCore().getSolrConfig().getRequestParams();
- RequestParams.ParamSet p = params.getParams(parts.get(2));
- Map<String, Object> m = new LinkedHashMap<>();
- m.put(ZNODEVER, params.getZnodeVersion());
- if (p != null) {
- m.put(RequestParams.NAME, Map.of(parts.get(2), p));
+ try {
+ V2ApiUtils.squashIntoNamedListWithoutHeader(
+ paramSets, new GetParamSets(req).getParamSet(parts.get(2)));
+ } catch (SolrException e) {
+ if (e.code() != SolrException.ErrorCode.NOT_FOUND.code) {
+ throw e;
+ }
+ // v1 has always returned 200 here with no "params" entry for an
unknown paramset
+ // name, rather than an error - preserve that for existing v1
callers. v2's
+ // GetParamSets keeps its own (more correct) 404 for its own
callers.
+ paramSets.add(
+ ZNODEVER,
req.getCore().getSolrConfig().getRequestParams().getZnodeVersion());
}
- resp.add(SolrQueryResponse.NAME, m);
} else {
- resp.add(SolrQueryResponse.NAME,
req.getCore().getSolrConfig().getRequestParams());
+ V2ApiUtils.squashIntoNamedListWithoutHeader(
+ paramSets, new GetParamSets(req).listParamSets());
}
-
+ resp.add(SolrQueryResponse.NAME, paramSets);
} else {
if (ZNODEVER.equals(parts.get(1))) {
V2ApiUtils.squashIntoSolrResponseWithoutHeader(
@@ -950,7 +957,6 @@ public class SolrConfigHandler extends RequestHandlerBase
@Override
public Collection<Api> getApis() {
final List<Api> apis = new ArrayList<>();
- apis.addAll(AnnotatedApi.getApis(new GetConfigAPI(this)));
apis.addAll(AnnotatedApi.getApis(new ModifyConfigComponentAPI(this)));
apis.addAll(AnnotatedApi.getApis(new ModifyParamSetAPI(this)));
return apis;
@@ -958,7 +964,7 @@ public class SolrConfigHandler extends RequestHandlerBase
@Override
public Collection<Class<? extends JerseyResource>> getJerseyResources() {
- return List.of(GetConfig.class);
+ return List.of(GetConfig.class, GetParamSets.class);
}
@Override
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
deleted file mode 100644
index 110974eec68..00000000000
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfigAPI.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.client.solrj.SolrRequest.METHOD.GET;
-import static
org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM;
-
-import org.apache.solr.api.EndPoint;
-import org.apache.solr.handler.SolrConfigHandler;
-import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.response.SolrQueryResponse;
-
-/**
- * V2 APIs for retrieving some or all configuration relevant to a particular
collection (or core).
- *
- * <p>This class only covers legacy AnnotatedApi paths under GET /config:
/params and
- * /params{paramset}
- */
-public class GetConfigAPI {
- private final SolrConfigHandler configHandler;
-
- public GetConfigAPI(SolrConfigHandler configHandler) {
- this.configHandler = configHandler;
- }
-
- @EndPoint(
- path = {"/config/params"},
- method = GET,
- permission = CONFIG_READ_PERM)
- public void getAllParamSets(SolrQueryRequest req, SolrQueryResponse rsp) {
- configHandler.handleRequest(req, rsp);
- }
-
- @EndPoint(
- path = {"/config/params/{paramset}"},
- method = GET,
- permission = CONFIG_READ_PERM)
- public void getSingleParamSet(SolrQueryRequest req, SolrQueryResponse rsp) {
- configHandler.handleRequest(req, rsp);
- }
-}
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/api/GetParamSets.java
b/solr/core/src/java/org/apache/solr/handler/admin/api/GetParamSets.java
new file mode 100644
index 00000000000..a4ed0924ac0
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetParamSets.java
@@ -0,0 +1,76 @@
+/*
+ * 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.security.PermissionNameProvider.Name.CONFIG_READ_PERM;
+
+import jakarta.inject.Inject;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.endpoint.ParamSetsApi;
+import org.apache.solr.client.api.model.ParamSetsResponse;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.RequestParams;
+import org.apache.solr.jersey.PermissionName;
+import org.apache.solr.request.SolrQueryRequest;
+
+public class GetParamSets extends JerseyResource implements ParamSetsApi.Get {
+
+ private final SolrQueryRequest solrQueryRequest;
+
+ @Inject
+ public GetParamSets(SolrQueryRequest solrQueryRequest) {
+ this.solrQueryRequest = solrQueryRequest;
+ }
+
+ @Override
+ @PermissionName(CONFIG_READ_PERM)
+ public ParamSetsResponse listParamSets() {
+ return buildResponse(null);
+ }
+
+ @Override
+ @PermissionName(CONFIG_READ_PERM)
+ public ParamSetsResponse getParamSet(String paramSetName) {
+ return buildResponse(paramSetName);
+ }
+
+ @SuppressWarnings("unchecked")
+ private ParamSetsResponse buildResponse(String paramSetName) {
+ final var response = instantiateJerseyResponse(ParamSetsResponse.class);
+ final RequestParams requestParams =
+ solrQueryRequest.getCore().getSolrConfig().getRequestParams();
+ response.znodeVersion = requestParams.getZnodeVersion();
+
+ if (paramSetName == null) {
+ Map<String, Object> paramsMap = Utils.convertToMap(requestParams, new
LinkedHashMap<>());
+ response.params = (Map<String, Object>)
paramsMap.get(RequestParams.NAME);
+ } else {
+ RequestParams.ParamSet paramSet = requestParams.getParams(paramSetName);
+ if (paramSet == null) {
+ throw new SolrException(
+ SolrException.ErrorCode.NOT_FOUND, "No paramset found with the
name: " + paramSetName);
+ }
+ Map<String, Object> paramsMap = new LinkedHashMap<>();
+ paramsMap.put(paramSetName, Utils.convertToMap(paramSet, new
LinkedHashMap<>()));
+ response.params = paramsMap;
+ }
+ return response;
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
index f00d7fd4cba..525eb36d673 100644
--- a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
+++ b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
@@ -26,6 +26,7 @@ import java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -108,6 +109,15 @@ public class TestSolrConfigHandler extends RestTestBase {
return harness.getBaseURL().contains("/____v2/") ? externalName :
internalName;
}
+ // The v2 paramset response is flat, while v1 nests it under "response"
+ private static List<String> paramSetsPath(RestTestHarness harness, String...
keys) {
+ List<String> path = new ArrayList<>();
+ if (!harness.getBaseURL().contains("/____v2/")) path.add("response");
+ path.add("params");
+ path.addAll(asList(keys));
+ return path;
+ }
+
public void testProperty() throws Exception {
RestTestHarness harness = restTestHarness;
String requestHandlerKey = fullConfigKey(harness, "requestHandler",
"requestHandlers");
@@ -646,6 +656,8 @@ public class TestSolrConfigHandler extends RestTestBase {
public void testReqParams() throws Exception {
RestTestHarness harness = restTestHarness;
+ String paramSetsKey = fullConfigKey(harness, "params", "paramsets");
+
String payload =
" {\n"
+ " 'set' : {'x': {"
@@ -657,10 +669,20 @@ public class TestSolrConfigHandler extends RestTestBase {
TestSolrConfigHandler.runConfigCommand(harness, "/config/params", payload);
TestSolrConfigHandler.testForResponseElement(
- harness, "/config/params", asList("response", "params", "x", "a"), "A
val", TIMEOUT_S);
+ harness, "/config/" + paramSetsKey, paramSetsPath(harness, "x", "a"),
"A val", TIMEOUT_S);
TestSolrConfigHandler.testForResponseElement(
- harness, "/config/params", asList("response", "params", "x", "b"), "B
val", TIMEOUT_S);
+ harness, "/config/" + paramSetsKey, paramSetsPath(harness, "x", "b"),
"B val", TIMEOUT_S);
+
+ // v2 404s for an unknown paramset name; v1's long-standing behavior (a
200 with no matching
+ // entry) is preserved for existing v1 callers.
+ String missing = harness.query("/config/" + paramSetsKey +
"/nonExistentParamSet");
+ if (harness.getBaseURL().contains("/____v2/")) {
+ assertTrue(missing, missing.contains("No paramset found with the name:
nonExistentParamSet"));
+ } else {
+ assertFalse(missing, missing.contains("nonExistentParamSet"));
+ assertTrue(missing, missing.contains("znodeVersion"));
+ }
payload =
"{\n"
@@ -710,7 +732,7 @@ public class TestSolrConfigHandler extends RestTestBase {
TestSolrConfigHandler.runConfigCommand(harness, "/config/params", payload);
TestSolrConfigHandler.testForResponseElement(
- harness, "/config/params", asList("response", "params", "y", "c"), "CY
val", TIMEOUT_S);
+ harness, "/config/" + paramSetsKey, paramSetsPath(harness, "y", "c"),
"CY val", TIMEOUT_S);
TestSolrConfigHandler.testForResponseElement(
harness, "/dump1?useParams=y", asList("params", "c"), "CY val",
TIMEOUT_S);
@@ -738,13 +760,13 @@ public class TestSolrConfigHandler extends RestTestBase {
TestSolrConfigHandler.testForResponseElement(
harness,
- "/config/params",
- asList("response", "params", "y", "c"),
+ "/config/" + paramSetsKey,
+ paramSetsPath(harness, "y", "c"),
"CY val modified",
TIMEOUT_S);
TestSolrConfigHandler.testForResponseElement(
- harness, "/config/params", asList("response", "params", "y", "e"), "EY
val", TIMEOUT_S);
+ harness, "/config/" + paramSetsKey, paramSetsPath(harness, "y", "e"),
"EY val", TIMEOUT_S);
payload =
" {\n"
@@ -757,14 +779,14 @@ public class TestSolrConfigHandler extends RestTestBase {
TestSolrConfigHandler.runConfigCommand(harness, "/config/params", payload);
TestSolrConfigHandler.testForResponseElement(
- harness, "/config/params", asList("response", "params", "y", "p"), "P
val", TIMEOUT_S);
+ harness, "/config/" + paramSetsKey, paramSetsPath(harness, "y", "p"),
"P val", TIMEOUT_S);
TestSolrConfigHandler.testForResponseElement(
- harness, "/config/params", asList("response", "params", "y", "c"),
null, TIMEOUT_S);
+ harness, "/config/" + paramSetsKey, paramSetsPath(harness, "y", "c"),
null, TIMEOUT_S);
payload = " {'delete' : 'y'}";
TestSolrConfigHandler.runConfigCommand(harness, "/config/params", payload);
TestSolrConfigHandler.testForResponseElement(
- harness, "/config/params", asList("response", "params", "y", "p"),
null, TIMEOUT_S);
+ harness, "/config/" + paramSetsKey, paramSetsPath(harness, "y", "p"),
null, TIMEOUT_S);
payload =
"{\n"
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 a671cfce9f2..6efac088d30 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
@@ -17,9 +17,7 @@
package org.apache.solr.handler.admin;
-import org.apache.solr.api.AnnotatedApi;
import org.apache.solr.handler.SolrConfigHandler;
-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.junit.Test;
@@ -39,29 +37,10 @@ public class V2ConfigAPIMappingTest extends
V2ApiMappingTest<SolrConfigHandler>
@Override
public void populateApiBag() {
- apiBag.registerObject(new GetConfigAPI(getRequestHandler()));
apiBag.registerObject(new ModifyConfigComponentAPI(getRequestHandler()));
apiBag.registerObject(new ModifyParamSetAPI(getRequestHandler()));
}
- @Test
- public void testGetParamsetsConfig() {
- assertAnnotatedApiExistsFor("GET", "/config/params");
- final AnnotatedApi getParamSetsApi = getAnnotatedApiFor("GET",
"/config/params");
- // Ensure that the /config/params path redirects to the /config/params
specific endpoint (and
- // not the generic "/config/{component}")
- final String[] getParamSetsApiPaths = getParamSetsApi.getEndPoint().path();
- assertEquals(1, getParamSetsApiPaths.length);
- assertEquals("/config/params", getParamSetsApiPaths[0]);
-
- assertAnnotatedApiExistsFor("GET", "/config/params/someParamSet");
- final AnnotatedApi getSingleParamSetApi =
- getAnnotatedApiFor("GET", "/config/params/someParamSet");
- final String[] getSingleParamsetApiPaths =
getSingleParamSetApi.getEndPoint().path();
- assertEquals(1, getSingleParamsetApiPaths.length);
- assertEquals("/config/params/{paramset}", getSingleParamsetApiPaths[0]);
- }
-
@Test
public void testModifyConfigComponentApis() {
assertAnnotatedApiExistsFor("POST", "config");
diff --git
a/solr/core/src/test/org/apache/solr/handler/admin/api/GetParamSetsTest.java
b/solr/core/src/test/org/apache/solr/handler/admin/api/GetParamSetsTest.java
new file mode 100644
index 00000000000..a74bcd9eb6f
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetParamSetsTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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 java.nio.file.Path;
+import java.util.Map;
+import org.apache.commons.io.file.PathUtils;
+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.SolrRequest;
+import org.apache.solr.client.solrj.request.ParamSetsApi;
+import org.apache.solr.client.solrj.request.V2Request;
+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 request param sets through the generated SolrJ
request. */
+public class GetParamSetsTest extends SolrTestCase {
+
+ private static final String CORE_NAME = "paramSetsApiTestCore";
+
+ @ClassRule public static final SolrJettyTestRule solrTestRule = new
SolrJettyTestRule();
+
+ @BeforeClass
+ public static void setupSolr() throws Exception {
+ // setting param sets writes params.json into the configset, so use a
private copy
+ Path configSet = createTempDir("configSet");
+ System.setProperty(ALLOW_PATHS_SYSPROP,
configSet.toAbsolutePath().toString());
+ solrTestRule.startSolr(createTempDir());
+ PathUtils.copyDirectory(ExternalPaths.DEFAULT_CONFIGSET, configSet);
+ solrTestRule.newCollection(CORE_NAME).withConfigSet(configSet).create();
+ }
+
+ private static void setParamSet(String name, String payloadBody) throws
Exception {
+ new V2Request.Builder("/cores/" + CORE_NAME + "/config/params")
+ .withMethod(SolrRequest.METHOD.POST)
+ .withPayload("{'set': {'" + name + "': " + payloadBody + "}}")
+ .build()
+ .process(solrTestRule.getSolrClient(CORE_NAME));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testListParamSets() throws Exception {
+ setParamSet("listA", "{'a': 'A val'}");
+ setParamSet("listB", "{'b': 'B val'}");
+
+ var response =
+ new ParamSetsApi.ListParamSets(IndexType.CORE, CORE_NAME)
+ .process(solrTestRule.getSolrClient(CORE_NAME));
+
+ assertNotNull(response);
+ assertNull(response.error);
+ assertTrue(response.znodeVersion >= 0);
+ assertNotNull(response.params);
+ assertEquals("A val", ((Map<String, Object>)
response.params.get("listA")).get("a"));
+ assertEquals("B val", ((Map<String, Object>)
response.params.get("listB")).get("b"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testGetSingleParamSet() throws Exception {
+ setParamSet("single", "{'a': 'A val', '_invariants_': {'wt': 'json'}}");
+
+ var response =
+ new ParamSetsApi.GetParamSet(IndexType.CORE, CORE_NAME, "single")
+ .process(solrTestRule.getSolrClient(CORE_NAME));
+
+ assertNull(response.error);
+ assertEquals(1, response.params.size());
+ Map<String, Object> paramSet = (Map<String, Object>)
response.params.get("single");
+ assertEquals("A val", paramSet.get("a"));
+ assertEquals(Map.of("wt", "json"), paramSet.get("_invariants_"));
+ }
+
+ @Test
+ public void testGetUnknownParamSetReturnsNotFound() {
+ var request = new ParamSetsApi.GetParamSet(IndexType.CORE, CORE_NAME,
"nonExistentParamSet");
+ final RemoteSolrException ex =
+ expectThrows(
+ RemoteSolrException.class,
+ () -> request.process(solrTestRule.getSolrClient(CORE_NAME)));
+ assertEquals(404, ex.code());
+ assertTrue(ex.getMessage(),
ex.getMessage().contains("nonExistentParamSet"));
+ }
+}
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 c045ba30725..67b9557abfd 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
@@ -34,7 +34,8 @@ All Config API endpoints are collection-specific, meaning
this API can inspect o
Use GET to retrieve and POST for executing commands.
* `_collection_/config/overlay`: retrieve the details in the
`configoverlay.json` only, removing any options defined in `solrconfig.xml`
directly or implicitly through defaults.
* `_collection_/config/params`: create parameter sets that can override or
take the place of parameters defined in `solrconfig.xml`.
-See xref:request-parameters-api.adoc[] for more information about this
endpoint.
+In the V2 API, use `_collection_/config/paramsets` to read them.
+See xref:request-parameters-api.adoc[] for more information about these
endpoints.
== Retrieving the Config
diff --git
a/solr/solr-ref-guide/modules/configuration-guide/pages/request-parameters-api.adoc
b/solr/solr-ref-guide/modules/configuration-guide/pages/request-parameters-api.adoc
index cc1af781d49..fc5820a18ff 100644
---
a/solr/solr-ref-guide/modules/configuration-guide/pages/request-parameters-api.adoc
+++
b/solr/solr-ref-guide/modules/configuration-guide/pages/request-parameters-api.adoc
@@ -159,8 +159,13 @@ curl
'http://localhost:8983/solr/techproducts/config/requestHandler?componentNam
== Viewing Request Parameters
-To see the paramsets that have been created, you can use the `/config/params`
endpoint to read the contents of `params.json`, or use the name in the request:
+To see the paramsets that have been created, we can read the contents of
`params.json`, or request a single paramset by name:
+[tabs#viewparamsets]
+======
+V1 API::
++
+====
[source,bash]
----
curl 'http://localhost:8983/solr/techproducts/config/params'
@@ -168,6 +173,26 @@ curl
'http://localhost:8983/solr/techproducts/config/params'
#Or use the paramset name
curl 'http://localhost:8983/solr/techproducts/config/params/myQueries'
----
+====
+
+V2 API::
++
+====
+[source,bash]
+----
+curl http://localhost:8983/api/collections/techproducts/config/paramsets
+
+#Or use the paramset name
+curl
http://localhost:8983/api/collections/techproducts/config/paramsets/myQueries
+----
+
+Creating, updating and deleting paramsets still uses `POST
api/collections/techproducts/config/params`.
+
+====
+
+Both APIs return a `404` if the named paramset does not exist.
+
+======
== The useParams Parameter
diff --git
a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiParamsetsScreenTest.java
b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiParamsetsScreenTest.java
index a5f3bba025c..cbaf9871766 100644
---
a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiParamsetsScreenTest.java
+++
b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiParamsetsScreenTest.java
@@ -62,6 +62,8 @@ public class AdminUiParamsetsScreenTest extends
AdminUiTestBase {
/** Returns the rows param of the paramset, or empty string when absent. */
private String paramsetRows(String name) {
try {
+ // adminApi() is a v1 request; v1's /config/params path and response
shape are unchanged
+ // by the GET /config/paramsets v2 migration.
Object rows =
adminApi("/" + COLLECTION + "/config/params/" + name, params())
._get(List.of("response", "params", name, "rows"), "");
diff --git a/solr/webapp/web/js/angular/controllers/paramsets.js
b/solr/webapp/web/js/angular/controllers/paramsets.js
index d466c31a8a6..32d0ecec29b 100644
--- a/solr/webapp/web/js/angular/controllers/paramsets.js
+++ b/solr/webapp/web/js/angular/controllers/paramsets.js
@@ -17,7 +17,7 @@
//helper for formatting JSON and others
solrAdminApp.controller('ParamSetsController',
- function($scope, $location, $routeParams, ParamSet, Constants) {
+ function($scope, $location, $routeParams, $timeout, ParamSet, ParamSetsV2,
Constants, ApiErrorHandler) {
$scope.paramsetList = [];
@@ -33,7 +33,7 @@ solrAdminApp.controller('ParamSetsController',
}
}
- // The v2 config/params API needs to know up front whether ":core" is a
collection name
+ // The v2 config/paramsets API needs to know up front whether ":core" is a
collection name
// (SolrCloud) or an actual core name (standalone/user-managed) -- there's
no equivalent of
// v1's flexible core-or-collection routing.
$scope.paramSetIndexType = function() {
@@ -48,60 +48,43 @@ solrAdminApp.controller('ParamSetsController',
$scope.getParamset = function (paramset) {
$scope.refresh();
- var params = {};
- params.core = $routeParams.core;
- params.indexType = $scope.paramSetIndexType();
- params.wt = "json";
- params.name = paramset;
-
- ParamSet.get(params, callback, failure);
-
- ///////
-
- function callback(success) {
- $scope.responseStatus = "success";
- delete success.$promise;
- delete success.$resolved;
- $scope.response = JSON.stringify(success, null, ' ');
- var apiPayload = {
- "set": success.response.params
- };
- // remove json key that is defined as "", it can't be submitted via
the API.
- var paramsetName = Object.keys(apiPayload.set)[0];
- delete apiPayload.set[paramsetName][""]
-
- $scope.paramsetContent = JSON.stringify(apiPayload, null, ' ');
+ // The dropdown's blank option (deselecting) fires this with an empty
name; the generated
+ // client requires a real paramSetName, so just leave the content
cleared in that case.
+ if (!paramset) {
+ return;
}
- function failure (failure) {
- $scope.responseStatus = failure;
- }
+ ParamSetsV2.getParamSet($scope.paramSetIndexType(), $routeParams.core,
paramset, function(error, data, response) {
+ $timeout(function() {
+ if (error) { ApiErrorHandler.handle(response); $scope.responseStatus
= response; return; }
+
+ $scope.responseStatus = "success";
+ $scope.response = JSON.stringify(data, null, ' ');
+ var apiPayload = {
+ "set": data.params
+ };
+ // remove json key that is defined as "", it can't be submitted via
the API.
+ var paramsetName = Object.keys(apiPayload.set)[0];
+ delete apiPayload.set[paramsetName][""]
+
+ $scope.paramsetContent = JSON.stringify(apiPayload, null, ' ');
+ });
+ });
}
$scope.getParamsets = function () {
$scope.refresh();
- var params = {};
- params.core = $routeParams.core;
- params.indexType = $scope.paramSetIndexType();
- params.wt = "json";
-
- ParamSet.get(params, callback, failure);
+ ParamSetsV2.listParamSets($scope.paramSetIndexType(), $routeParams.core,
function(error, data, response) {
+ $timeout(function() {
+ if (error) { ApiErrorHandler.handle(response); $scope.responseStatus
= response; return; }
- ///////
-
- function callback(success) {
- $scope.responseStatus = "success";
- delete success.$promise;
- delete success.$resolved;
- $scope.response = JSON.stringify(success, null, ' ');
- $scope.paramsetList = success.response.params ?
Object.keys(success.response.params) : [];
- }
-
- function failure (failure) {
- $scope.responseStatus = failure;
- }
+ $scope.responseStatus = "success";
+ $scope.response = JSON.stringify(data, null, ' ');
+ $scope.paramsetList = data.params ? Object.keys(data.params) : [];
+ });
+ });
}
// resetMenu() above populates $scope.isCloudEnabled asynchronously, so
calling
diff --git a/solr/webapp/web/js/angular/controllers/query.js
b/solr/webapp/web/js/angular/controllers/query.js
index 1d135e2069c..e11702ba1a3 100644
--- a/solr/webapp/web/js/angular/controllers/query.js
+++ b/solr/webapp/web/js/angular/controllers/query.js
@@ -16,7 +16,7 @@
*/
solrAdminApp.controller('QueryController',
- function($scope, $route, $routeParams, $location, Query, Constants,
ParamSet){
+ function($scope, $route, $routeParams, $location, $timeout, Query,
Constants, ParamSetsV2, ApiErrorHandler){
$scope.resetMenu("query", Constants.IS_COLLECTION_PAGE);
$scope._models = [];
@@ -40,28 +40,18 @@ solrAdminApp.controller('QueryController',
});
function getParamsets() {
-
- var params = {};
- params.core = $routeParams.core;
- // The v2 config/params API needs to know up front whether ":core" is a
collection name
+ // The v2 config/paramsets API needs to know up front whether ":core" is
a collection name
// (SolrCloud) or an actual core name (standalone/user-managed); see
paramsets.js for details.
- params.indexType = $scope.isCloudEnabled ? "collections" : "cores";
- params.wt = "json";
-
- ParamSet.get(params, callback, failure);
+ var indexType = $scope.isCloudEnabled ? "collections" : "cores";
- ///////
+ ParamSetsV2.listParamSets(indexType, $routeParams.core, function(error,
data, response) {
+ $timeout(function() {
+ if (error) { ApiErrorHandler.handle(response); $scope.responseStatus
= response; return; }
- function callback(success) {
- $scope.responseStatus = "success";
- delete success.$promise;
- delete success.$resolved;
- $scope.paramsetList = success.response.params ?
Object.keys(success.response.params) : [];
- }
-
- function failure (failure) {
- $scope.responseStatus = failure;
- }
+ $scope.responseStatus = "success";
+ $scope.paramsetList = data.params ? Object.keys(data.params) : [];
+ });
+ });
}
// get list of ng-models that have a form element
diff --git a/solr/webapp/web/js/angular/services.js
b/solr/webapp/web/js/angular/services.js
index 39441a90724..2cac309ec3b 100644
--- a/solr/webapp/web/js/angular/services.js
+++ b/solr/webapp/web/js/angular/services.js
@@ -260,20 +260,23 @@ solrAdminServices.factory('Metrics',
"postCsv": {headers: {'Content-type': 'application/csv'}, method:
"POST", params: {handler: '@handler'}}
});
}])
+.factory('ParamSetsV2',
+ function() {
+ solrApi.ApiClient.instance.basePath = '/api';
+ delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
+ return new solrApi.ParamSetsApi();
+ })
.factory('ParamSet',
['$resource', function($resource) {
- // v2 GetConfigAPI/ModifyParamSetAPI
(/api/(cores|collections)/:core/config/params) still
- // delegate straight through to the same v1 SolrConfigHandler, so the
response shape is
- // byte-identical -- no generated solrApi client class exists for it
(old-style @EndPoint API,
- // predates the OpenAPI-based v2 framework), so this stays a plain
$resource.
+ // Creating, updating and deleting paramsets is still on ModifyParamSetAPI
(/config/params) --
+ // reading them goes through ParamSetsV2 instead (see paramsets.js,
query.js).
// NB: unlike v1's flexible routing, the v2 API requires knowing up front
whether ":core" is a
// collection name (SolrCloud) or an actual core name
(standalone/user-managed) --
// /api/collections/... 500s in standalone mode (it tries to resolve
aliases, which needs ZK),
// and there's no such thing as a "collection" there anyway. Callers must
pass "indexType" as
// "collections" or "cores" (see paramsets.js, driven by
$scope.isCloudEnabled).
- return $resource('/api/:indexType/:core/config/params/:name', {core:
'@core', indexType: '@indexType', wt:'json', _:Date.now()}, {
- "submit": {headers: {'Content-type': 'application/json'}, method:
"POST"},
- "get": {headers: {'Content-type': 'application/json'}, method: "GET"}
+ return $resource('/api/:indexType/:core/config/params', {core: '@core',
indexType: '@indexType', wt:'json', _:Date.now()}, {
+ "submit": {headers: {'Content-type': 'application/json'}, method: "POST"}
});
}])
.service('FileUpload', function ($http) {