From 6fa37b7bce013a47a283dfaeba98e3de4b0cf30a Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 07:45:27 -0500 Subject: [PATCH 01/14] Raw is used by /solr/admin/zookeeper ZookeeperInfoHandler endpoint Now I see why we would want request handlers to inject their own request writers ;-) --- .../java/org/apache/solr/response/ResponseWritersRegistry.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java index f7d342bc286..67ec648cbf6 100644 --- a/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java +++ b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java @@ -55,6 +55,8 @@ private ResponseWritersRegistry() { jsonWriter, // Alias for JSON "xml", new XMLResponseWriter(), + "raw", + new RawResponseWriter(), PROMETHEUS_METRICS_WT, prometheusWriter, OPEN_METRICS_WT, From 0fd3316bf5247865af1338a1f33cceb1b77a9ac9 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 08:29:29 -0500 Subject: [PATCH 02/14] Add basic tests --- .../admin/ZookeeperInfoHandlerTest.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 solr/core/src/test/org/apache/solr/handler/admin/ZookeeperInfoHandlerTest.java diff --git a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperInfoHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperInfoHandlerTest.java new file mode 100644 index 00000000000..f6696b7bd2c --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperInfoHandlerTest.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.handler.admin; + +import java.io.IOException; +import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrRequest; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.request.CollectionAdminRequest; +import org.apache.solr.client.solrj.request.GenericSolrRequest; +import org.apache.solr.client.solrj.response.SimpleSolrResponse; +import org.apache.solr.client.solrj.response.json.JsonMapResponseParser; +import org.apache.solr.cloud.SolrCloudTestCase; +import org.apache.solr.common.params.CommonParams; +import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.util.NamedList; +import org.junit.BeforeClass; +import org.junit.Test; + +/** Basic tests for {@link ZookeeperInfoHandler} */ +public class ZookeeperInfoHandlerTest extends SolrCloudTestCase { + + @BeforeClass + public static void setupCluster() throws Exception { + configureCluster(1).addConfig("conf", configset("cloud-minimal")).configure(); + } + + @Test + public void testZkInfoHandler() throws SolrServerException, IOException { + SolrClient client = cluster.getSolrClient(); + + ModifiableSolrParams params = new ModifiableSolrParams(); + params.set(CommonParams.PATH, "/"); + params.set("detail", "true"); + GenericSolrRequest req = + new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/zookeeper", params); + req.setResponseParser(new JsonMapResponseParser()); + + NamedList response = client.request(req); + assertNotNull("Response should not be null", response); + + // ZK handler should return 'znode' for detail requests + assertNotNull(response.get("znode")); + } + + @Test + public void testZkInfoHandlerCollectionsView() throws Exception { + // Create a test collection first + String collectionName = "zkinfo_test_collection"; + CollectionAdminRequest.createCollection(collectionName, "conf", 1, 1) + .process(cluster.getSolrClient()); + cluster.waitForActiveCollection(collectionName, 1, 1); + + SolrClient client = cluster.getSolrClient(); + // Test collections view (graph view with clusterstate.json) + ModifiableSolrParams params = new ModifiableSolrParams(); + params.set(CommonParams.PATH, "/clusterstate.json"); + params.set("view", "graph"); + + GenericSolrRequest req = + new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/zookeeper", params); + req.setResponseParser(new JsonMapResponseParser()); + + // Verify the request completes and returns collection data + SimpleSolrResponse response = req.process(client); + NamedList responseData = response.getResponse(); + + // Collections view should return znode with collection data + assertNotNull("Response should not be null", responseData); + + assertNotNull( + "Response should contain 'znode' for collections view", responseData.get("znode")); + } +} From 455850bdc6b5d3e07e2e281821667e46a8b4e6ee Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 09:05:31 -0500 Subject: [PATCH 03/14] migrate away from ContentStream, eliminate clusterstate.json as a parameter --- .../handler/admin/ZookeeperInfoHandler.java | 74 +++++++------------ solr/webapp/web/js/angular/services.js | 2 +- 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java index f7598af121f..c78ce5497f3 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java @@ -21,9 +21,7 @@ import static org.apache.solr.common.params.CommonParams.WT; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStreamWriter; -import java.io.Reader; import java.io.Writer; import java.lang.invoke.MethodHandles; import java.net.URLEncoder; @@ -55,21 +53,17 @@ import org.apache.solr.common.cloud.ZkStateReader; import org.apache.solr.common.params.MapSolrParams; import org.apache.solr.common.params.SolrParams; -import org.apache.solr.common.util.ContentStream; import org.apache.solr.common.util.SuppressForbidden; import org.apache.solr.common.util.Utils; import org.apache.solr.core.CoreContainer; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.response.JSONResponseWriter; -import org.apache.solr.response.RawResponseWriter; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.security.AuthorizationContext; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.data.Stat; -import org.apache.zookeeper.server.ByteBufferInputStream; import org.noggit.CharArr; import org.noggit.JSONWriter; import org.slf4j.Logger; @@ -360,7 +354,8 @@ public void onReconnect() { @SuppressWarnings({"unchecked"}) public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { final SolrParams params = req.getParams(); - Map map = Map.of(WT, "raw", OMIT_HEADER, "true"); + // Force JSON response and omit header for cleaner output + Map map = Map.of(WT, "json", OMIT_HEADER, "true"); req.setParams(SolrParams.wrapDefaults(new MapSolrParams(map), params)); synchronized (this) { if (pagingSupport == null) { @@ -405,11 +400,9 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw ZKPrinter printer = new ZKPrinter(cores.getZkController()); printer.detail = detail; printer.dump = dump; + // Graph formatted data is used in services.js to power the Admin UI Cloud - Graph boolean isGraphView = "graph".equals(params.get("view")); - // There is no znode /clusterstate.json (removed in Solr 9), but we do as if there's one and - // return collection listing. Need to change services.js if cleaning up here, collection list is - // used from Admin UI Cloud - Graph - boolean paginateCollections = (isGraphView && "/clusterstate.json".equals(path)); + boolean paginateCollections = isGraphView; printer.page = paginateCollections ? new PageOfCollections(start, rows, type, filter) : null; printer.pagingSupport = pagingSupport; @@ -424,7 +417,22 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw } finally { printer.close(); } - rsp.getValues().add(RawResponseWriter.CONTENT, printer); + + // Parse the JSON we built and return as structured data + // This allows any response writer (json, xml, etc.) to serialize it properly + Object parsedJson = Utils.fromJSONString(printer.getJsonString()); + + // If it's a Map, add its contents directly to the response + if (parsedJson instanceof Map) { + @SuppressWarnings("unchecked") + Map jsonMap = (Map) parsedJson; + for (Map.Entry entry : jsonMap.entrySet()) { + rsp.add(entry.getKey(), entry.getValue()); + } + } else { + // Fallback: add as single value + rsp.add("zk_data", parsedJson); + } } @SuppressForbidden(reason = "JDK String class doesn't offer a stripEnd equivalent") @@ -436,7 +444,7 @@ private String normalizePath(String path) { // // -------------------------------------------------------------------------------------- - static class ZKPrinter implements ContentStream { + public static class ZKPrinter { static boolean FULLPATH_DEFAULT = false; boolean indent = true; @@ -772,40 +780,12 @@ boolean printZnode(JSONWriter json, String path) throws IOException { return true; } - /* @Override - public void write(OutputStream os) throws IOException { - ByteBuffer bytes = baos.getByteBuffer(); - os.write(bytes.array(),0,bytes.limit()); - } - */ - @Override - public String getName() { - return null; - } - - @Override - public String getSourceInfo() { - return null; - } - - @Override - public String getContentType() { - return JSONResponseWriter.CONTENT_TYPE_JSON_UTF8; - } - - @Override - public Long getSize() { - return null; - } - - @Override - public InputStream getStream() throws IOException { - return new ByteBufferInputStream(baos.getByteBuffer()); - } - - @Override - public Reader getReader() throws IOException { - return null; + /** + * Returns the JSON content as a string. This will be parsed back into objects for proper + * serialization by response writers. + */ + public String getJsonString() { + return baos.toString(); } } } diff --git a/solr/webapp/web/js/angular/services.js b/solr/webapp/web/js/angular/services.js index 67eaa42e21f..4b6fc6e3eee 100644 --- a/solr/webapp/web/js/angular/services.js +++ b/solr/webapp/web/js/angular/services.js @@ -99,7 +99,7 @@ solrAdminServices.factory('System', return $resource('admin/zookeeper', {wt:'json', _:Date.now()}, { "simple": {}, "liveNodes": {params: {path: '/live_nodes'}}, - "clusterState": {params: {detail: "true", path: "/clusterstate.json"}}, + "clusterState": {params: {detail: "true"}}, "detail": {params: {detail: "true", path: "@path"}}, "configs": {params: {detail:false, path: "/configs/"}}, "aliases": {params: {detail: "true", path: "/aliases.json"}, transformResponse:function(data) { From 20b3ff0677fe74132e320cda9b188694203e4a16 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 09:17:36 -0500 Subject: [PATCH 04/14] Update test now that we don't need this param from services.js --- .../apache/solr/handler/admin/ZookeeperInfoHandlerTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperInfoHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperInfoHandlerTest.java index f6696b7bd2c..c172439628c 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperInfoHandlerTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperInfoHandlerTest.java @@ -66,9 +66,8 @@ public void testZkInfoHandlerCollectionsView() throws Exception { cluster.waitForActiveCollection(collectionName, 1, 1); SolrClient client = cluster.getSolrClient(); - // Test collections view (graph view with clusterstate.json) + // Return the data to power the Solr Admin UI - Graph. ModifiableSolrParams params = new ModifiableSolrParams(); - params.set(CommonParams.PATH, "/clusterstate.json"); params.set("view", "graph"); GenericSolrRequest req = From 97b06274074367b6cd19d1b2736e9dc0252b0190 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 09:17:43 -0500 Subject: [PATCH 05/14] Code cleanups --- .../handler/admin/ZookeeperInfoHandler.java | 46 +++++-------------- 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java index c78ce5497f3..ebff2709099 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java @@ -111,7 +111,7 @@ public Name getPermissionName(AuthorizationContext request) { } /** Enumeration of ways to filter collections on the graph panel. */ - static enum FilterType { + enum FilterType { none, name, status @@ -176,7 +176,7 @@ List applyNameFilter(List collections) { * user is filtering by. */ @SuppressWarnings("unchecked") - final boolean matchesStatusFilter(Map collectionState, Set liveNodes) { + boolean matchesStatusFilter(Map collectionState, Set liveNodes) { if (filterType != FilterType.status || filter == null || filter.length() == 0) return true; // no status filter, so all match @@ -227,7 +227,7 @@ final boolean matchesStatusFilter(Map collectionState, Set 10) { page.rows = 20; - page.start = 0; } // apply the name filter if supplied (we don't need to pull state @@ -462,7 +461,7 @@ public static class ZKPrinter { PagedCollectionSupport pagingSupport; ZkController zkController; - public ZKPrinter(ZkController controller) throws IOException { + public ZKPrinter(ZkController controller) { this.zkController = controller; keeperAddr = controller.getZkServerAddress(); zkClient = controller.getZkClient(); @@ -564,7 +563,7 @@ void printPaginatedCollections() throws IOException { page.selectPage(matchesStatusFilter); // rebuild the Map of state data - SortedMap map = new TreeMap(pagingSupport); + SortedMap map = new TreeMap<>(pagingSupport); for (String next : page.selected) map.put(next, collectionStates.get(next)); collectionStates = map; } @@ -597,25 +596,8 @@ void printPaginatedCollections() throws IOException { out.write(chars.toString()); } - void writeError(int code, String msg) throws IOException { + void writeError(int code, String msg) { throw new SolrException(ErrorCode.getErrorCode(code), msg); - /*response.setStatus(code); - - CharArr chars = new CharArr(); - JSONWriter w = new JSONWriter(chars, 2); - w.startObject(); - w.indent(); - w.writeString("status"); - w.writeNameSeparator(); - w.write(code); - w.writeValueSeparator(); - w.indent(); - w.writeString("error"); - w.writeNameSeparator(); - w.writeString(msg); - w.endObject(); - - out.write(chars.toString());*/ } boolean printTree(JSONWriter json, String path) throws IOException { @@ -686,10 +668,7 @@ boolean printTree(JSONWriter json, String path) throws IOException { } first = false; } - } catch (KeeperException e) { - writeError(500, e.toString()); - return false; - } catch (InterruptedException e) { + } catch (KeeperException | InterruptedException e) { writeError(500, e.toString()); return false; } catch (IllegalArgumentException e) { @@ -705,7 +684,7 @@ boolean printTree(JSONWriter json, String path) throws IOException { } String time(long ms) { - return (new Date(ms)).toString() + " (" + ms + ")"; + return (new Date(ms)) + " (" + ms + ")"; } public void writeKeyValue(JSONWriter json, String k, Object v, boolean isFirst) { @@ -720,7 +699,7 @@ public void writeKeyValue(JSONWriter json, String k, Object v, boolean isFirst) json.write(v); } - boolean printZnode(JSONWriter json, String path) throws IOException { + boolean printZnode(JSONWriter json, String path) { try { String dataStr = null; String dataStrErr = null; @@ -731,7 +710,7 @@ boolean printZnode(JSONWriter json, String path) throws IOException { try { dataStr = (new BytesRef(data)).utf8ToString(); } catch (Exception e) { - dataStrErr = "data is not parsable as a utf8 String: " + e.toString(); + dataStrErr = "data is not parsable as a utf8 String: " + e; } } @@ -770,10 +749,7 @@ boolean printZnode(JSONWriter json, String path) throws IOException { } json.endObject(); - } catch (KeeperException e) { - writeError(500, e.toString()); - return false; - } catch (InterruptedException e) { + } catch (KeeperException | InterruptedException e) { writeError(500, e.toString()); return false; } From 1a935f95d9751fd981b556b431a455294f062c4f Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 09:56:39 -0500 Subject: [PATCH 06/14] no longer need raw --- .../java/org/apache/solr/response/ResponseWritersRegistry.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java index 67ec648cbf6..f7d342bc286 100644 --- a/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java +++ b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java @@ -55,8 +55,6 @@ private ResponseWritersRegistry() { jsonWriter, // Alias for JSON "xml", new XMLResponseWriter(), - "raw", - new RawResponseWriter(), PROMETHEUS_METRICS_WT, prometheusWriter, OPEN_METRICS_WT, From 804f90e9be08e76c1b525130b60a2cf38c86db9e Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 13:09:28 -0500 Subject: [PATCH 07/14] Small UI fixes for pagination --- solr/webapp/web/partials/cloud.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/solr/webapp/web/partials/cloud.html b/solr/webapp/web/partials/cloud.html index d5715fbaff3..7afb9752910 100644 --- a/solr/webapp/web/partials/cloud.html +++ b/solr/webapp/web/partials/cloud.html @@ -269,7 +269,7 @@ Filter by:  T:{{filterType}} +   -   +   Show per page. From d7b1f0d038fd5e9318dba7f9c0f6513327ef4cd3 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 13:15:17 -0500 Subject: [PATCH 08/14] bit more whitespace. --- solr/webapp/web/partials/cloud.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solr/webapp/web/partials/cloud.html b/solr/webapp/web/partials/cloud.html index 7afb9752910..ed72cbbc90e 100644 --- a/solr/webapp/web/partials/cloud.html +++ b/solr/webapp/web/partials/cloud.html @@ -283,7 +283,7 @@   - Show per page. +  Show per page. From 1cd94fd22205229580a3064bfee0f72196e49847 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 13:50:44 -0500 Subject: [PATCH 09/14] Refactor the code to be clearer on when doing a graph or a file path request.. --- .../handler/admin/ZookeeperInfoHandler.java | 164 ++++++++++++++---- 1 file changed, 131 insertions(+), 33 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java index ebff2709099..53c60cc4871 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java @@ -353,73 +353,171 @@ public void onReconnect() { @SuppressWarnings({"unchecked"}) public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { final SolrParams params = req.getParams(); + // Force JSON response and omit header for cleaner output Map map = Map.of(WT, "json", OMIT_HEADER, "true"); req.setParams(SolrParams.wrapDefaults(new MapSolrParams(map), params)); + + // Ensure paging support is initialized + ensurePagingSupportInitialized(); + + // Validate parameters + validateParameters(params); + + // Determine request type and handle accordingly + boolean isGraphView = "graph".equals(params.get("view")); + String jsonResult = isGraphView + ? handleGraphViewRequest(params) + : handlePathViewRequest(params); + + // Convert JSON string to structured response + addJsonToResponse(jsonResult, rsp); + } + + /** + * Ensures the paging support is initialized (thread-safe lazy initialization). + */ + private void ensurePagingSupportInitialized() { synchronized (this) { if (pagingSupport == null) { pagingSupport = new PagedCollectionSupport(); ZkController zkController = cores.getZkController(); if (zkController != null) { - // get notified when the ZK session expires (so we can clear the cached collections and - // rebuild) + // Get notified when the ZK session expires (so we can clear cached collections) zkController.addOnReconnectListener(pagingSupport); } } } + } - String path = params.get(PATH); - + /** + * Validates incoming parameters. + * + * @param params Request parameters to validate + * @throws SolrException if validation fails + */ + private void validateParameters(SolrParams params) { if (params.get("addr") != null) { throw new SolrException(ErrorCode.BAD_REQUEST, "Illegal parameter \"addr\""); } + } - String detailS = params.get(PARAM_DETAIL); - boolean detail = detailS != null && detailS.equals("true"); + /** + * Handles the graph view request with paginated collections. + * + * @param params Request parameters including pagination settings + * @return JSON string representing paginated collection data + * @throws IOException if an I/O error occurs + */ + private String handleGraphViewRequest(SolrParams params) throws IOException { + // Extract pagination parameters + int start = params.getInt("start", 0); + int rows = params.getInt("rows", -1); - String dumpS = params.get("dump"); - boolean dump = dumpS != null && dumpS.equals("true"); + // Extract filter parameters + FilterType filterType = extractFilterType(params); + String filter = extractFilter(params, filterType); - int start = params.getInt("start", 0); // Note start ignored if rows not specified - int rows = params.getInt("rows", -1); + // Extract display options (applicable to graph view) + boolean detail = params.getBool(PARAM_DETAIL, false); + boolean dump = params.getBool("dump", false); - String filterType = params.get("filterType"); - if (filterType != null) { - filterType = filterType.trim().toLowerCase(Locale.ROOT); - if (filterType.length() == 0) filterType = null; - } - FilterType type = (filterType != null) ? FilterType.valueOf(filterType) : FilterType.none; + // Create printer for paginated collections + ZKPrinter printer = new ZKPrinter(cores.getZkController()); + printer.detail = detail; + printer.dump = dump; + printer.page = new PageOfCollections(start, rows, filterType, filter); + printer.pagingSupport = pagingSupport; - String filter = (type != FilterType.none) ? params.get("filter") : null; - if (filter != null) { - filter = filter.trim(); - if (filter.length() == 0) filter = null; + try { + printer.printPaginatedCollections(); + } finally { + printer.close(); } + return printer.getJsonString(); + } + + /** + * Handles the path view request for a specific ZooKeeper path. + * + * @param params Request parameters including the path to display + * @return JSON string representing the ZooKeeper path data + * @throws IOException if an I/O error occurs + */ + private String handlePathViewRequest(SolrParams params) throws IOException { + // Extract path parameter + String path = params.get(PATH); + + // Extract display options + boolean detail = params.getBool(PARAM_DETAIL, false); + boolean dump = params.getBool("dump", false); + + // Create printer for specific path ZKPrinter printer = new ZKPrinter(cores.getZkController()); printer.detail = detail; printer.dump = dump; - // Graph formatted data is used in services.js to power the Admin UI Cloud - Graph - boolean isGraphView = "graph".equals(params.get("view")); - boolean paginateCollections = isGraphView; - printer.page = paginateCollections ? new PageOfCollections(start, rows, type, filter) : null; - printer.pagingSupport = pagingSupport; + // Note: page and pagingSupport are null for path view try { - if (paginateCollections) { - // List collections and allow pagination, but no specific znode info like when looking at a - // normal ZK path - printer.printPaginatedCollections(); - } else { - printer.print(path); - } + printer.print(path); } finally { printer.close(); } + return printer.getJsonString(); + } + + /** + * Extracts and normalizes the filter type from request parameters. + * + * @param params Request parameters + * @return The filter type (defaults to FilterType.none if not specified) + */ + private FilterType extractFilterType(SolrParams params) { + String filterType = params.get("filterType"); + if (filterType != null) { + filterType = filterType.trim().toLowerCase(Locale.ROOT); + if (filterType.length() == 0) { + return FilterType.none; + } + return FilterType.valueOf(filterType); + } + return FilterType.none; + } + + /** + * Extracts and normalizes the filter value from request parameters. + * + * @param params Request parameters + * @param filterType The filter type being used + * @return The filter string, or null if not applicable + */ + private String extractFilter(SolrParams params, FilterType filterType) { + if (filterType == FilterType.none) { + return null; + } + + String filter = params.get("filter"); + if (filter != null) { + filter = filter.trim(); + if (filter.length() > 0) { + return filter; + } + } + return null; + } + + /** + * Converts JSON string to structured response objects and adds to SolrQueryResponse. + * + * @param jsonString The JSON string to parse + * @param rsp The response object to populate + */ + private void addJsonToResponse(String jsonString, SolrQueryResponse rsp) { // Parse the JSON we built and return as structured data // This allows any response writer (json, xml, etc.) to serialize it properly - Object parsedJson = Utils.fromJSONString(printer.getJsonString()); + Object parsedJson = Utils.fromJSONString(jsonString); // If it's a Map, add its contents directly to the response if (parsedJson instanceof Map) { From 6906f426d1b7bb90f2d3123e8beb3cb097c25417 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 14:00:04 -0500 Subject: [PATCH 10/14] Code cleanups and rename print() to printPath() as thats what it does. --- .../handler/admin/ZookeeperInfoHandler.java | 58 +++++++------------ .../admin/ZookeeperInfoHandlerTest.java | 6 +- 2 files changed, 24 insertions(+), 40 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java index 53c60cc4871..4e0906bc2b1 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java @@ -366,17 +366,14 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw // Determine request type and handle accordingly boolean isGraphView = "graph".equals(params.get("view")); - String jsonResult = isGraphView - ? handleGraphViewRequest(params) - : handlePathViewRequest(params); + String jsonResult = + isGraphView ? handleGraphViewRequest(params) : handlePathViewRequest(params); // Convert JSON string to structured response addJsonToResponse(jsonResult, rsp); } - /** - * Ensures the paging support is initialized (thread-safe lazy initialization). - */ + /** Ensures the paging support is initialized (thread-safe lazy initialization). */ private void ensurePagingSupportInitialized() { synchronized (this) { if (pagingSupport == null) { @@ -460,7 +457,7 @@ private String handlePathViewRequest(SolrParams params) throws IOException { // Note: page and pagingSupport are null for path view try { - printer.print(path); + printer.printPath(path); } finally { printer.close(); } @@ -517,18 +514,13 @@ private String extractFilter(SolrParams params, FilterType filterType) { private void addJsonToResponse(String jsonString, SolrQueryResponse rsp) { // Parse the JSON we built and return as structured data // This allows any response writer (json, xml, etc.) to serialize it properly - Object parsedJson = Utils.fromJSONString(jsonString); - - // If it's a Map, add its contents directly to the response - if (parsedJson instanceof Map) { - @SuppressWarnings("unchecked") - Map jsonMap = (Map) parsedJson; - for (Map.Entry entry : jsonMap.entrySet()) { - rsp.add(entry.getKey(), entry.getValue()); - } - } else { - // Fallback: add as single value - rsp.add("zk_data", parsedJson); + // The JSON is always a Map since both printPath() and printPaginatedCollections() + // start with json.startObject() and end with json.endObject() + @SuppressWarnings("unchecked") + Map jsonMap = (Map) Utils.fromJSONString(jsonString); + + for (Map.Entry entry : jsonMap.entrySet()) { + rsp.add(entry.getKey(), entry.getValue()); } } @@ -542,10 +534,7 @@ private String normalizePath(String path) { // -------------------------------------------------------------------------------------- public static class ZKPrinter { - static boolean FULLPATH_DEFAULT = false; - boolean indent = true; - boolean fullpath = FULLPATH_DEFAULT; boolean detail = false; boolean dump = false; @@ -574,7 +563,7 @@ public void close() { } // main entry point for printing from path - void print(String path) throws IOException { + void printPath(String path) throws IOException { if (zkClient == null) { return; } @@ -584,7 +573,7 @@ void print(String path) throws IOException { path = "/"; } else { path = path.trim(); - if (path.length() == 0) { + if (path.isEmpty()) { path = "/"; } } @@ -595,7 +584,7 @@ void print(String path) throws IOException { int idx = path.lastIndexOf('/'); String parent = idx >= 0 ? path.substring(0, idx) : path; - if (parent.length() == 0) { + if (parent.isEmpty()) { parent = "/"; } @@ -681,11 +670,9 @@ void printPaginatedCollections() throws IOException { // For some reason, without this the Json is badly formed writeKeyValue(json, PATH, "Undefined", true); - if (collectionStates != null) { - CharArr collectionOut = new CharArr(); - new JSONWriter(collectionOut, 2).write(collectionStates); - writeKeyValue(json, "data", collectionOut.toString(), false); - } + CharArr collectionOut = new CharArr(); + new JSONWriter(collectionOut, 2).write(collectionStates); + writeKeyValue(json, "data", collectionOut.toString(), false); writeKeyValue(json, "paging", page.getPagingHeader(), false); @@ -698,12 +685,11 @@ void writeError(int code, String msg) { throw new SolrException(ErrorCode.getErrorCode(code), msg); } - boolean printTree(JSONWriter json, String path) throws IOException { - String label = path; - if (!fullpath) { - int idx = path.lastIndexOf('/'); - label = idx > 0 ? path.substring(idx + 1) : path; - } + boolean printTree(JSONWriter json, String path) { + + int idx = path.lastIndexOf('/'); + String label = idx > 0 ? path.substring(idx + 1) : path; + json.startObject(); writeKeyValue(json, "text", label, true); json.writeValueSeparator(); diff --git a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperInfoHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperInfoHandlerTest.java index c172439628c..53706de97fc 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperInfoHandlerTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperInfoHandlerTest.java @@ -40,7 +40,7 @@ public static void setupCluster() throws Exception { } @Test - public void testZkInfoHandler() throws SolrServerException, IOException { + public void testZkInfoHandlerDetailView() throws SolrServerException, IOException { SolrClient client = cluster.getSolrClient(); ModifiableSolrParams params = new ModifiableSolrParams(); @@ -58,7 +58,7 @@ public void testZkInfoHandler() throws SolrServerException, IOException { } @Test - public void testZkInfoHandlerCollectionsView() throws Exception { + public void testZkInfoHandlerGraphView() throws Exception { // Create a test collection first String collectionName = "zkinfo_test_collection"; CollectionAdminRequest.createCollection(collectionName, "conf", 1, 1) @@ -78,9 +78,7 @@ public void testZkInfoHandlerCollectionsView() throws Exception { SimpleSolrResponse response = req.process(client); NamedList responseData = response.getResponse(); - // Collections view should return znode with collection data assertNotNull("Response should not be null", responseData); - assertNotNull( "Response should contain 'znode' for collections view", responseData.get("znode")); } From 0ce78908aa2afe3c33e18a78daf9cf9ac1b17160 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 15:33:22 -0500 Subject: [PATCH 11/14] refactor the ZKPrinter into two subclasses that do their specific job, but have the same .print() method --- .../handler/admin/ZookeeperInfoHandler.java | 330 +++++++++--------- 1 file changed, 173 insertions(+), 157 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java index 4e0906bc2b1..5ca078f2d77 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java @@ -366,11 +366,16 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw // Determine request type and handle accordingly boolean isGraphView = "graph".equals(params.get("view")); - String jsonResult = + ZkBasePrinter printer = isGraphView ? handleGraphViewRequest(params) : handlePathViewRequest(params); - // Convert JSON string to structured response - addJsonToResponse(jsonResult, rsp); + try { + printer.print(); + } finally { + printer.close(); + } + + addJsonToResponse(printer.getJsonString(), rsp); } /** Ensures the paging support is initialized (thread-safe lazy initialization). */ @@ -404,9 +409,8 @@ private void validateParameters(SolrParams params) { * * @param params Request parameters including pagination settings * @return JSON string representing paginated collection data - * @throws IOException if an I/O error occurs */ - private String handleGraphViewRequest(SolrParams params) throws IOException { + private ZkBasePrinter handleGraphViewRequest(SolrParams params) { // Extract pagination parameters int start = params.getInt("start", 0); int rows = params.getInt("rows", -1); @@ -420,19 +424,12 @@ private String handleGraphViewRequest(SolrParams params) throws IOException { boolean dump = params.getBool("dump", false); // Create printer for paginated collections - ZKPrinter printer = new ZKPrinter(cores.getZkController()); - printer.detail = detail; - printer.dump = dump; - printer.page = new PageOfCollections(start, rows, filterType, filter); - printer.pagingSupport = pagingSupport; - - try { - printer.printPaginatedCollections(); - } finally { - printer.close(); - } - - return printer.getJsonString(); + return new ZkGraphPrinter( + cores.getZkController(), + new PageOfCollections(start, rows, filterType, filter), + pagingSupport, + detail, + dump); } /** @@ -440,9 +437,8 @@ private String handleGraphViewRequest(SolrParams params) throws IOException { * * @param params Request parameters including the path to display * @return JSON string representing the ZooKeeper path data - * @throws IOException if an I/O error occurs */ - private String handlePathViewRequest(SolrParams params) throws IOException { + private ZkBasePrinter handlePathViewRequest(SolrParams params) { // Extract path parameter String path = params.get(PATH); @@ -451,18 +447,7 @@ private String handlePathViewRequest(SolrParams params) throws IOException { boolean dump = params.getBool("dump", false); // Create printer for specific path - ZKPrinter printer = new ZKPrinter(cores.getZkController()); - printer.detail = detail; - printer.dump = dump; - // Note: page and pagingSupport are null for path view - - try { - printer.printPath(path); - } finally { - printer.close(); - } - - return printer.getJsonString(); + return new ZkPathPrinter(cores.getZkController(), path, detail, dump); } /** @@ -533,27 +518,30 @@ private String normalizePath(String path) { // // -------------------------------------------------------------------------------------- - public static class ZKPrinter { - boolean indent = true; - boolean detail = false; - boolean dump = false; - - String keeperAddr; // the address we're connected to - - final Utils.BAOS baos = new Utils.BAOS(); - final Writer out = new OutputStreamWriter(baos, StandardCharsets.UTF_8); - SolrZkClient zkClient; + /** + * Base class for ZooKeeper JSON printers. Provides common functionality for building JSON from + * ZooKeeper data. + */ + abstract static class ZkBasePrinter { + protected boolean detail; + protected boolean dump; - PageOfCollections page; - PagedCollectionSupport pagingSupport; - ZkController zkController; + protected final Utils.BAOS baos = new Utils.BAOS(); + protected final Writer out = new OutputStreamWriter(baos, StandardCharsets.UTF_8); + protected final SolrZkClient zkClient; + protected final ZkController zkController; + protected final String keeperAddr; - public ZKPrinter(ZkController controller) { + public ZkBasePrinter(ZkController controller, boolean detail, boolean dump) { this.zkController = controller; - keeperAddr = controller.getZkServerAddress(); - zkClient = controller.getZkClient(); + this.detail = detail; + this.dump = dump; + this.keeperAddr = controller.getZkServerAddress(); + this.zkClient = controller.getZkClient(); } + public abstract void print() throws IOException; + public void close() { try { out.flush(); @@ -562,8 +550,50 @@ public void close() { } } - // main entry point for printing from path - void printPath(String path) throws IOException { + /** + * Returns the JSON content as a string. This will be parsed back into objects for proper + * serialization by response writers. + */ + public String getJsonString() { + return baos.toString(StandardCharsets.UTF_8); + } + + protected void writeError(int code, String msg) { + throw new SolrException(ErrorCode.getErrorCode(code), msg); + } + + protected String time(long ms) { + return (new Date(ms)) + " (" + ms + ")"; + } + + protected void writeKeyValue(JSONWriter json, String k, Object v, boolean isFirst) { + if (!isFirst) { + json.writeValueSeparator(); + } + + json.indent(); + + json.writeString(k); + json.writeNameSeparator(); + json.write(v); + } + } + + /** + * Printer for specific ZooKeeper path details and tree structure. Used for the path view in the + * Admin UI. + */ + static class ZkPathPrinter extends ZkBasePrinter { + + private String path; + + public ZkPathPrinter(ZkController controller, String path, boolean detail, boolean dump) { + super(controller, detail, dump); + this.path = path; + } + + @Override + public void print() throws IOException { if (zkClient == null) { return; } @@ -610,83 +640,7 @@ void printPath(String path) throws IOException { out.write(chars.toString()); } - // main entry point for printing collections - @SuppressWarnings("unchecked") - void printPaginatedCollections() throws IOException { - SortedMap collectionStates; - try { - // support paging of the collections graph view (in case there are many collections) - // fetch the requested page of collections and then retrieve the state for each - pagingSupport.fetchPage(page, zkClient); - // keep track of how many collections match the filter - boolean applyStatusFilter = (page.filterType == FilterType.status && page.filter != null); - List matchesStatusFilter = applyStatusFilter ? new ArrayList<>() : null; - ClusterState cs = zkController.getZkStateReader().getClusterState(); - Set liveNodes = applyStatusFilter ? cs.getLiveNodes() : null; - - collectionStates = new TreeMap<>(pagingSupport); - for (String collection : page.selected) { - DocCollection dc = cs.getCollectionOrNull(collection); - if (dc != null) { - // TODO: for collections with perReplicaState, a ser/deser to JSON was needed to get the - // state to render correctly for the UI? - Map collectionState = dc.toMap(new LinkedHashMap<>()); - if (applyStatusFilter) { - // verify this collection matches the filtered state - if (page.matchesStatusFilter(collectionState, liveNodes)) { - matchesStatusFilter.add(collection); - collectionStates.put( - collection, ClusterStatus.postProcessCollectionJSON(collectionState)); - } - } else { - collectionStates.put( - collection, ClusterStatus.postProcessCollectionJSON(collectionState)); - } - } - } - - if (applyStatusFilter) { - // update the paged navigation info after applying the status filter - page.selectPage(matchesStatusFilter); - - // rebuild the Map of state data - SortedMap map = new TreeMap<>(pagingSupport); - for (String next : page.selected) map.put(next, collectionStates.get(next)); - collectionStates = map; - } - } catch (KeeperException | InterruptedException e) { - writeError(500, e.toString()); - return; - } - - CharArr chars = new CharArr(); - JSONWriter json = new JSONWriter(chars, 2); - json.startObject(); - - json.writeString("znode"); - json.writeNameSeparator(); - json.startObject(); - - // For some reason, without this the Json is badly formed - writeKeyValue(json, PATH, "Undefined", true); - - CharArr collectionOut = new CharArr(); - new JSONWriter(collectionOut, 2).write(collectionStates); - writeKeyValue(json, "data", collectionOut.toString(), false); - - writeKeyValue(json, "paging", page.getPagingHeader(), false); - - json.endObject(); - json.endObject(); - out.write(chars.toString()); - } - - void writeError(int code, String msg) { - throw new SolrException(ErrorCode.getErrorCode(code), msg); - } - - boolean printTree(JSONWriter json, String path) { - + private boolean printTree(JSONWriter json, String path) { int idx = path.lastIndexOf('/'); String label = idx > 0 ? path.substring(idx + 1) : path; @@ -729,9 +683,9 @@ boolean printTree(JSONWriter json, String path) { if (stat.getNumChildren() > 0) { json.writeValueSeparator(); - if (indent) { - json.indent(); - } + + json.indent(); + json.writeString("children"); json.writeNameSeparator(); json.startArray(); @@ -767,23 +721,7 @@ boolean printTree(JSONWriter json, String path) { return true; } - String time(long ms) { - return (new Date(ms)) + " (" + ms + ")"; - } - - public void writeKeyValue(JSONWriter json, String k, Object v, boolean isFirst) { - if (!isFirst) { - json.writeValueSeparator(); - } - if (indent) { - json.indent(); - } - json.writeString(k); - json.writeNameSeparator(); - json.write(v); - } - - boolean printZnode(JSONWriter json, String path) { + private boolean printZnode(JSONWriter json, String path) { try { String dataStr = null; String dataStrErr = null; @@ -828,10 +766,6 @@ boolean printZnode(JSONWriter json, String path) { writeKeyValue(json, "data", dataStr, false); } - if (page != null) { - writeKeyValue(json, "paging", page.getPagingHeader(), false); - } - json.endObject(); } catch (KeeperException | InterruptedException e) { writeError(500, e.toString()); @@ -839,13 +773,95 @@ boolean printZnode(JSONWriter json, String path) { } return true; } + } - /** - * Returns the JSON content as a string. This will be parsed back into objects for proper - * serialization by response writers. - */ - public String getJsonString() { - return baos.toString(); + /** + * Printer for paginated collection data for the Admin UI graph view. Handles filtering, + * pagination, and collection state retrieval. + */ + static class ZkGraphPrinter extends ZkBasePrinter { + private final PageOfCollections page; + private final PagedCollectionSupport pagingSupport; + + public ZkGraphPrinter( + ZkController controller, + PageOfCollections page, + PagedCollectionSupport pagingSupport, + boolean detail, + boolean dump) { + super(controller, detail, dump); + this.page = page; + this.pagingSupport = pagingSupport; + } + + @Override + public void print() throws IOException { + SortedMap collectionStates; + try { + // support paging of the collections graph view (in case there are many collections) + // fetch the requested page of collections and then retrieve the state for each + pagingSupport.fetchPage(page, zkClient); + // keep track of how many collections match the filter + boolean applyStatusFilter = (page.filterType == FilterType.status && page.filter != null); + List matchesStatusFilter = applyStatusFilter ? new ArrayList<>() : null; + ClusterState cs = zkController.getZkStateReader().getClusterState(); + Set liveNodes = applyStatusFilter ? cs.getLiveNodes() : null; + + collectionStates = new TreeMap<>(pagingSupport); + for (String collection : page.selected) { + DocCollection dc = cs.getCollectionOrNull(collection); + if (dc != null) { + // TODO: for collections with perReplicaState, a ser/deser to JSON was needed to get the + // state to render correctly for the UI? + Map collectionState = dc.toMap(new LinkedHashMap<>()); + if (applyStatusFilter) { + // verify this collection matches the filtered state + if (page.matchesStatusFilter(collectionState, liveNodes)) { + matchesStatusFilter.add(collection); + collectionStates.put( + collection, ClusterStatus.postProcessCollectionJSON(collectionState)); + } + } else { + collectionStates.put( + collection, ClusterStatus.postProcessCollectionJSON(collectionState)); + } + } + } + + if (applyStatusFilter) { + // update the paged navigation info after applying the status filter + page.selectPage(matchesStatusFilter); + + // rebuild the Map of state data + SortedMap map = new TreeMap<>(pagingSupport); + for (String next : page.selected) map.put(next, collectionStates.get(next)); + collectionStates = map; + } + } catch (KeeperException | InterruptedException e) { + writeError(500, e.toString()); + return; + } + + CharArr chars = new CharArr(); + JSONWriter json = new JSONWriter(chars, 2); + json.startObject(); + + json.writeString("znode"); + json.writeNameSeparator(); + json.startObject(); + + // For some reason, without this the Json is badly formed + writeKeyValue(json, PATH, "Undefined", true); + + CharArr collectionOut = new CharArr(); + new JSONWriter(collectionOut, 2).write(collectionStates); + writeKeyValue(json, "data", collectionOut.toString(), false); + + writeKeyValue(json, "paging", page.getPagingHeader(), false); + + json.endObject(); + json.endObject(); + out.write(chars.toString()); } } } From 2fd1efe76274f340e42dd85c1a09e5406bb947f3 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 15:42:45 -0500 Subject: [PATCH 12/14] doc the change --- changelog/unreleased/SOLR-18113.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 changelog/unreleased/SOLR-18113.yml diff --git a/changelog/unreleased/SOLR-18113.yml b/changelog/unreleased/SOLR-18113.yml new file mode 100644 index 00000000000..c8f80140c01 --- /dev/null +++ b/changelog/unreleased/SOLR-18113.yml @@ -0,0 +1,8 @@ +# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc +title: Improve footer UI of Solr Cloud - Graph when you have large numbers of collections. Plus code refactor. +type: fixed # added, changed, fixed, deprecated, removed, dependency_update, security, other +authors: + - name: Eric Pugh +links: + - name: SOLR-18113 + url: https://issues.apache.org/jira/browse/SOLR-18113 From 39d5c309021b5c3c12186a00eb0e9bdb4e9f88e9 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 15:52:27 -0500 Subject: [PATCH 13/14] We need raw at the core level for the ShowFileHandler. --- solr/core/src/resources/ImplicitPlugins.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/solr/core/src/resources/ImplicitPlugins.json b/solr/core/src/resources/ImplicitPlugins.json index eba9bd05ba0..7eed8eb09bf 100644 --- a/solr/core/src/resources/ImplicitPlugins.json +++ b/solr/core/src/resources/ImplicitPlugins.json @@ -159,6 +159,9 @@ } }, "queryResponseWriter": { + "raw": { + "class": "solr.RawResponseWriter" + }, "geojson": { "class": "solr.GeoJSONResponseWriter" }, From 92d7a3f1799b4c43851287db27d97b275a95406f Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 9 Feb 2026 16:07:51 -0500 Subject: [PATCH 14/14] Update solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../solr/handler/admin/ZookeeperInfoHandler.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java index 5ca078f2d77..2340a023fbe 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java @@ -463,7 +463,18 @@ private FilterType extractFilterType(SolrParams params) { if (filterType.length() == 0) { return FilterType.none; } - return FilterType.valueOf(filterType); + switch (filterType) { + case "none": + return FilterType.none; + case "name": + return FilterType.name; + case "status": + return FilterType.status; + default: + throw new SolrException( + ErrorCode.BAD_REQUEST, + "Invalid filterType '" + filterType + "'. Allowed values are: none, name, status"); + } } return FilterType.none; }