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 0259ab2dda3 Migrate org.apache.solr.cli tools from V1 to V2 APIs 
(#4154)
0259ab2dda3 is described below

commit 0259ab2dda3cd422246fb74801934c6bbe29ec88
Author: Eric Pugh <[email protected]>
AuthorDate: Tue Mar 17 06:59:11 2026 -0400

    Migrate org.apache.solr.cli tools from V1 to V2 APIs (#4154)
    
    Co-authored-by: copilot-swe-agent[bot] 
<[email protected]>
    Co-authored-by: epugh <[email protected]>
---
 .../client/api/endpoint/ListClusterNodesApi.java   | 33 ++++++++++++++++
 .../client/api/model/ListClusterNodesResponse.java | 33 ++++++++++++++++
 .../src/java/org/apache/solr/cli/CLIUtils.java     | 13 +++---
 .../src/java/org/apache/solr/cli/CreateTool.java   | 44 ++++++++-------------
 .../src/java/org/apache/solr/cli/DeleteTool.java   | 46 ++++++----------------
 .../src/java/org/apache/solr/cli/StatusTool.java   | 25 ++++++------
 .../src/java/org/apache/solr/cli/ToolBase.java     | 11 ++++++
 7 files changed, 125 insertions(+), 80 deletions(-)

diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ListClusterNodesApi.java
 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListClusterNodesApi.java
new file mode 100644
index 00000000000..4141e8ae825
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListClusterNodesApi.java
@@ -0,0 +1,33 @@
+/*
+ * 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 io.swagger.v3.oas.annotations.Operation;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import org.apache.solr.client.api.model.ListClusterNodesResponse;
+
+/** V2 API definition for listing the nodes in the SolrCloud cluster. */
+@Path("/cluster/nodes")
+public interface ListClusterNodesApi {
+
+  @GET
+  @Operation(
+      summary = "List the nodes in this Solr cluster.",
+      tags = {"cluster"})
+  ListClusterNodesResponse listClusterNodes();
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/ListClusterNodesResponse.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/ListClusterNodesResponse.java
new file mode 100644
index 00000000000..bd7a0195ae1
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/ListClusterNodesResponse.java
@@ -0,0 +1,33 @@
+/*
+ * 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 io.swagger.v3.oas.annotations.media.Schema;
+import java.util.Set;
+
+/**
+ * Response for the v2 "list cluster nodes" API. This is a bit unusual that 
it's wrapping a non
+ * JAX-RS V2 API defined in org.apache.solr.handler.ClusterAPI.getNodes(). The 
calls are made using
+ * just the defaults. TODO: Update this when we migrate ClusterAPI to JAX-RS.
+ */
+public class ListClusterNodesResponse extends SolrJerseyResponse {
+
+  @Schema(description = "The live nodes in the cluster.")
+  @JsonProperty("nodes")
+  public Set<String> nodes;
+}
diff --git a/solr/core/src/java/org/apache/solr/cli/CLIUtils.java 
b/solr/core/src/java/org/apache/solr/cli/CLIUtils.java
index 1c05377e889..5653bffa897 100644
--- a/solr/core/src/java/org/apache/solr/cli/CLIUtils.java
+++ b/solr/core/src/java/org/apache/solr/cli/CLIUtils.java
@@ -27,7 +27,6 @@ import java.net.URISyntaxException;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
@@ -41,7 +40,7 @@ import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.impl.SolrZkClientTimeout;
 import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.CollectionsApi;
 import org.apache.solr.client.solrj.request.CoresApi;
 import org.apache.solr.client.solrj.request.SystemInfoRequest;
 import org.apache.solr.client.solrj.response.SystemInfoResponse;
@@ -49,7 +48,6 @@ import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.util.EnvUtils;
-import org.apache.solr.common.util.NamedList;
 
 /** Utility class that holds various helper methods for the CLI. */
 public final class CLIUtils {
@@ -311,10 +309,11 @@ public final class CLIUtils {
       String solrUrl, String collection, String credentials) {
     boolean exists = false;
     try (var solrClient = getSolrClient(solrUrl, credentials)) {
-      NamedList<Object> existsCheckResult = solrClient.request(new 
CollectionAdminRequest.List());
-      @SuppressWarnings("unchecked")
-      List<String> collections = (List<String>) 
existsCheckResult.get("collections");
-      exists = collections != null && collections.contains(collection);
+      var response = new CollectionsApi.ListCollections().process(solrClient);
+      exists =
+          response != null
+              && response.collections != null
+              && response.collections.contains(collection);
     } catch (Exception exc) {
       // just ignore it since we're only interested in a positive result here
     }
diff --git a/solr/core/src/java/org/apache/solr/cli/CreateTool.java 
b/solr/core/src/java/org/apache/solr/cli/CreateTool.java
index 33ab28ef383..74778e7e490 100644
--- a/solr/core/src/java/org/apache/solr/cli/CreateTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/CreateTool.java
@@ -32,18 +32,13 @@ import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.CoreAdminRequest;
+import org.apache.solr.client.solrj.request.CollectionsApi;
+import org.apache.solr.client.solrj.request.CoresApi;
 import org.apache.solr.client.solrj.request.SystemInfoRequest;
-import org.apache.solr.client.solrj.response.CoreAdminResponse;
 import org.apache.solr.client.solrj.response.SystemInfoResponse;
-import org.apache.solr.client.solrj.response.json.JsonMapResponseParser;
 import org.apache.solr.cloud.ZkConfigSetService;
 import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.ConfigSetService;
-import org.noggit.CharArr;
-import org.noggit.JSONWriter;
 
 /** Supports create command in the bin/solr script. */
 public class CreateTool extends ToolBase {
@@ -182,14 +177,13 @@ public class CreateTool extends ToolBase {
               + coreInstanceDir.toAbsolutePath());
     }
 
-    echoIfVerbose("\nCreating new core '" + coreName + "' using 
CoreAdminRequest");
+    echoIfVerbose("\nCreating new core '" + coreName + "' using V2 Cores API");
 
     try {
-      CoreAdminResponse res = CoreAdminRequest.createCore(coreName, coreName, 
solrClient);
-      if (isVerbose()) {
-        echo(res.jsonStr());
-        echo("\n");
-      }
+      var req = new CoresApi.CreateCore();
+      req.setName(coreName);
+      req.setInstanceDir(coreName);
+      req.process(solrClient);
       echo(String.format(Locale.ROOT, "\nCreated new core '%s'", coreName));
 
     } catch (Exception e) {
@@ -277,31 +271,25 @@ public class CreateTool extends ToolBase {
       throw new IllegalStateException(
           "\nCollection '"
               + collectionName
-              + "' already exists!\nChecked collection existence using 
CollectionAdminRequest");
+              + "' already exists!\nChecked collection existence using V2 
Collections API");
     }
 
     // doesn't seem to exist ... try to create
-    echoIfVerbose(
-        "\nCreating new collection '" + collectionName + "' using 
CollectionAdminRequest");
+    echoIfVerbose("\nCreating new collection '" + collectionName + "' using V2 
Collections API");
 
-    NamedList<Object> response;
     try {
-      var req =
-          CollectionAdminRequest.createCollection(
-              collectionName, confName, numShards, replicationFactor);
-      req.setResponseParser(new JsonMapResponseParser());
-      response = cloudSolrClient.request(req);
+      var req = new CollectionsApi.CreateCollection();
+      req.setName(collectionName);
+      req.setConfig(confName);
+      req.setNumShards(numShards);
+      req.setReplicationFactor(replicationFactor);
+      var response = req.process(cloudSolrClient);
+      echoIfVerbose(response);
     } catch (SolrServerException sse) {
       throw new Exception(
           "Failed to create collection '" + collectionName + "' due to: " + 
sse.getMessage());
     }
 
-    if (isVerbose()) {
-      // pretty-print the response to stdout
-      CharArr arr = new CharArr();
-      new JSONWriter(arr, 2).write(response.asMap(10));
-      echo(arr.toString());
-    }
     String endMessage =
         String.format(
             Locale.ROOT,
diff --git a/solr/core/src/java/org/apache/solr/cli/DeleteTool.java 
b/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
index 2610e2af18d..77db2184a4c 100644
--- a/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
@@ -29,13 +29,9 @@ import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.CoreAdminRequest;
-import org.apache.solr.client.solrj.response.json.JsonMapResponseParser;
+import org.apache.solr.client.solrj.request.CollectionsApi;
+import org.apache.solr.client.solrj.request.CoresApi;
 import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.util.NamedList;
-import org.noggit.CharArr;
-import org.noggit.JSONWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -176,13 +172,12 @@ public class DeleteTool extends ToolBase {
       }
     }
 
-    echoIfVerbose("\nDeleting collection '" + collectionName + "' using 
CollectionAdminRequest");
+    echoIfVerbose("\nDeleting collection '" + collectionName + "' using V2 
Collections API");
 
-    NamedList<Object> response;
     try {
-      var req = CollectionAdminRequest.deleteCollection(collectionName);
-      req.setResponseParser(new JsonMapResponseParser());
-      response = cloudSolrClient.request(req);
+      var req = new CollectionsApi.DeleteCollection(collectionName);
+      var response = req.process(cloudSolrClient);
+      echoIfVerbose(response);
     } catch (SolrServerException sse) {
       throw new Exception(
           "Failed to delete collection '" + collectionName + "' due to: " + 
sse.getMessage());
@@ -202,38 +197,23 @@ public class DeleteTool extends ToolBase {
       }
     }
 
-    if (isVerbose() && response != null) {
-      // pretty-print the response to stdout
-      CharArr arr = new CharArr();
-      new JSONWriter(arr, 2).write(response.asMap(10));
-      echo(arr.toString());
-      echo("\n");
-    }
-
     echo(String.format(Locale.ROOT, "\nDeleted collection '%s'", 
collectionName));
   }
 
   protected void deleteCore(CommandLine cli, SolrClient solrClient) throws 
Exception {
     String coreName = cli.getOptionValue(COLLECTION_NAME_OPTION);
 
-    echo("\nDeleting core '" + coreName + "' using CoreAdminRequest\n");
+    echo("\nDeleting core '" + coreName + "' using V2 Cores API\n");
 
-    NamedList<Object> response;
     try {
-      CoreAdminRequest.Unload unloadRequest = new 
CoreAdminRequest.Unload(true);
-      unloadRequest.setDeleteIndex(true);
-      unloadRequest.setDeleteDataDir(true);
-      unloadRequest.setDeleteInstanceDir(true);
-      unloadRequest.setCoreName(coreName);
-      unloadRequest.setResponseParser(new JsonMapResponseParser());
-      response = solrClient.request(unloadRequest);
+      var req = new CoresApi.UnloadCore(coreName);
+      req.setDeleteIndex(true);
+      req.setDeleteDataDir(true);
+      req.setDeleteInstanceDir(true);
+      var response = req.process(solrClient);
+      echoIfVerbose(response);
     } catch (SolrServerException sse) {
       throw new Exception("Failed to delete core '" + coreName + "' due to: " 
+ sse.getMessage());
     }
-
-    if (response != null) {
-      echoIfVerbose((String) response.get("response"));
-      echoIfVerbose("\n");
-    }
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/cli/StatusTool.java 
b/solr/core/src/java/org/apache/solr/cli/StatusTool.java
index 1783e1bef1d..ca9391fca18 100644
--- a/solr/core/src/java/org/apache/solr/cli/StatusTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/StatusTool.java
@@ -31,10 +31,10 @@ import org.apache.commons.cli.OptionGroup;
 import org.apache.commons.cli.Options;
 import org.apache.solr.cli.SolrProcessManager.SolrProcess;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.ClusterApi;
+import org.apache.solr.client.solrj.request.CollectionsApi;
 import org.apache.solr.client.solrj.request.SystemInfoRequest;
 import org.apache.solr.client.solrj.response.SystemInfoResponse;
-import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.URLUtil;
 import org.noggit.CharArr;
 import org.noggit.JSONWriter;
@@ -318,23 +318,24 @@ public class StatusTool extends ToolBase {
   }
 
   /**
-   * Calls the CLUSTERSTATUS endpoint in Solr to get basic status information 
about the SolrCloud
-   * cluster.
+   * Calls V2 API endpoints to get basic status information about the 
SolrCloud cluster.
+   *
+   * <p>Uses GET /cluster/nodes for live node count and GET /collections for 
collection count.
    */
-  @SuppressWarnings("unchecked")
   private static Map<String, String> getCloudStatus(SolrClient solrClient, 
String zkHost)
       throws Exception {
     Map<String, String> cloudStatus = new LinkedHashMap<>();
     cloudStatus.put("ZooKeeper", (zkHost != null) ? zkHost : "?");
 
-    // TODO add booleans to request just what we want; not everything
-    NamedList<Object> json = solrClient.request(new 
CollectionAdminRequest.ClusterStatus());
-
-    List<String> liveNodes = (List<String>) json._get(List.of("cluster", 
"live_nodes"), null);
-    cloudStatus.put("liveNodes", String.valueOf(liveNodes.size()));
+    var nodesResponse = new ClusterApi.ListClusterNodes().process(solrClient);
+    var liveNodes = nodesResponse != null ? nodesResponse.nodes : null;
+    cloudStatus.put("liveNodes", String.valueOf(liveNodes != null ? 
liveNodes.size() : 0));
 
-    // TODO get this as a metric from the metrics API instead, or something 
else.
-    var collections = (Map<String, Object>) json._get(List.of("cluster", 
"collections"), null);
+    var collectionsResponse = new 
CollectionsApi.ListCollections().process(solrClient);
+    var collections =
+        collectionsResponse != null && collectionsResponse.collections != null
+            ? collectionsResponse.collections
+            : List.of();
     cloudStatus.put("collections", String.valueOf(collections.size()));
 
     return cloudStatus;
diff --git a/solr/core/src/java/org/apache/solr/cli/ToolBase.java 
b/solr/core/src/java/org/apache/solr/cli/ToolBase.java
index 96ed3138ace..9d3734c3d93 100644
--- a/solr/core/src/java/org/apache/solr/cli/ToolBase.java
+++ b/solr/core/src/java/org/apache/solr/cli/ToolBase.java
@@ -17,9 +17,11 @@
 
 package org.apache.solr.cli;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.OptionGroup;
 import org.apache.commons.cli.Options;
+import org.apache.solr.client.solrj.request.json.JacksonContentWriter;
 import org.apache.solr.util.StartupLoggingUtils;
 
 public abstract class ToolBase implements Tool {
@@ -43,6 +45,15 @@ public abstract class ToolBase implements Tool {
     }
   }
 
+  protected void echoIfVerbose(Object response) throws JsonProcessingException 
{
+    if (verbose && response != null) {
+      echo(
+          JacksonContentWriter.DEFAULT_MAPPER
+              .writerWithDefaultPrettyPrinter()
+              .writeValueAsString(response));
+    }
+  }
+
   protected void echo(final String msg) {
     runtime.println(msg);
   }

Reply via email to