This is an automated email from the ASF dual-hosted git repository.

gerlowskija pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new 6545499f396 SOLR-16825: Migrate v2 API definitions to 'api' module, pt 
9 (#2870)
6545499f396 is described below

commit 6545499f3963d7e58b0657f6164c6c30d59e3bb5
Author: Jason Gerlowski <[email protected]>
AuthorDate: Thu Nov 21 11:16:22 2024 -0800

    SOLR-16825: Migrate v2 API definitions to 'api' module, pt 9 (#2870)
    
    SOLR-16825 added a new gradle module, 'api', which holds v2 API
    definitions as interfaces.  This allows us to generate an OAS (and other
    artifacts) as a part of the solrj build.  But these artifacts only cover
    the v2 APIs present in the 'api' module.
    
    This commit moves all remaining v2 API defining annotations to new
    interfaces in the 'api' module. These APIs are now reflected in our OAS
    and generated artifacts.
---
 ...tionBackupApi.java => CollectionBackupApi.java} |  37 ++--
 .../api/endpoint/CollectionSnapshotApis.java       |  89 ++++++++++
 .../api/endpoint/CreateCollectionSnapshotApi.java  |  46 -----
 .../api/endpoint/DeleteCollectionSnapshotApi.java  |  50 ------
 ...onBackupApi.java => ReplicationBackupApis.java} |  28 +--
 .../client/api/endpoint/ZooKeeperReadApis.java     |  69 ++++++++
 .../model/DeleteCollectionSnapshotResponse.java    |   6 +-
 .../client/api/model}/ExperimentalResponse.java    |   3 +-
 .../model/ListCollectionSnapshotsResponse.java}    |  22 +--
 .../api/model/ReplicationBackupRequestBody.java    |  56 ++++++
 .../api/model/ReplicationBackupResponse.java}      |  26 +--
 ...onse.java => RestoreCollectionRequestBody.java} |  32 ++--
 .../client/api/model/ZooKeeperFileResponse.java}   |  19 +--
 .../api/model/ZooKeeperListChildrenResponse.java   |  44 +++++
 .../solr/client/api/model/ZooKeeperStat.java}      |  46 +++--
 .../java/org/apache/solr/core/CoreContainer.java   |   4 +-
 .../solr/handler/admin/CollectionsHandler.java     |  22 +--
 .../{ZookeeperReadAPI.java => ZookeeperRead.java}  | 190 ++++++---------------
 .../handler/admin/api/CreateCollectionBackup.java  |   4 +-
 .../admin/api/CreateCollectionSnapshot.java        |   5 +-
 .../admin/api/DeleteCollectionSnapshot.java        |   5 +-
 ...pshotsAPI.java => ListCollectionSnapshots.java} |  40 ++---
 ...reCollectionAPI.java => RestoreCollection.java} |  70 +++-----
 .../solr/handler/admin/api/SnapshotBackupAPI.java  |  98 ++---------
 .../solr/jersey/MediaTypeOverridingFilter.java     |   4 +-
 .../solr/handler/admin/ZookeeperReadAPITest.java   |  11 +-
 .../admin/api/RestoreCollectionAPITest.java        |  20 +--
 .../handler/admin/api/SnapshotBackupAPITest.java   |   3 +-
 28 files changed, 523 insertions(+), 526 deletions(-)

diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionBackupApi.java
 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionBackupApi.java
similarity index 54%
copy from 
solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionBackupApi.java
copy to 
solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionBackupApi.java
index 3e08bad6d45..5c6c9d3bbbe 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionBackupApi.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionBackupApi.java
@@ -21,23 +21,38 @@ import jakarta.ws.rs.POST;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.PathParam;
 import org.apache.solr.client.api.model.CreateCollectionBackupRequestBody;
+import org.apache.solr.client.api.model.RestoreCollectionRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 
 /**
  * V2 API definition for creating a new "backup" of a specified collection
  *
  * <p>This API is analogous to the v1 /admin/collections?action=BACKUP command.
  */
-@Path("/collections/{collectionName}/backups/{backupName}/versions")
-public interface CreateCollectionBackupApi {
+public interface CollectionBackupApi {
 
-  @POST
-  @Operation(
-      summary = "Creates a new backup point for a collection",
-      tags = {"collection-backups"})
-  SolrJerseyResponse createCollectionBackup(
-      @PathParam("collectionName") String collectionName,
-      @PathParam("backupName") String backupName,
-      CreateCollectionBackupRequestBody requestBody)
-      throws Exception;
+  @Path("/collections/{collectionName}/backups/{backupName}/versions")
+  interface Create {
+    @POST
+    @Operation(
+        summary = "Creates a new backup point for a collection",
+        tags = {"collection-backups"})
+    SolrJerseyResponse createCollectionBackup(
+        @PathParam("collectionName") String collectionName,
+        @PathParam("backupName") String backupName,
+        CreateCollectionBackupRequestBody requestBody)
+        throws Exception;
+  }
+
+  @Path("/backups/{backupName}/restore")
+  interface Restore {
+    @POST
+    @Operation(
+        summary = "Restores an existing backup point to a (potentially new) 
collection.",
+        tags = {"collection-backups"})
+    SubResponseAccumulatingJerseyResponse restoreCollection(
+        @PathParam("backupName") String backupName, 
RestoreCollectionRequestBody requestBody)
+        throws Exception;
+  }
 }
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionSnapshotApis.java
 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionSnapshotApis.java
new file mode 100644
index 00000000000..21c1dc44224
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionSnapshotApis.java
@@ -0,0 +1,89 @@
+/*
+ * 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 io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.CreateCollectionSnapshotRequestBody;
+import org.apache.solr.client.api.model.CreateCollectionSnapshotResponse;
+import org.apache.solr.client.api.model.DeleteCollectionSnapshotResponse;
+import org.apache.solr.client.api.model.ListCollectionSnapshotsResponse;
+
+/** V2 API definitions for creating, accessing, and deleting collection-level 
snapshots. */
+public interface CollectionSnapshotApis {
+
+  @Path("/collections/{collName}/snapshots")
+  interface Create {
+    @POST
+    @Path("/{snapshotName}")
+    @Operation(
+        summary = "Creates a new snapshot of the specified collection.",
+        tags = {"collection-snapshots"})
+    CreateCollectionSnapshotResponse createCollectionSnapshot(
+        @Parameter(description = "The name of the collection.", required = 
true)
+            @PathParam("collName")
+            String collName,
+        @Parameter(description = "The name of the snapshot to be created.", 
required = true)
+            @PathParam("snapshotName")
+            String snapshotName,
+        @RequestBody(description = "Contains user provided parameters", 
required = true)
+            CreateCollectionSnapshotRequestBody requestBody)
+        throws Exception;
+  }
+
+  @Path("/collections/{collName}/snapshots/{snapshotName}")
+  interface Delete {
+    @DELETE
+    @Operation(
+        summary = "Delete an existing collection-snapshot by name.",
+        tags = {"collection-snapshots"})
+    DeleteCollectionSnapshotResponse deleteCollectionSnapshot(
+        @Parameter(description = "The name of the collection.", required = 
true)
+            @PathParam("collName")
+            String collName,
+        @Parameter(description = "The name of the snapshot to be deleted.", 
required = true)
+            @PathParam("snapshotName")
+            String snapshotName,
+        @Parameter(description = "A flag that treats the collName parameter as 
a collection alias.")
+            @DefaultValue("false")
+            @QueryParam("followAliases")
+            boolean followAliases,
+        @QueryParam("async") String asyncId)
+        throws Exception;
+  }
+
+  @Path("/collections/{collName}/snapshots")
+  interface List {
+    @GET
+    @Operation(
+        summary = "List the snapshots available for a specified collection.",
+        tags = {"collection-snapshots"})
+    ListCollectionSnapshotsResponse listSnapshots(
+        @Parameter(description = "The name of the collection.", required = 
true)
+            @PathParam("collName")
+            String collName)
+        throws Exception;
+  }
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionSnapshotApi.java
 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionSnapshotApi.java
deleted file mode 100644
index 70240a02e65..00000000000
--- 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionSnapshotApi.java
+++ /dev/null
@@ -1,46 +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.client.api.endpoint;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.parameters.RequestBody;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import org.apache.solr.client.api.model.CreateCollectionSnapshotRequestBody;
-import org.apache.solr.client.api.model.CreateCollectionSnapshotResponse;
-
-/** V2 API definition for creating a collection-level snapshot. */
-@Path("/collections/{collName}/snapshots")
-public interface CreateCollectionSnapshotApi {
-  @POST
-  @Path("/{snapshotName}")
-  @Operation(
-      summary = "Creates a new snapshot of the specified collection.",
-      tags = {"collection-snapshots"})
-  CreateCollectionSnapshotResponse createCollectionSnapshot(
-      @Parameter(description = "The name of the collection.", required = true)
-          @PathParam("collName")
-          String collName,
-      @Parameter(description = "The name of the snapshot to be created.", 
required = true)
-          @PathParam("snapshotName")
-          String snapshotName,
-      @RequestBody(description = "Contains user provided parameters", required 
= true)
-          CreateCollectionSnapshotRequestBody requestBody)
-      throws Exception;
-}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java
 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java
deleted file mode 100644
index 8fed7eb4ff9..00000000000
--- 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java
+++ /dev/null
@@ -1,50 +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.client.api.endpoint;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import jakarta.ws.rs.DELETE;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.QueryParam;
-import org.apache.solr.client.api.model.DeleteCollectionSnapshotResponse;
-
-@Path("/collections/{collName}/snapshots/{snapshotName}")
-public interface DeleteCollectionSnapshotApi {
-
-  /** This API is analogous to V1's (POST 
/solr/admin/collections?action=DELETESNAPSHOT) */
-  @DELETE
-  @Operation(
-      summary = "Delete an existing collection-snapshot by name.",
-      tags = {"collection-snapshots"})
-  DeleteCollectionSnapshotResponse deleteCollectionSnapshot(
-      @Parameter(description = "The name of the collection.", required = true)
-          @PathParam("collName")
-          String collName,
-      @Parameter(description = "The name of the snapshot to be deleted.", 
required = true)
-          @PathParam("snapshotName")
-          String snapshotName,
-      @Parameter(description = "A flag that treats the collName parameter as a 
collection alias.")
-          @DefaultValue("false")
-          @QueryParam("followAliases")
-          boolean followAliases,
-      @QueryParam("async") String asyncId)
-      throws Exception;
-}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionBackupApi.java
 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationBackupApis.java
similarity index 56%
rename from 
solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionBackupApi.java
rename to 
solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationBackupApis.java
index 3e08bad6d45..ef28f902ecc 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionBackupApi.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationBackupApis.java
@@ -17,27 +17,27 @@
 package org.apache.solr.client.api.endpoint;
 
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
 import jakarta.ws.rs.POST;
 import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import org.apache.solr.client.api.model.CreateCollectionBackupRequestBody;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.model.ReplicationBackupRequestBody;
+import org.apache.solr.client.api.model.ReplicationBackupResponse;
+import org.apache.solr.client.api.util.CoreApiParameters;
 
 /**
- * V2 API definition for creating a new "backup" of a specified collection
+ * V2 endpoint for Backup API used for User-Managed clusters and Single-Node 
Installation.
  *
- * <p>This API is analogous to the v1 /admin/collections?action=BACKUP command.
+ * @see ReplicationApis
  */
-@Path("/collections/{collectionName}/backups/{backupName}/versions")
-public interface CreateCollectionBackupApi {
+@Path("/cores/{coreName}/replication")
+public interface ReplicationBackupApis {
 
   @POST
+  @CoreApiParameters
+  @Path("/backups")
   @Operation(
-      summary = "Creates a new backup point for a collection",
-      tags = {"collection-backups"})
-  SolrJerseyResponse createCollectionBackup(
-      @PathParam("collectionName") String collectionName,
-      @PathParam("backupName") String backupName,
-      CreateCollectionBackupRequestBody requestBody)
-      throws Exception;
+      summary = "Create a backup of a single core using Solr's 'Replication 
Handler'",
+      tags = {"replication-backups"})
+  ReplicationBackupResponse createBackup(
+      @RequestBody ReplicationBackupRequestBody backupReplicationPayload);
 }
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ZooKeeperReadApis.java 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ZooKeeperReadApis.java
new file mode 100644
index 00000000000..f41e8de3d63
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ZooKeeperReadApis.java
@@ -0,0 +1,69 @@
+/*
+ * 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 io.swagger.v3.oas.annotations.Parameter;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+import org.apache.solr.client.api.model.ZooKeeperFileResponse;
+import org.apache.solr.client.api.model.ZooKeeperListChildrenResponse;
+
+/** V2 API definitions for Solr's ZooKeeper ready-proxy endpoint */
+@Path("/cluster/zookeeper/")
+public interface ZooKeeperReadApis {
+
+  @GET
+  @Path("/data{zkPath:.+}")
+  @Operation(
+      summary = "Return the data stored in a specified ZooKeeper node",
+      tags = {"zookeeper-read"})
+  @Produces({"application/vnd.apache.solr.raw", MediaType.APPLICATION_JSON})
+  ZooKeeperFileResponse readNode(
+      @Parameter(description = "The path of the node to read from ZooKeeper") 
@PathParam("zkPath")
+          String zkPath);
+
+  // The 'Operation' annotation is omitted intentionally here to ensure this 
API isn't picked up in
+  // the OpenAPI spec and consequent code-generation.  The server side needs 
this method to be
+  // different from 'readNode' above for security reasons (more privileges are 
needed to access
+  // security.json), but it's the same logical API expressed by the 'readNode' 
signature above.
+  @GET
+  @Path("/data/security.json")
+  @Produces({"application/vnd.apache.solr.raw", MediaType.APPLICATION_JSON})
+  ZooKeeperFileResponse readSecurityJsonNode();
+
+  @GET
+  @Path("/children{zkPath:.*}")
+  @Produces({"application/json", "application/javabin"})
+  @Operation(
+      summary = "List and stat all children of a specified ZooKeeper node",
+      tags = {"zookeeper-read"})
+  ZooKeeperListChildrenResponse listNodes(
+      @Parameter(description = "The path of the ZooKeeper node to stat and 
list children of")
+          @PathParam("zkPath")
+          String zkPath,
+      @Parameter(
+              description =
+                  "Controls whether stat information for child nodes is 
included in the response. 'true' by default.")
+          @QueryParam("children")
+          Boolean includeChildren)
+      throws Exception;
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
index 569d4fbd096..905b0937f1d 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
@@ -21,12 +21,8 @@ import static 
org.apache.solr.client.api.model.Constants.COLLECTION;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
-import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
 
-/**
- * The Response for {@link 
DeleteCollectionSnapshotApi#deleteCollectionSnapshot(String, String,
- * boolean, String)}
- */
+/** The Response for {@link 
org.apache.solr.client.api.endpoint.CollectionSnapshotApis.Delete} */
 public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
   @Schema(description = "The name of the collection.")
   @JsonProperty(COLLECTION)
diff --git 
a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java 
b/solr/api/src/java/org/apache/solr/client/api/model/ExperimentalResponse.java
similarity index 92%
copy from solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
copy to 
solr/api/src/java/org/apache/solr/client/api/model/ExperimentalResponse.java
index 7824b7bc314..c460537e3fb 100644
--- a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/ExperimentalResponse.java
@@ -15,10 +15,9 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
+package org.apache.solr.client.api.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
 
 /**
  * {@link SolrJerseyResponse} implementation with a warning field indicating 
that the format may
diff --git 
a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java 
b/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionSnapshotsResponse.java
similarity index 60%
copy from solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
copy to 
solr/api/src/java/org/apache/solr/client/api/model/ListCollectionSnapshotsResponse.java
index 7824b7bc314..962cfda7014 100644
--- a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionSnapshotsResponse.java
@@ -14,18 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.apache.solr.jersey;
+package org.apache.solr.client.api.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.Map;
 
-/**
- * {@link SolrJerseyResponse} implementation with a warning field indicating 
that the format may
- * change
- */
-public class ExperimentalResponse extends SolrJerseyResponse {
-  @JsonProperty("WARNING")
-  public String warning =
-      "This response format is experimental.  It is likely to change in the 
future.";
+/** The Response for the v2 "list collection snapshots" API */
+public class ListCollectionSnapshotsResponse extends AsyncJerseyResponse {
+
+  // TODO In practice, map values are of the CollectionSnapshotMetaData type, 
but that cannot be
+  // used here until the class is made into more of a POJO and can join the 
'api' module here
+  @Schema(description = "The snapshots for the collection.")
+  @JsonProperty("snapshots")
+  public Map<String, Object> snapshots;
 }
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/ReplicationBackupRequestBody.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationBackupRequestBody.java
new file mode 100644
index 00000000000..65b93a1d829
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationBackupRequestBody.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+public class ReplicationBackupRequestBody {
+
+  public ReplicationBackupRequestBody() {}
+
+  public ReplicationBackupRequestBody(
+      String location, String name, int numberToKeep, String repository, 
String commitName) {
+    this.location = location;
+    this.name = name;
+    this.numberToKeep = numberToKeep;
+    this.repository = repository;
+    this.commitName = commitName;
+  }
+
+  @Schema(description = "The path where the backup will be created")
+  @JsonProperty
+  public String location;
+
+  @Schema(description = "The backup will be created in a directory called 
snapshot.<name>")
+  @JsonProperty
+  public String name;
+
+  @Schema(description = "The number of backups to keep.")
+  @JsonProperty
+  public int numberToKeep;
+
+  @Schema(description = "The name of the repository to be used for e backup.")
+  @JsonProperty
+  public String repository;
+
+  @Schema(
+      description =
+          "The name of the commit which was used while taking a snapshot using 
the CREATESNAPSHOT command.")
+  @JsonProperty
+  public String commitName;
+}
diff --git 
a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java 
b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationBackupResponse.java
similarity index 67%
copy from solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
copy to 
solr/api/src/java/org/apache/solr/client/api/model/ReplicationBackupResponse.java
index 7824b7bc314..15581fa734e 100644
--- a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationBackupResponse.java
@@ -14,18 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.apache.solr.jersey;
+package org.apache.solr.client.api.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
 
-/**
- * {@link SolrJerseyResponse} implementation with a warning field indicating 
that the format may
- * change
- */
-public class ExperimentalResponse extends SolrJerseyResponse {
-  @JsonProperty("WARNING")
-  public String warning =
-      "This response format is experimental.  It is likely to change in the 
future.";
+/** Response for the v2 "replication backup" API */
+public class ReplicationBackupResponse extends SolrJerseyResponse {
+
+  @JsonProperty("result")
+  public Object result;
+
+  @JsonProperty("status")
+  public String status;
+
+  @JsonProperty("message")
+  public String message;
+
+  @JsonProperty("exception")
+  public Exception exception;
 }
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/RestoreCollectionRequestBody.java
similarity index 58%
copy from 
solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
copy to 
solr/api/src/java/org/apache/solr/client/api/model/RestoreCollectionRequestBody.java
index 569d4fbd096..9a592d41e3b 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/RestoreCollectionRequestBody.java
@@ -14,29 +14,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.api.model;
 
-import static org.apache.solr.client.api.model.Constants.COLLECTION;
-
 import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
-import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
 
-/**
- * The Response for {@link 
DeleteCollectionSnapshotApi#deleteCollectionSnapshot(String, String,
- * boolean, String)}
- */
-public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
-  @Schema(description = "The name of the collection.")
-  @JsonProperty(COLLECTION)
+/** Request body for the v2 "restore collection" API. */
+public class RestoreCollectionRequestBody {
+
+  @JsonProperty(required = true)
   public String collection;
 
-  @Schema(description = "The name of the snapshot to be deleted.")
-  @JsonProperty("snapshot")
-  public String snapshotName;
+  @JsonProperty public String location;
+  @JsonProperty public String repository;
+  @JsonProperty public Integer backupId;
+
+  @Schema(
+      description =
+          "Parameters to be used for any collections created by this restore.  
Only used if the collection specified by the 'collection' property does not 
exist.",
+      name = "createCollectionParams")
+  @JsonProperty("create-collection")
+  public CreateCollectionRequestBody createCollectionParams;
 
-  @Schema(description = "A flag that treats the collName parameter as a 
collection alias.")
-  @JsonProperty("followAliases")
-  public boolean followAliases;
+  @JsonProperty public String async;
 }
diff --git 
a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java 
b/solr/api/src/java/org/apache/solr/client/api/model/ZooKeeperFileResponse.java
similarity index 67%
copy from solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
copy to 
solr/api/src/java/org/apache/solr/client/api/model/ZooKeeperFileResponse.java
index 7824b7bc314..d09302fa168 100644
--- a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/ZooKeeperFileResponse.java
@@ -14,18 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.apache.solr.jersey;
+package org.apache.solr.client.api.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
 
-/**
- * {@link SolrJerseyResponse} implementation with a warning field indicating 
that the format may
- * change
- */
-public class ExperimentalResponse extends SolrJerseyResponse {
-  @JsonProperty("WARNING")
-  public String warning =
-      "This response format is experimental.  It is likely to change in the 
future.";
+public class ZooKeeperFileResponse extends SolrJerseyResponse {
+  // TODO Should be switched over to using StreamingOutput as a part of 
SOLR-17562
+  @JsonProperty("content") // A flag value that RawResponseWriter handles 
specially
+  public Object output;
+
+  @JsonProperty("zkData")
+  public String zkData;
 }
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/ZooKeeperListChildrenResponse.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/ZooKeeperListChildrenResponse.java
new file mode 100644
index 00000000000..be7a69575dc
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/ZooKeeperListChildrenResponse.java
@@ -0,0 +1,44 @@
+/*
+ * 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.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ZooKeeperListChildrenResponse extends ExperimentalResponse {
+  @JsonProperty("stat")
+  public ZooKeeperStat stat;
+
+  // TODO Currently the list response (when child information is fetched) 
consists primarily of an
+  //  object with only one key - the name of the root node - with separate 
objects under there for
+  //  each child.  The additional nesting under the root node doesn't serve 
much purpose afaict
+  //  and should be removed.
+  public Map<String, Map<String, ZooKeeperStat>> unknownFields = new 
HashMap<>();
+
+  @JsonAnyGetter
+  public Map<String, Map<String, ZooKeeperStat>> unknownProperties() {
+    return unknownFields;
+  }
+
+  @JsonAnySetter
+  public void setUnknownProperty(String field, Map<String, ZooKeeperStat> 
value) {
+    unknownFields.put(field, value);
+  }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java 
b/solr/api/src/java/org/apache/solr/client/api/model/ZooKeeperStat.java
similarity index 55%
rename from solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
rename to solr/api/src/java/org/apache/solr/client/api/model/ZooKeeperStat.java
index 7824b7bc314..302b3885095 100644
--- a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/ZooKeeperStat.java
@@ -14,18 +14,42 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.apache.solr.jersey;
+package org.apache.solr.client.api.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
 
-/**
- * {@link SolrJerseyResponse} implementation with a warning field indicating 
that the format may
- * change
- */
-public class ExperimentalResponse extends SolrJerseyResponse {
-  @JsonProperty("WARNING")
-  public String warning =
-      "This response format is experimental.  It is likely to change in the 
future.";
+/** Represents the data returned by a ZooKeeper 'stat' call */
+public class ZooKeeperStat {
+  @JsonProperty("version")
+  public int version;
+
+  @JsonProperty("aversion")
+  public int aversion;
+
+  @JsonProperty("children")
+  public int children;
+
+  @JsonProperty("ctime")
+  public long ctime;
+
+  @JsonProperty("cversion")
+  public int cversion;
+
+  @JsonProperty("czxid")
+  public long czxid;
+
+  @JsonProperty("ephemeralOwner")
+  public long ephemeralOwner;
+
+  @JsonProperty("mtime")
+  public long mtime;
+
+  @JsonProperty("mzxid")
+  public long mzxid;
+
+  @JsonProperty("pzxid")
+  public long pzxid;
+
+  @JsonProperty("dataLength")
+  public int dataLength;
 }
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java 
b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index ed49e0740ea..e9812aa6b7c 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -126,7 +126,7 @@ import org.apache.solr.handler.admin.SecurityConfHandler;
 import org.apache.solr.handler.admin.SecurityConfHandlerLocal;
 import org.apache.solr.handler.admin.SecurityConfHandlerZk;
 import org.apache.solr.handler.admin.ZookeeperInfoHandler;
-import org.apache.solr.handler.admin.ZookeeperReadAPI;
+import org.apache.solr.handler.admin.ZookeeperRead;
 import org.apache.solr.handler.admin.ZookeeperStatusHandler;
 import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.handler.component.ShardHandlerFactory;
@@ -876,7 +876,7 @@ public class CoreContainer {
       packageLoader = new SolrPackageLoader(this);
       registerV2ApiIfEnabled(packageLoader.getPackageAPI().editAPI);
       registerV2ApiIfEnabled(packageLoader.getPackageAPI().readAPI);
-      registerV2ApiIfEnabled(ZookeeperReadAPI.class);
+      registerV2ApiIfEnabled(ZookeeperRead.class);
     }
 
     MDCLoggingContext.setNode(this);
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index 42fd24261ce..3bcc0ce9c3f 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -126,6 +126,7 @@ import 
org.apache.solr.client.api.model.CreateAliasRequestBody;
 import org.apache.solr.client.api.model.CreateCollectionSnapshotRequestBody;
 import org.apache.solr.client.api.model.CreateCollectionSnapshotResponse;
 import org.apache.solr.client.api.model.InstallShardDataRequestBody;
+import org.apache.solr.client.api.model.ListCollectionSnapshotsResponse;
 import org.apache.solr.client.api.model.ReplaceNodeRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.api.model.UpdateAliasPropertiesRequestBody;
@@ -193,7 +194,7 @@ import org.apache.solr.handler.admin.api.ForceLeader;
 import org.apache.solr.handler.admin.api.InstallShardData;
 import org.apache.solr.handler.admin.api.ListAliases;
 import org.apache.solr.handler.admin.api.ListCollectionBackups;
-import org.apache.solr.handler.admin.api.ListCollectionSnapshotsAPI;
+import org.apache.solr.handler.admin.api.ListCollectionSnapshots;
 import org.apache.solr.handler.admin.api.ListCollections;
 import org.apache.solr.handler.admin.api.MigrateDocsAPI;
 import org.apache.solr.handler.admin.api.MigrateReplicas;
@@ -203,7 +204,7 @@ import 
org.apache.solr.handler.admin.api.RebalanceLeadersAPI;
 import org.apache.solr.handler.admin.api.ReloadCollectionAPI;
 import org.apache.solr.handler.admin.api.RenameCollection;
 import org.apache.solr.handler.admin.api.ReplaceNode;
-import org.apache.solr.handler.admin.api.RestoreCollectionAPI;
+import org.apache.solr.handler.admin.api.RestoreCollection;
 import org.apache.solr.handler.admin.api.SplitShardAPI;
 import org.apache.solr.handler.admin.api.SyncShard;
 import org.apache.solr.handler.api.V2ApiUtils;
@@ -1067,7 +1068,7 @@ public class CollectionsHandler extends 
RequestHandlerBase implements Permission
     RESTORE_OP(
         RESTORE,
         (req, rsp, h) -> {
-          final var response = RestoreCollectionAPI.invokeFromV1Params(req, 
rsp, h.coreContainer);
+          final var response = RestoreCollection.invokeFromV1Params(req, rsp, 
h.coreContainer);
           V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response);
           return null;
         }),
@@ -1152,15 +1153,16 @@ public class CollectionsHandler extends 
RequestHandlerBase implements Permission
         (req, rsp, h) -> {
           req.getParams().required().check(COLLECTION_PROP);
 
-          final ListCollectionSnapshotsAPI listCollectionSnapshotsAPI =
-              new ListCollectionSnapshotsAPI(h.coreContainer, req, rsp);
+          final ListCollectionSnapshots listCollectionSnapshotsAPI =
+              new ListCollectionSnapshots(h.coreContainer, req, rsp);
 
-          final ListCollectionSnapshotsAPI.ListSnapshotsResponse response =
+          final ListCollectionSnapshotsResponse response =
               
listCollectionSnapshotsAPI.listSnapshots(req.getParams().get(COLLECTION_PROP));
 
           NamedList<Object> snapshots = new NamedList<>();
-          for (CollectionSnapshotMetaData meta : response.snapshots.values()) {
-            snapshots.add(meta.getName(), meta.toNamedList());
+          for (Object meta : response.snapshots.values()) {
+            final var metaTyped = (CollectionSnapshotMetaData) meta;
+            snapshots.add(metaTyped.getName(), metaTyped.toNamedList());
           }
 
           rsp.add(SolrSnapshotManager.SNAPSHOTS_INFO, snapshots);
@@ -1379,13 +1381,13 @@ public class CollectionsHandler extends 
RequestHandlerBase implements Permission
         ReplaceNode.class,
         MigrateReplicas.class,
         BalanceReplicas.class,
-        RestoreCollectionAPI.class,
+        RestoreCollection.class,
         SyncShard.class,
         CollectionProperty.class,
         DeleteNode.class,
         ListAliases.class,
         AliasProperty.class,
-        ListCollectionSnapshotsAPI.class,
+        ListCollectionSnapshots.class,
         CreateCollectionSnapshot.class,
         DeleteCollectionSnapshot.class);
   }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java 
b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperRead.java
similarity index 51%
rename from 
solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/ZookeeperRead.java
index c3e2c4c6a93..8ded06a7a01 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperRead.java
@@ -17,112 +17,77 @@
 
 package org.apache.solr.handler.admin;
 
-import static org.apache.solr.response.RawResponseWriter.CONTENT;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.SECURITY_READ_PERM;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.ZK_READ_PERM;
 
-import com.fasterxml.jackson.annotation.JsonAnyGetter;
-import com.fasterxml.jackson.annotation.JsonAnySetter;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Parameter;
 import jakarta.inject.Inject;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.MediaType;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.endpoint.ZooKeeperReadApis;
+import org.apache.solr.client.api.model.ZooKeeperFileResponse;
+import org.apache.solr.client.api.model.ZooKeeperListChildrenResponse;
+import org.apache.solr.client.api.model.ZooKeeperStat;
 import org.apache.solr.client.solrj.impl.BinaryResponseParser;
 import org.apache.solr.client.solrj.impl.XMLResponseParser;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.util.ContentStream;
 import org.apache.solr.common.util.ContentStreamBase;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.api.AdminAPIBase;
-import org.apache.solr.jersey.ExperimentalResponse;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.response.RawResponseWriter;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.data.Stat;
 
 /**
- * Exposes the content of the Zookeeper This is an expert feature that exposes 
the data inside the
- * back end zookeeper.This API may change or be removed in future versions. 
This is not a public
- * API. The data that is returned is not guaranteed to remain same across 
releases, as the data
- * stored in Zookeeper may change from time to time.
+ * v2 API definition exposing read-content in Zookeeper.
+ *
+ * <p>This is an expert feature that exposes the data inside the back end 
zookeeper.This API may
+ * change or be removed in future versions. This is not a public API. The data 
that is returned is
+ * not guaranteed to remain same across releases, as the data stored in 
Zookeeper may change from
+ * time to time.
  *
  * @lucene.experimental
  */
-@Path("/cluster/zookeeper/")
-public class ZookeeperReadAPI extends AdminAPIBase {
+public class ZookeeperRead extends AdminAPIBase implements ZooKeeperReadApis {
+
+  private static final String EMPTY = "empty";
+
   @Inject
-  public ZookeeperReadAPI(
-      CoreContainer coreContainer, SolrQueryRequest req, SolrQueryResponse 
rsp) {
+  public ZookeeperRead(CoreContainer coreContainer, SolrQueryRequest req, 
SolrQueryResponse rsp) {
     super(coreContainer, req, rsp);
   }
 
   /** Request contents of a znode, except security.json */
-  @GET
-  @Path("/data{zkPath:.+}")
-  @Produces({RawResponseWriter.CONTENT_TYPE, MediaType.APPLICATION_JSON})
+  @Override
   @PermissionName(ZK_READ_PERM)
-  public ZooKeeperFileResponse readNode(
-      @Parameter(description = "The path of the node to read from ZooKeeper") 
@PathParam("zkPath")
-          String zkPath) {
+  public ZooKeeperFileResponse readNode(String zkPath) {
     zkPath = sanitizeZkPath(zkPath);
     return readNodeAndAddToResponse(zkPath);
   }
 
   /** Request contents of the security.json node */
-  @GET
-  @Path("/data/security.json")
-  @Produces({RawResponseWriter.CONTENT_TYPE, MediaType.APPLICATION_JSON})
+  @Override
   @PermissionName(SECURITY_READ_PERM)
   public ZooKeeperFileResponse readSecurityJsonNode() {
     return readNodeAndAddToResponse("/security.json");
   }
 
-  private String sanitizeZkPath(String zkPath) {
-    if (zkPath == null || zkPath.isEmpty()) {
-      return "/";
-    } else if (zkPath.length() > 1 && zkPath.endsWith("/")) {
-      return zkPath.substring(0, zkPath.length() - 1);
-    }
-
-    return zkPath;
-  }
-
   /** List the children of a certain zookeeper znode */
-  @GET
-  @Path("/children{zkPath:.*}")
-  @Produces({"application/json", "application/javabin"})
+  @Override
   @PermissionName(ZK_READ_PERM)
-  public ListZkChildrenResponse listNodes(
-      @Parameter(description = "The path of the ZooKeeper node to stat and 
list children of")
-          @PathParam("zkPath")
-          String zkPath,
-      @Parameter(
-              description =
-                  "Controls whether stat information for child nodes is 
included in the response. 'true' by default.")
-          @QueryParam("children")
-          Boolean includeChildren)
+  public ZooKeeperListChildrenResponse listNodes(String zkPath, Boolean 
includeChildren)
       throws Exception {
-    final ListZkChildrenResponse listResponse =
-        instantiateJerseyResponse(ListZkChildrenResponse.class);
+    final ZooKeeperListChildrenResponse listResponse =
+        instantiateJerseyResponse(ZooKeeperListChildrenResponse.class);
 
     zkPath = sanitizeZkPath(zkPath);
     try {
       Stat stat = coreContainer.getZkController().getZkClient().exists(zkPath, 
null, true);
-      listResponse.stat = new AnnotatedStat(stat);
+      listResponse.stat = createAnnotatedStatFrom(stat);
       if (includeChildren != null && !includeChildren.booleanValue()) {
         return listResponse;
       }
@@ -140,9 +105,9 @@ public class ZookeeperReadAPI extends AdminAPIBase {
         }
       }
 
-      final Map<String, AnnotatedStat> childStats = new HashMap<>();
+      final Map<String, ZooKeeperStat> childStats = new HashMap<>();
       for (Map.Entry<String, Stat> e : stats.entrySet()) {
-        childStats.put(e.getKey(), new AnnotatedStat(e.getValue()));
+        childStats.put(e.getKey(), createAnnotatedStatFrom(e.getValue()));
       }
       listResponse.unknownFields.put(zkPath, childStats);
 
@@ -152,6 +117,16 @@ public class ZookeeperReadAPI extends AdminAPIBase {
     }
   }
 
+  private String sanitizeZkPath(String zkPath) {
+    if (zkPath == null || zkPath.isEmpty()) {
+      return "/";
+    } else if (zkPath.length() > 1 && zkPath.endsWith("/")) {
+      return zkPath.substring(0, zkPath.length() - 1);
+    }
+
+    return zkPath;
+  }
+
   /** Simple mime type guessing based on first character of the response */
   private String guessMime(byte firstByte) {
     switch (firstByte) {
@@ -193,85 +168,20 @@ public class ZookeeperReadAPI extends AdminAPIBase {
     return d;
   }
 
-  public static class ListZkChildrenResponse extends ExperimentalResponse {
-    @JsonProperty("stat")
-    public AnnotatedStat stat;
-
-    // TODO Currently the list response (when child information is fetched) 
consists primarily of an
-    //  object with only one key - the name of the root node - with separate 
objects under there for
-    //  each child.  The additional nesting under the root node doesn't serve 
much purpose afaict
-    //  and should be removed.
-    private Map<String, Map<String, AnnotatedStat>> unknownFields = new 
HashMap<>();
-
-    @JsonAnyGetter
-    public Map<String, Map<String, AnnotatedStat>> unknownProperties() {
-      return unknownFields;
-    }
-
-    @JsonAnySetter
-    public void setUnknownProperty(String field, Map<String, AnnotatedStat> 
value) {
-      unknownFields.put(field, value);
-    }
-  }
-
-  public static class AnnotatedStat implements JacksonReflectMapWriter {
-    @JsonProperty("version")
-    public int version;
-
-    @JsonProperty("aversion")
-    public int aversion;
-
-    @JsonProperty("children")
-    public int children;
-
-    @JsonProperty("ctime")
-    public long ctime;
-
-    @JsonProperty("cversion")
-    public int cversion;
-
-    @JsonProperty("czxid")
-    public long czxid;
-
-    @JsonProperty("ephemeralOwner")
-    public long ephemeralOwner;
-
-    @JsonProperty("mtime")
-    public long mtime;
-
-    @JsonProperty("mzxid")
-    public long mzxid;
-
-    @JsonProperty("pzxid")
-    public long pzxid;
-
-    @JsonProperty("dataLength")
-    public int dataLength;
-
-    public AnnotatedStat(Stat stat) {
-      this.version = stat.getVersion();
-      this.aversion = stat.getAversion();
-      this.children = stat.getNumChildren();
-      this.ctime = stat.getCtime();
-      this.cversion = stat.getCversion();
-      this.czxid = stat.getCzxid();
-      this.ephemeralOwner = stat.getEphemeralOwner();
-      this.mtime = stat.getMtime();
-      this.mzxid = stat.getMzxid();
-      this.pzxid = stat.getPzxid();
-      this.dataLength = stat.getDataLength();
-    }
-
-    public AnnotatedStat() {}
-  }
-
-  private static final String EMPTY = "empty";
-
-  public static class ZooKeeperFileResponse extends SolrJerseyResponse {
-    @JsonProperty(CONTENT) // A flag value that RawResponseWriter handles 
specially
-    public ContentStream output;
-
-    @JsonProperty("zkData")
-    public String zkData;
+  public static ZooKeeperStat createAnnotatedStatFrom(Stat stat) {
+    final var annotatedStat = new ZooKeeperStat();
+    annotatedStat.version = stat.getVersion();
+    annotatedStat.aversion = stat.getAversion();
+    annotatedStat.children = stat.getNumChildren();
+    annotatedStat.ctime = stat.getCtime();
+    annotatedStat.cversion = stat.getCversion();
+    annotatedStat.czxid = stat.getCzxid();
+    annotatedStat.ephemeralOwner = stat.getEphemeralOwner();
+    annotatedStat.mtime = stat.getMtime();
+    annotatedStat.mzxid = stat.getMzxid();
+    annotatedStat.pzxid = stat.getPzxid();
+    annotatedStat.dataLength = stat.getDataLength();
+
+    return annotatedStat;
   }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackup.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackup.java
index 09729da6ea2..94c1c7768fa 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackup.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackup.java
@@ -38,7 +38,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import jakarta.inject.Inject;
 import java.util.HashMap;
 import java.util.Map;
-import org.apache.solr.client.api.endpoint.CreateCollectionBackupApi;
+import org.apache.solr.client.api.endpoint.CollectionBackupApi;
 import org.apache.solr.client.api.model.CreateCollectionBackupRequestBody;
 import org.apache.solr.client.api.model.CreateCollectionBackupResponseBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
@@ -58,7 +58,7 @@ import org.apache.solr.response.SolrQueryResponse;
 import org.apache.zookeeper.common.StringUtils;
 
 /** V2 API for creating a new "backup" of a specified collection */
-public class CreateCollectionBackup extends BackupAPIBase implements 
CreateCollectionBackupApi {
+public class CreateCollectionBackup extends BackupAPIBase implements 
CollectionBackupApi.Create {
   private final ObjectMapper objectMapper;
 
   @Inject
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshot.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshot.java
index dd4a7a4033b..9d0a6d295af 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshot.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshot.java
@@ -26,7 +26,7 @@ import static 
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PER
 import jakarta.inject.Inject;
 import java.util.HashMap;
 import java.util.Map;
-import org.apache.solr.client.api.endpoint.CreateCollectionSnapshotApi;
+import org.apache.solr.client.api.endpoint.CollectionSnapshotApis;
 import org.apache.solr.client.api.model.CreateCollectionSnapshotRequestBody;
 import org.apache.solr.client.api.model.CreateCollectionSnapshotResponse;
 import org.apache.solr.client.solrj.SolrResponse;
@@ -43,7 +43,8 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
 /** V2 API implementation for creating a collection-level snapshot. */
-public class CreateCollectionSnapshot extends AdminAPIBase implements 
CreateCollectionSnapshotApi {
+public class CreateCollectionSnapshot extends AdminAPIBase
+    implements CollectionSnapshotApis.Create {
 
   @Inject
   public CreateCollectionSnapshot(
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
index 5ead94e0432..abe76571dde 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
@@ -26,7 +26,7 @@ import static 
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PER
 import jakarta.inject.Inject;
 import java.util.HashMap;
 import java.util.Map;
-import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
+import org.apache.solr.client.api.endpoint.CollectionSnapshotApis;
 import org.apache.solr.client.api.model.DeleteCollectionSnapshotResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
@@ -39,7 +39,8 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
 /** V2 API impl for Deleting Collection Snapshots. */
-public class DeleteCollectionSnapshot extends AdminAPIBase implements 
DeleteCollectionSnapshotApi {
+public class DeleteCollectionSnapshot extends AdminAPIBase
+    implements CollectionSnapshotApis.Delete {
 
   @Inject
   public DeleteCollectionSnapshot(
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshots.java
similarity index 61%
rename from 
solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java
rename to 
solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshots.java
index dec0f5998f6..9bbc94f202b 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshots.java
@@ -16,20 +16,13 @@
  */
 package org.apache.solr.handler.admin.api;
 
-import static 
org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.inject.Inject;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
 import java.util.Collection;
 import java.util.Map;
-import org.apache.solr.client.api.model.AsyncJerseyResponse;
+import org.apache.solr.client.api.endpoint.CollectionSnapshotApis;
+import org.apache.solr.client.api.model.ListCollectionSnapshotsResponse;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.util.CollectionUtil;
 import org.apache.solr.core.CoreContainer;
@@ -39,12 +32,11 @@ import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
-/** V2 API for Listing Collection Snapshots. */
-@Path("/collections/{collName}/snapshots")
-public class ListCollectionSnapshotsAPI extends AdminAPIBase {
+/** V2 API implementation for Listing Collection Snapshots. */
+public class ListCollectionSnapshots extends AdminAPIBase implements 
CollectionSnapshotApis.List {
 
   @Inject
-  public ListCollectionSnapshotsAPI(
+  public ListCollectionSnapshots(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
@@ -52,16 +44,12 @@ public class ListCollectionSnapshotsAPI extends 
AdminAPIBase {
   }
 
   /** This API is analogous to V1's (POST 
/solr/admin/collections?action=LISTSNAPSHOTS) */
-  @GET
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_READ_PERM)
-  public ListSnapshotsResponse listSnapshots(
-      @Parameter(description = "The name of the collection.", required = true)
-          @PathParam("collName")
-          String collName)
-      throws Exception {
+  public ListCollectionSnapshotsResponse listSnapshots(String collName) throws 
Exception {
 
-    final ListSnapshotsResponse response = 
instantiateJerseyResponse(ListSnapshotsResponse.class);
+    final ListCollectionSnapshotsResponse response =
+        instantiateJerseyResponse(ListCollectionSnapshotsResponse.class);
     final CoreContainer coreContainer = 
fetchAndValidateZooKeeperAwareCoreContainer();
     recordCollectionForLogAndTracing(collName, solrQueryRequest);
 
@@ -71,20 +59,12 @@ public class ListCollectionSnapshotsAPI extends 
AdminAPIBase {
     Collection<CollectionSnapshotMetaData> m =
         SolrSnapshotManager.listSnapshots(client, collectionName);
 
-    Map<String, CollectionSnapshotMetaData> snapshots = 
CollectionUtil.newHashMap(m.size());
+    final Map<String, Object> snapshots = CollectionUtil.newHashMap(m.size());
     for (CollectionSnapshotMetaData metaData : m) {
       snapshots.put(metaData.getName(), metaData);
     }
-
     response.snapshots = snapshots;
 
     return response;
   }
-
-  /** The Response for {@link ListCollectionSnapshotsAPI}'s {@link 
#listSnapshots(String)} */
-  public static class ListSnapshotsResponse extends AsyncJerseyResponse {
-    @Schema(description = "The snapshots for the collection.")
-    @JsonProperty(SolrSnapshotManager.SNAPSHOTS_INFO)
-    public Map<String, CollectionSnapshotMetaData> snapshots;
-  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCollectionAPI.java
 b/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCollection.java
similarity index 76%
rename from 
solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCollectionAPI.java
rename to 
solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCollection.java
index e47b9c29fb4..4ee125917c8 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCollectionAPI.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCollection.java
@@ -17,8 +17,6 @@
 
 package org.apache.solr.handler.admin.api;
 
-import static 
org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
-import static 
org.apache.solr.client.solrj.request.beans.V2ApiConstants.CREATE_COLLECTION_KEY;
 import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
 import static org.apache.solr.common.params.CollectionAdminParams.COLL_CONF;
@@ -38,16 +36,11 @@ import static 
org.apache.solr.common.params.CoreAdminParams.TRUSTED;
 import static 
org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
 import jakarta.inject.Inject;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
-import org.apache.solr.client.api.model.CreateCollectionRequestBody;
+import org.apache.solr.client.api.endpoint.CollectionBackupApi;
+import org.apache.solr.client.api.model.RestoreCollectionRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
@@ -56,10 +49,10 @@ import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.handler.configsets.ConfigSetAPIBase;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
@@ -69,8 +62,7 @@ import org.apache.solr.response.SolrQueryResponse;
  *
  * <p>This API is analogous to the v1 /admin/collections?action=RESTORE 
command.
  */
-@Path("/backups/{backupName}/restore")
-public class RestoreCollectionAPI extends BackupAPIBase {
+public class RestoreCollection extends BackupAPIBase implements 
CollectionBackupApi.Restore {
 
   private static final Set<String> CREATE_PARAM_ALLOWLIST =
       Set.of(
@@ -83,19 +75,17 @@ public class RestoreCollectionAPI extends BackupAPIBase {
           CREATE_NODE_SET_SHUFFLE_PARAM);
 
   @Inject
-  public RestoreCollectionAPI(
+  public RestoreCollection(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
     super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @POST
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SubResponseAccumulatingJerseyResponse restoreCollection(
-      @PathParam("backupName") String backupName, RestoreCollectionRequestBody 
requestBody)
-      throws Exception {
+      String backupName, RestoreCollectionRequestBody requestBody) throws 
Exception {
     final var response = 
instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
 
     if (requestBody == null) {
@@ -159,7 +149,7 @@ public class RestoreCollectionAPI extends BackupAPIBase {
 
   public ZkNodeProps createRemoteMessage(
       String backupName, RestoreCollectionRequestBody requestBody) {
-    final Map<String, Object> remoteMessage = requestBody.toMap(new 
HashMap<>());
+    final Map<String, Object> remoteMessage = Utils.reflectToMap(requestBody);
 
     // If the RESTORE is setup to create a new collection, copy those 
parameters first
     final var createReqBody = requestBody.createCollectionParams;
@@ -177,12 +167,7 @@ public class RestoreCollectionAPI extends BackupAPIBase {
 
     // Copy restore-specific parameters
     remoteMessage.put(QUEUE_OPERATION, 
CollectionParams.CollectionAction.RESTORE.toLower());
-    remoteMessage.put(COLLECTION_PROP, requestBody.collection);
     remoteMessage.put(NAME, backupName);
-    remoteMessage.put(BACKUP_LOCATION, requestBody.location);
-    if (requestBody.backupId != null) remoteMessage.put(BACKUP_ID, 
requestBody.backupId);
-    if (requestBody.repository != null)
-      remoteMessage.put(BACKUP_REPOSITORY, requestBody.repository);
     remoteMessage.put(
         TRUSTED,
         ConfigSetAPIBase.isTrusted(
@@ -198,39 +183,24 @@ public class RestoreCollectionAPI extends BackupAPIBase {
     final var params = solrQueryRequest.getParams();
     params.required().check(NAME, COLLECTION_PROP);
     final String backupName = params.get(NAME);
-    final var requestBody = RestoreCollectionRequestBody.fromV1Params(params);
+    final var requestBody = createRequestBodyFromV1Params(params);
 
     final var restoreApi =
-        new RestoreCollectionAPI(coreContainer, solrQueryRequest, 
solrQueryResponse);
+        new RestoreCollection(coreContainer, solrQueryRequest, 
solrQueryResponse);
     return restoreApi.restoreCollection(backupName, requestBody);
   }
 
-  /** Request body for the v2 "restore collection" API. */
-  public static class RestoreCollectionRequestBody implements 
JacksonReflectMapWriter {
-    @JsonProperty(required = true)
-    public String collection;
-
-    @JsonProperty public String location;
-    @JsonProperty public String repository;
-    @JsonProperty public Integer backupId;
-
-    @JsonProperty(CREATE_COLLECTION_KEY)
-    public CreateCollectionRequestBody createCollectionParams;
+  public static RestoreCollectionRequestBody 
createRequestBodyFromV1Params(SolrParams solrParams) {
+    final var restoreBody = new RestoreCollectionRequestBody();
+    restoreBody.collection = solrParams.get(COLLECTION_PROP);
+    restoreBody.location = solrParams.get(BACKUP_LOCATION);
+    restoreBody.repository = solrParams.get(BACKUP_REPOSITORY);
+    restoreBody.backupId = solrParams.getInt(BACKUP_ID);
+    restoreBody.async = solrParams.get(ASYNC);
 
-    @JsonProperty public String async;
+    restoreBody.createCollectionParams =
+        CreateCollection.createRequestBodyFromV1Params(solrParams, false);
 
-    public static RestoreCollectionRequestBody fromV1Params(SolrParams 
solrParams) {
-      final var restoreBody = new RestoreCollectionRequestBody();
-      restoreBody.collection = solrParams.get(COLLECTION_PROP);
-      restoreBody.location = solrParams.get(BACKUP_LOCATION);
-      restoreBody.repository = solrParams.get(BACKUP_REPOSITORY);
-      restoreBody.backupId = solrParams.getInt(BACKUP_ID);
-      restoreBody.async = solrParams.get(ASYNC);
-
-      restoreBody.createCollectionParams =
-          CreateCollection.createRequestBodyFromV1Params(solrParams, false);
-
-      return restoreBody;
-    }
+    return restoreBody;
   }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/SnapshotBackupAPI.java 
b/solr/core/src/java/org/apache/solr/handler/admin/api/SnapshotBackupAPI.java
index e4024b119c5..d2fbabc04c0 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/admin/api/SnapshotBackupAPI.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/SnapshotBackupAPI.java
@@ -16,37 +16,33 @@
  */
 package org.apache.solr.handler.admin.api;
 
-import static 
org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
 import static org.apache.solr.handler.ReplicationHandler.ERR_STATUS;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM;
 
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.parameters.RequestBody;
 import jakarta.inject.Inject;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.core.MediaType;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.function.Consumer;
 import org.apache.solr.api.JerseyResource;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.endpoint.ReplicationBackupApis;
+import org.apache.solr.client.api.model.ReplicationBackupRequestBody;
+import org.apache.solr.client.api.model.ReplicationBackupResponse;
 import org.apache.solr.common.SolrException;
-import org.apache.solr.common.annotation.JsonProperty;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.ReplicationHandler;
 import org.apache.solr.handler.ReplicationHandler.ReplicationHandlerConfig;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/** V2 endpoint for Backup API used for User-Managed clusters and Single-Node 
Installation. */
-@Path("/cores/{coreName}/replication/backups")
-public class SnapshotBackupAPI extends JerseyResource {
+/**
+ * v2 API implementation for replication-handler based backup creation.
+ *
+ * <p>This is the main backup functionality available to 'standalone' users.
+ */
+public class SnapshotBackupAPI extends JerseyResource implements 
ReplicationBackupApis {
 
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   private final SolrCore solrCore;
@@ -62,22 +58,17 @@ public class SnapshotBackupAPI extends JerseyResource {
    * This API (POST /api/cores/coreName/replication/backups {...}) is 
analogous to the v1
    * /solr/coreName/replication?command=backup
    */
-  @POST
-  @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, 
BINARY_CONTENT_TYPE_V2})
-  @Operation(summary = "Backup command using ReplicationHandler")
+  @Override
   @PermissionName(CORE_EDIT_PERM)
-  public BackupReplicationResponse createBackup(
-      @RequestBody BackupReplicationRequestBody backupReplicationPayload) 
throws Exception {
+  public ReplicationBackupResponse createBackup(
+      @RequestBody ReplicationBackupRequestBody backupReplicationPayload) {
     ensureRequiredRequestBodyProvided(backupReplicationPayload);
-    ReplicationHandler replicationHandler =
-        (ReplicationHandler) 
solrCore.getRequestHandler(ReplicationHandler.PATH);
-    return doBackup(replicationHandler, backupReplicationPayload);
+    return doBackup(backupReplicationPayload);
   }
 
-  private BackupReplicationResponse doBackup(
-      ReplicationHandler replicationHandler,
-      BackupReplicationRequestBody backupReplicationPayload) {
-    BackupReplicationResponse response = 
instantiateJerseyResponse(BackupReplicationResponse.class);
+  private ReplicationBackupResponse doBackup(
+      ReplicationBackupRequestBody backupReplicationPayload) {
+    ReplicationBackupResponse response = 
instantiateJerseyResponse(ReplicationBackupResponse.class);
     int numberToKeep = backupReplicationPayload.numberToKeep;
     int numberBackupsToKeep = 
replicationHandlerConfig.getNumberBackupsToKeep();
     String location = backupReplicationPayload.location;
@@ -128,63 +119,8 @@ public class SnapshotBackupAPI extends JerseyResource {
         resultConsumer);
   }
 
-  /* POJO for v2 endpoints request body. */
-  public static class BackupReplicationRequestBody implements 
JacksonReflectMapWriter {
-
-    public BackupReplicationRequestBody() {}
-
-    public BackupReplicationRequestBody(
-        String location, String name, int numberToKeep, String repository, 
String commitName) {
-      this.location = location;
-      this.name = name;
-      this.numberToKeep = numberToKeep;
-      this.repository = repository;
-      this.commitName = commitName;
-    }
-
-    @Schema(description = "The path where the backup will be created")
-    @JsonProperty
-    public String location;
-
-    @Schema(description = "The backup will be created in a directory called 
snapshot.<name>")
-    @JsonProperty
-    public String name;
-
-    @Schema(description = "The number of backups to keep.")
-    @JsonProperty
-    public int numberToKeep;
-
-    @Schema(description = "The name of the repository to be used for e 
backup.")
-    @JsonProperty
-    public String repository;
-
-    @Schema(
-        description =
-            "The name of the commit which was used while taking a snapshot 
using the CREATESNAPSHOT command.")
-    @JsonProperty
-    public String commitName;
-  }
-
-  /** Response for {@link 
SnapshotBackupAPI#createBackup(BackupReplicationRequestBody)}. */
-  public static class BackupReplicationResponse extends SolrJerseyResponse {
-
-    @JsonProperty("result")
-    public NamedList<?> result;
-
-    @JsonProperty("status")
-    public String status;
-
-    @JsonProperty("message")
-    public String message;
-
-    @JsonProperty("exception")
-    public Exception exception;
-
-    public BackupReplicationResponse() {}
-  }
-
   private static void reportErrorOnResponse(
-      BackupReplicationResponse response, String message, Exception e) {
+      ReplicationBackupResponse response, String message, Exception e) {
     response.status = ERR_STATUS;
     response.message = message;
     if (e != null) {
diff --git 
a/solr/core/src/java/org/apache/solr/jersey/MediaTypeOverridingFilter.java 
b/solr/core/src/java/org/apache/solr/jersey/MediaTypeOverridingFilter.java
index 29695918224..e5a7f7150cc 100644
--- a/solr/core/src/java/org/apache/solr/jersey/MediaTypeOverridingFilter.java
+++ b/solr/core/src/java/org/apache/solr/jersey/MediaTypeOverridingFilter.java
@@ -29,7 +29,7 @@ import jakarta.ws.rs.core.MediaType;
 import java.io.IOException;
 import java.util.List;
 import org.apache.solr.api.JerseyResource;
-import org.apache.solr.handler.admin.ZookeeperReadAPI;
+import org.apache.solr.handler.admin.ZookeeperRead;
 import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.request.SolrQueryRequest;
 
@@ -39,7 +39,7 @@ import org.apache.solr.request.SolrQueryRequest;
 public class MediaTypeOverridingFilter implements ContainerResponseFilter {
 
   private static final List<Class<? extends JerseyResource>> 
EXEMPTED_RESOURCES =
-      List.of(ZookeeperReadAPI.class);
+      List.of(ZookeeperRead.class);
 
   @Context private ResourceInfo resourceInfo;
 
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperReadAPITest.java 
b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperReadAPITest.java
index 1a580953c5e..fd87fe340bc 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperReadAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperReadAPITest.java
@@ -25,6 +25,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import java.net.URL;
 import java.util.Map;
 import java.util.stream.Collectors;
+import org.apache.solr.client.api.model.ZooKeeperListChildrenResponse;
+import org.apache.solr.client.api.model.ZooKeeperStat;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.SolrException;
@@ -35,7 +37,7 @@ import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-/** Integration tests for {@link ZookeeperReadAPI} */
+/** Integration tests for {@link ZookeeperRead} */
 public class ZookeeperReadAPITest extends SolrCloudTestCase {
   @BeforeClass
   public static void setupCluster() throws Exception {
@@ -126,13 +128,12 @@ public class ZookeeperReadAPITest extends 
SolrCloudTestCase {
   @Test
   public void testCanListChildNodes() throws Exception {
     try (HttpSolrClient client = new 
HttpSolrClient.Builder(baseUrl.toString()).build()) {
-      final ZookeeperReadAPI.ListZkChildrenResponse response =
+      final ZooKeeperListChildrenResponse response =
           Utils.executeGET(
               client.getHttpClient(),
               basezkls + "/configs/_default",
               is -> {
-                return new ObjectMapper()
-                    .readValue(is, 
ZookeeperReadAPI.ListZkChildrenResponse.class);
+                return new ObjectMapper().readValue(is, 
ZooKeeperListChildrenResponse.class);
               });
 
       // At the top level, the response contains a key with the value of the 
specified zkPath
@@ -145,7 +146,7 @@ public class ZookeeperReadAPITest extends SolrCloudTestCase 
{
       // node.
       // The actual stat values vary a good bit so aren't very useful to 
assert on, so let's just
       // make sure all of the expected child nodes were found.
-      final Map<String, ZookeeperReadAPI.AnnotatedStat> childStatsByPath =
+      final Map<String, ZooKeeperStat> childStatsByPath =
           response.unknownProperties().get("/configs/_default");
       assertEquals(6, childStatsByPath.size());
       assertThat(
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/RestoreCollectionAPITest.java
 
b/solr/core/src/test/org/apache/solr/handler/admin/api/RestoreCollectionAPITest.java
index 922cc989726..e4aab80a2df 100644
--- 
a/solr/core/src/test/org/apache/solr/handler/admin/api/RestoreCollectionAPITest.java
+++ 
b/solr/core/src/test/org/apache/solr/handler/admin/api/RestoreCollectionAPITest.java
@@ -37,6 +37,7 @@ import java.util.List;
 import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.api.model.CreateCollectionRequestBody;
+import org.apache.solr.client.api.model.RestoreCollectionRequestBody;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
@@ -46,15 +47,15 @@ import org.apache.solr.request.LocalSolrQueryRequest;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-/** Unit tests for {@link RestoreCollectionAPI} */
+/** Unit tests for {@link RestoreCollection} */
 public class RestoreCollectionAPITest extends SolrTestCaseJ4 {
 
-  private static RestoreCollectionAPI restoreCollectionAPI;
+  private static RestoreCollection restoreCollectionAPI;
 
   @BeforeClass
   public static void setUpApi() {
     restoreCollectionAPI =
-        new RestoreCollectionAPI(
+        new RestoreCollection(
             new CoreContainer(
                 new NodeConfig.NodeConfigBuilder("testnode", 
createTempDir()).build()),
             new LocalSolrQueryRequest(null, new NamedList<>()),
@@ -63,7 +64,7 @@ public class RestoreCollectionAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testReportsErrorIfBackupNameMissing() {
-    final var requestBody = new 
RestoreCollectionAPI.RestoreCollectionRequestBody();
+    final var requestBody = new RestoreCollectionRequestBody();
     requestBody.collection = "someCollection";
     final SolrException thrown =
         expectThrows(
@@ -92,7 +93,7 @@ public class RestoreCollectionAPITest extends SolrTestCaseJ4 {
   @Test
   public void testReportsErrorIfCollectionNameMissing() {
     // No 'collection' set on the requestBody
-    final var requestBody = new 
RestoreCollectionAPI.RestoreCollectionRequestBody();
+    final var requestBody = new RestoreCollectionRequestBody();
     final SolrException thrown =
         expectThrows(
             SolrException.class,
@@ -106,7 +107,7 @@ public class RestoreCollectionAPITest extends 
SolrTestCaseJ4 {
 
   @Test
   public void testReportsErrorIfProvidedCollectionNameIsInvalid() {
-    final var requestBody = new 
RestoreCollectionAPI.RestoreCollectionRequestBody();
+    final var requestBody = new RestoreCollectionRequestBody();
     requestBody.collection = "invalid$collection@name";
     final SolrException thrown =
         expectThrows(
@@ -122,7 +123,7 @@ public class RestoreCollectionAPITest extends 
SolrTestCaseJ4 {
 
   @Test
   public void testCreatesValidRemoteMessageForExistingCollectionRestore() {
-    final var requestBody = new 
RestoreCollectionAPI.RestoreCollectionRequestBody();
+    final var requestBody = new RestoreCollectionRequestBody();
     requestBody.collection = "someCollectionName";
     requestBody.location = "/some/location/path";
     requestBody.backupId = 123;
@@ -145,7 +146,7 @@ public class RestoreCollectionAPITest extends 
SolrTestCaseJ4 {
 
   @Test
   public void testCreatesValidRemoteMessageForNewCollectionRestore() {
-    final var requestBody = new 
RestoreCollectionAPI.RestoreCollectionRequestBody();
+    final var requestBody = new RestoreCollectionRequestBody();
     requestBody.collection = "someCollectionName";
     requestBody.location = "/some/location/path";
     requestBody.backupId = 123;
@@ -197,8 +198,7 @@ public class RestoreCollectionAPITest extends 
SolrTestCaseJ4 {
     v1Params.add("createNodeSet", "node1,node2");
     v1Params.add("createNodeSet.shuffle", "false");
 
-    final var requestBody =
-        
RestoreCollectionAPI.RestoreCollectionRequestBody.fromV1Params(v1Params);
+    final var requestBody = 
RestoreCollection.createRequestBodyFromV1Params(v1Params);
 
     assertEquals("someCollectionName", requestBody.collection);
     assertEquals("/some/location/str", requestBody.location);
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/SnapshotBackupAPITest.java
 
b/solr/core/src/test/org/apache/solr/handler/admin/api/SnapshotBackupAPITest.java
index dee50c1ebcb..aede987f2b6 100644
--- 
a/solr/core/src/test/org/apache/solr/handler/admin/api/SnapshotBackupAPITest.java
+++ 
b/solr/core/src/test/org/apache/solr/handler/admin/api/SnapshotBackupAPITest.java
@@ -24,6 +24,7 @@ import jakarta.inject.Inject;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import org.apache.solr.SolrTestCase;
+import org.apache.solr.client.api.model.ReplicationBackupRequestBody;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.SolrCore;
@@ -65,7 +66,7 @@ public class SnapshotBackupAPITest extends SolrTestCase {
   @Test
   public void testSuccessfulBackupCommand() throws Exception {
     when(replicationHandlerConfig.getNumberBackupsToKeep()).thenReturn(11);
-    final var backupRequestBody = new 
SnapshotBackupAPI.BackupReplicationRequestBody();
+    final var backupRequestBody = new ReplicationBackupRequestBody();
     backupRequestBody.name = "test";
     backupRequestBody.numberToKeep = 7;
 

Reply via email to