You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ge...@apache.org on 2022/10/24 21:19:01 UTC

[solr] branch branch_9x updated: SOLR-16392: Refactor and update v2 ADDREPLICAPROP API

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 936329923e1 SOLR-16392:  Refactor and update v2 ADDREPLICAPROP API
936329923e1 is described below

commit 936329923e1282303fdbffa1c435f123a2e58fa5
Author: Jason Gerlowski <ge...@apache.org>
AuthorDate: Sun Oct 2 16:56:30 2022 -0400

    SOLR-16392:  Refactor and update v2 ADDREPLICAPROP API
    
    This commit modifies the existing AddReplicaPropertyAPI definition to be
    more REST-ful, and use the new JAX-RS framework. Specifically, this
    commit changes the API from:
    
    POST /collections/collName
        {"add-replica-property" : {...}}
    
    to
    
    PUT /collections/<coll>/shards/<shard>/replicas/<replica>/properties/<prop>
        {"value": <val>}
    
    This is a breaking change for users of the v2 API, but one that is
    allowed because of v2's "experimental" designation.
    
    In support of this, this commit also refactors AddReplicaPropertyAPI to
    use the new JAX-RS framework.  It also introduces the new class,
    "InjectionFactories" to allow Jersey to inject common "context"
    values/objects as ctor args for resource classes such as
    AddReplicaPropertyAPI.
---
 .../java/org/apache/solr/core/CoreContainer.java   |   4 +-
 .../solr/handler/admin/CollectionsHandler.java     | 124 +++++++--------
 .../handler/admin/api/AddReplicaPropertyAPI.java   | 167 +++++++++++++++++----
 .../solr/handler/admin/api/AdminAPIBase.java       |  75 +++++++++
 .../apache/solr/jersey/CoreContainerFactory.java   |  44 ------
 .../org/apache/solr/jersey/InjectionFactories.java |  81 ++++++++++
 .../org/apache/solr/jersey/JerseyApplications.java |  26 +++-
 .../org/apache/solr/jersey/SolrCoreFactory.java    |  44 ------
 .../solr/handler/admin/TestApiFramework.java       |   5 +-
 .../solr/handler/admin/TestCollectionAPIs.java     |   7 -
 .../admin/api/AddReplicaPropertyAPITest.java       | 120 +++++++++++++++
 .../admin/api/V2CollectionAPIMappingTest.java      |  22 ---
 .../handler/configsets/ListConfigSetsAPITest.java  |   4 +-
 .../deployment-guide/pages/replica-management.adoc |   9 +-
 .../request/beans/AddReplicaPropertyPayload.java   |  36 -----
 15 files changed, 508 insertions(+), 260 deletions(-)

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 cd5c267eda2..8a51afb9371 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -128,7 +128,7 @@ import org.apache.solr.handler.admin.ZookeeperReadAPI;
 import org.apache.solr.handler.admin.ZookeeperStatusHandler;
 import org.apache.solr.handler.component.ShardHandlerFactory;
 import org.apache.solr.handler.designer.SchemaDesignerAPI;
-import org.apache.solr.jersey.CoreContainerFactory;
+import org.apache.solr.jersey.InjectionFactories;
 import org.apache.solr.logging.LogWatcher;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.metrics.SolrCoreMetricManager;
@@ -1078,7 +1078,7 @@ public class CoreContainer {
             new AbstractBinder() {
               @Override
               protected void configure() {
-                bindFactory(new CoreContainerFactory(thisCCRef))
+                bindFactory(new InjectionFactories.SingletonFactory<>(thisCCRef))
                     .to(CoreContainer.class)
                     .in(Singleton.class);
               }
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 b428ddf7c33..5760ff0dff7 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
@@ -145,6 +145,7 @@ import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.solr.api.AnnotatedApi;
 import org.apache.solr.api.Api;
+import org.apache.solr.api.JerseyResource;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.impl.HttpSolrClient.Builder;
@@ -186,6 +187,7 @@ import org.apache.solr.common.params.CollectionParams.CollectionAction;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.RequiredSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Pair;
@@ -203,6 +205,7 @@ import org.apache.solr.core.snapshots.SolrSnapshotManager;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.admin.api.AddReplicaAPI;
 import org.apache.solr.handler.admin.api.AddReplicaPropertyAPI;
+import org.apache.solr.handler.admin.api.AdminAPIBase;
 import org.apache.solr.handler.admin.api.BalanceShardUniqueAPI;
 import org.apache.solr.handler.admin.api.CollectionStatusAPI;
 import org.apache.solr.handler.admin.api.CreateShardAPI;
@@ -219,6 +222,8 @@ import org.apache.solr.handler.admin.api.ReloadCollectionAPI;
 import org.apache.solr.handler.admin.api.SetCollectionPropertyAPI;
 import org.apache.solr.handler.admin.api.SplitShardAPI;
 import org.apache.solr.handler.admin.api.SyncShardAPI;
+import org.apache.solr.handler.api.V2ApiUtils;
+import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
@@ -301,37 +306,31 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     // Pick the action
     SolrParams params = req.getParams();
     String a = params.get(CoreAdminParams.ACTION);
-    if (a != null) {
-      CollectionAction action = CollectionAction.get(a);
-      if (action == null) {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown action: " + a);
-      }
-      final String collection = params.get(COLLECTION);
-      MDCLoggingContext.setCollection(collection);
-      TraceUtils.setDbInstance(req, collection);
-      CollectionOperation operation = CollectionOperation.get(action);
-      if (log.isDebugEnabled()) {
-        log.debug(
-            "Invoked Collection Action: {} with params {}", action.toLower(), req.getParamString());
-      }
-      invokeAction(req, rsp, cores, action, operation);
-    } else {
+    if (a == null) {
       throw new SolrException(ErrorCode.BAD_REQUEST, "action is a required param");
     }
+    CollectionAction action = CollectionAction.get(a);
+    if (action == null) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown action: " + a);
+    }
+
+    // Initial logging/tracing setup
+    final String collection = params.get(COLLECTION);
+    MDCLoggingContext.setCollection(collection);
+    TraceUtils.setDbInstance(req, collection);
+    if (log.isDebugEnabled()) {
+      log.debug(
+          "Invoked Collection Action: {} with params {}", action.toLower(), req.getParamString());
+    }
+
+    CollectionOperation operation = CollectionOperation.get(action);
+    invokeAction(req, rsp, cores, action, operation);
     rsp.setHttpCaching(false);
   }
 
   protected CoreContainer checkErrors() {
     CoreContainer cores = getCoreContainer();
-    if (cores == null) {
-      throw new SolrException(ErrorCode.BAD_REQUEST, "Core container instance missing");
-    }
-
-    // Make sure that the core is ZKAware
-    if (!cores.isZooKeeperAware()) {
-      throw new SolrException(
-          ErrorCode.BAD_REQUEST, "Solr instance is not running in SolrCloud mode.");
-    }
+    AdminAPIBase.validateZooKeeperAwareCoreContainer(cores);
     return cores;
   }
 
@@ -389,8 +388,13 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     return submitCollectionApiCommand(m, action, DEFAULT_COLLECTION_OP_TIMEOUT);
   }
 
-  public SolrResponse submitCollectionApiCommand(
-      ZkNodeProps m, CollectionAction action, long timeout)
+  public static SolrResponse submitCollectionApiCommand(
+      CoreContainer coreContainer,
+      Optional<DistributedCollectionConfigSetCommandRunner>
+          distributedCollectionConfigSetCommandRunner,
+      ZkNodeProps m,
+      CollectionAction action,
+      long timeout)
       throws KeeperException, InterruptedException {
     // Collection API messages are either sent to Overseer and processed there, or processed
     // locally. Distributing Collection API implies we're also distributing Cluster State Updates.
@@ -472,6 +476,13 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     }
   }
 
+  public SolrResponse submitCollectionApiCommand(
+      ZkNodeProps m, CollectionAction action, long timeout)
+      throws KeeperException, InterruptedException {
+    return submitCollectionApiCommand(
+        coreContainer, distributedCollectionConfigSetCommandRunner, m, action, timeout);
+  }
+
   private boolean overseerCollectionQueueContains(String asyncId)
       throws KeeperException, InterruptedException {
     OverseerTaskQueue collectionQueue =
@@ -1266,39 +1277,28 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     ADDREPLICAPROP_OP(
         ADDREPLICAPROP,
         (req, rsp, h) -> {
-          Map<String, Object> map =
-              copy(
-                  req.getParams().required(),
-                  null,
-                  COLLECTION_PROP,
-                  PROPERTY_PROP,
-                  SHARD_ID_PROP,
-                  REPLICA_PROP,
-                  PROPERTY_VALUE_PROP);
-          copy(req.getParams(), map, SHARD_UNIQUE);
-          String property = (String) map.get(PROPERTY_PROP);
-          if (!property.startsWith(PROPERTY_PREFIX)) {
-            property = PROPERTY_PREFIX + property;
-          }
-
-          boolean uniquePerSlice = Boolean.parseBoolean((String) map.get(SHARD_UNIQUE));
-
-          // Check if we're trying to set a property with parameters that allow us to set the
-          // property on multiple replicas in a slice on properties that are known to only be
-          // one-per-slice and error out if so.
-          if (StringUtils.isNotBlank((String) map.get(SHARD_UNIQUE))
-              && SliceMutator.SLICE_UNIQUE_BOOLEAN_PROPERTIES.contains(
-                  property.toLowerCase(Locale.ROOT))
-              && uniquePerSlice == false) {
-            throw new SolrException(
-                ErrorCode.BAD_REQUEST,
-                "Overseer replica property command received for property "
-                    + property
-                    + " with the "
-                    + SHARD_UNIQUE
-                    + " parameter set to something other than 'true'. No action taken.");
-          }
-          return map;
+          final RequiredSolrParams requiredParams = req.getParams().required();
+          final AddReplicaPropertyAPI.AddReplicaPropertyRequestBody requestBody =
+              new AddReplicaPropertyAPI.AddReplicaPropertyRequestBody();
+          requestBody.value = requiredParams.get(PROPERTY_VALUE_PROP);
+          requestBody.shardUnique = req.getParams().getBool(SHARD_UNIQUE);
+          final String propName = requiredParams.get(PROPERTY_PROP);
+          final String trimmedPropName =
+              propName.startsWith(PROPERTY_PREFIX)
+                  ? propName.substring(PROPERTY_PREFIX.length())
+                  : propName;
+
+          final AddReplicaPropertyAPI addReplicaPropertyAPI =
+              new AddReplicaPropertyAPI(h.coreContainer, req, rsp);
+          final SolrJerseyResponse addReplicaPropResponse =
+              addReplicaPropertyAPI.addReplicaProperty(
+                  requiredParams.get(COLLECTION_PROP),
+                  requiredParams.get(SHARD_ID_PROP),
+                  requiredParams.get(REPLICA_PROP),
+                  trimmedPropName,
+                  requestBody);
+          V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, addReplicaPropResponse);
+          return null;
         }),
     // XXX should this command support followAliases?
     DELETEREPLICAPROP_OP(
@@ -2074,6 +2074,11 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     return Boolean.TRUE;
   }
 
+  @Override
+  public Collection<Class<? extends JerseyResource>> getJerseyResources() {
+    return List.of(AddReplicaPropertyAPI.class);
+  }
+
   @Override
   public Collection<Api> getApis() {
     final List<Api> apis = new ArrayList<>();
@@ -2084,7 +2089,6 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     apis.addAll(AnnotatedApi.getApis(new SyncShardAPI(this)));
     apis.addAll(AnnotatedApi.getApis(new ForceLeaderAPI(this)));
     apis.addAll(AnnotatedApi.getApis(new DeleteReplicaAPI(this)));
-    apis.addAll(AnnotatedApi.getApis(new AddReplicaPropertyAPI(this)));
     apis.addAll(AnnotatedApi.getApis(new BalanceShardUniqueAPI(this)));
     apis.addAll(AnnotatedApi.getApis(new DeleteCollectionAPI(this)));
     apis.addAll(AnnotatedApi.getApis(new DeleteReplicaPropertyAPI(this)));
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaPropertyAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaPropertyAPI.java
index c13f569da0c..5bb8e5395df 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaPropertyAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaPropertyAPI.java
@@ -16,51 +16,158 @@
  */
 package org.apache.solr.handler.admin.api;
 
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
-import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION;
-import static org.apache.solr.common.params.CommonParams.ACTION;
-import static org.apache.solr.handler.ClusterAPI.wrapParams;
+import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
+import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
+import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.SHARD_UNIQUE;
+import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
+import static org.apache.solr.common.cloud.ZkStateReader.PROPERTY_PROP;
+import static org.apache.solr.common.cloud.ZkStateReader.PROPERTY_VALUE_PROP;
+import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_PROP;
+import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
+import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_PREFIX;
+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 io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
-import org.apache.solr.api.Command;
-import org.apache.solr.api.EndPoint;
-import org.apache.solr.api.PayloadObj;
-import org.apache.solr.client.solrj.request.beans.AddReplicaPropertyPayload;
+import javax.inject.Inject;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.cloud.overseer.SliceMutator;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
+import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
+import org.apache.solr.jersey.JacksonReflectMapWriter;
+import org.apache.solr.jersey.PermissionName;
+import org.apache.solr.jersey.SolrJerseyResponse;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
 
 /**
  * V2 API for adding a property to a collection replica
  *
- * <p>This API (POST /v2/collections/collectionName {'add-replica-property': {...}}) is analogous to
- * the v1 /admin/collections?action=ADDREPLICAPROP command.
- *
- * @see AddReplicaPropertyPayload
+ * <p>This API is analogous to the v1 /admin/collections?action=ADDREPLICAPROP command.
  */
-@EndPoint(
-    path = {"/c/{collection}", "/collections/{collection}"},
-    method = POST,
-    permission = COLL_EDIT_PERM)
-public class AddReplicaPropertyAPI {
-  private static final String V2_ADD_REPLICA_PROPERTY_CMD = "add-replica-property";
+@Path("/collections/{collName}/shards/{shardName}/replicas/{replicaName}/properties/{propName}")
+public class AddReplicaPropertyAPI extends AdminAPIBase {
+
+  @Inject
+  public AddReplicaPropertyAPI(
+      CoreContainer coreContainer,
+      SolrQueryRequest solrQueryRequest,
+      SolrQueryResponse solrQueryResponse) {
+    super(coreContainer, solrQueryRequest, solrQueryResponse);
+  }
 
-  private final CollectionsHandler collectionsHandler;
+  @PUT
+  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @PermissionName(COLL_EDIT_PERM)
+  public SolrJerseyResponse addReplicaProperty(
+      @Parameter(
+              description = "The name of the collection the replica belongs to.",
+              required = true)
+          @PathParam("collName")
+          String collName,
+      @Parameter(description = "The name of the shard the replica belongs to.", required = true)
+          @PathParam("shardName")
+          String shardName,
+      @Parameter(description = "The replica, e.g., `core_node1`.", required = true)
+          @PathParam("replicaName")
+          String replicaName,
+      @Parameter(description = "The name of the property to add.", required = true)
+          @PathParam("propName")
+          String propertyName,
+      @RequestBody(
+              description = "The value of the replica property to create or update",
+              required = true)
+          AddReplicaPropertyRequestBody requestBody)
+      throws Exception {
+    final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
+    final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
+    recordCollectionForLogAndTracing(collName, solrQueryRequest);
 
-  public AddReplicaPropertyAPI(CollectionsHandler collectionsHandler) {
-    this.collectionsHandler = collectionsHandler;
+    final ZkNodeProps remoteMessage =
+        createRemoteMessage(collName, shardName, replicaName, propertyName, requestBody);
+    final SolrResponse remoteResponse =
+        CollectionsHandler.submitCollectionApiCommand(
+            coreContainer,
+            coreContainer.getDistributedCollectionCommandRunner(),
+            remoteMessage,
+            CollectionParams.CollectionAction.ADDREPLICAPROP,
+            DEFAULT_COLLECTION_OP_TIMEOUT);
+    if (remoteResponse.getException() != null) {
+      throw remoteResponse.getException();
+    }
+
+    disableResponseCaching();
+    return response;
   }
 
-  @Command(name = V2_ADD_REPLICA_PROPERTY_CMD)
-  public void addReplicaProperty(PayloadObj<AddReplicaPropertyPayload> obj) throws Exception {
-    final AddReplicaPropertyPayload v2Body = obj.get();
-    final Map<String, Object> v1Params = v2Body.toMap(new HashMap<>());
-    v1Params.put(ACTION, CollectionParams.CollectionAction.ADDREPLICAPROP.toLower());
-    v1Params.put(COLLECTION, obj.getRequest().getPathTemplateValues().get(COLLECTION));
-    v1Params.put("property", v1Params.remove("name"));
-    v1Params.put("property.value", v1Params.remove("value"));
+  public ZkNodeProps createRemoteMessage(
+      String collName,
+      String shardName,
+      String replicaName,
+      String propertyName,
+      AddReplicaPropertyRequestBody requestBody) {
+    final Map<String, Object> remoteMessage = new HashMap<>();
+    remoteMessage.put(COLLECTION_PROP, collName);
+    remoteMessage.put(PROPERTY_PROP, propertyName);
+    remoteMessage.put(SHARD_ID_PROP, shardName);
+    remoteMessage.put(REPLICA_PROP, replicaName);
+    remoteMessage.put(PROPERTY_VALUE_PROP, requestBody.value);
+    remoteMessage.put(QUEUE_OPERATION, CollectionParams.CollectionAction.ADDREPLICAPROP.toLower());
+    if (requestBody.shardUnique != null) {
+      remoteMessage.put(SHARD_UNIQUE, requestBody.shardUnique);
+    }
+    final String prefixedPropertyName = PROPERTY_PREFIX + propertyName;
+    boolean uniquePerSlice = requestBody.shardUnique == null ? false : requestBody.shardUnique;
+
+    // Check if we're trying to set a property with parameters that allow us to set the
+    // property on multiple replicas in a slice on properties that are known to only be
+    // one-per-slice and error out if so.
+    if (requestBody.shardUnique != null
+        && SliceMutator.SLICE_UNIQUE_BOOLEAN_PROPERTIES.contains(
+            prefixedPropertyName.toLowerCase(Locale.ROOT))
+        && uniquePerSlice == false) {
+      throw new SolrException(
+          SolrException.ErrorCode.BAD_REQUEST,
+          "Overseer replica property command received for property "
+              + prefixedPropertyName
+              + " with the "
+              + SHARD_UNIQUE
+              + " parameter set to something other than 'true'. No action taken.");
+    }
+
+    return new ZkNodeProps(remoteMessage);
+  }
+
+  public static class AddReplicaPropertyRequestBody implements JacksonReflectMapWriter {
+
+    public AddReplicaPropertyRequestBody() {}
+
+    public AddReplicaPropertyRequestBody(String value) {
+      this.value = value;
+    }
+
+    @Schema(description = "The value to assign to the property.", required = true)
+    @JsonProperty("value")
+    public String value;
 
-    collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse());
+    @Schema(
+        description =
+            "If `true`, then setting this property in one replica will remove the property from all other replicas in that shard. The default is `false`.\\nThere is one pre-defined property `preferredLeader` for which `shardUnique` is forced to `true` and an error returned if `shardUnique` is explicitly set to `false`.",
+        defaultValue = "false")
+    @JsonProperty("shardUnique")
+    public Boolean shardUnique;
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/AdminAPIBase.java b/solr/core/src/java/org/apache/solr/handler/admin/api/AdminAPIBase.java
new file mode 100644
index 00000000000..16d4b189d02
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/AdminAPIBase.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import org.apache.solr.api.JerseyResource;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.tracing.TraceUtils;
+
+/** A common parent for "admin" (i.e. container-level) APIs. */
+public abstract class AdminAPIBase extends JerseyResource {
+
+  protected final CoreContainer coreContainer;
+  protected final SolrQueryRequest solrQueryRequest;
+  protected final SolrQueryResponse solrQueryResponse;
+
+  public AdminAPIBase(
+      CoreContainer coreContainer,
+      SolrQueryRequest solrQueryRequest,
+      SolrQueryResponse solrQueryResponse) {
+    this.coreContainer = coreContainer;
+    this.solrQueryRequest = solrQueryRequest;
+    this.solrQueryResponse = solrQueryResponse;
+  }
+
+  protected CoreContainer fetchAndValidateZooKeeperAwareCoreContainer() {
+    validateZooKeeperAwareCoreContainer(coreContainer);
+    return coreContainer;
+  }
+
+  public static void validateZooKeeperAwareCoreContainer(CoreContainer coreContainer) {
+    if (coreContainer == null) {
+      throw new SolrException(
+          SolrException.ErrorCode.BAD_REQUEST, "Core container instance missing");
+    }
+
+    // Make sure that the core is ZKAware
+    if (!coreContainer.isZooKeeperAware()) {
+      throw new SolrException(
+          SolrException.ErrorCode.BAD_REQUEST, "Solr instance is not running in SolrCloud mode.");
+    }
+  }
+
+  /**
+   * TODO Taken from CollectionsHandler.handleRequestBody, but its unclear where (if ever) this gets
+   * cleared.
+   */
+  public static void recordCollectionForLogAndTracing(
+      String collection, SolrQueryRequest solrQueryRequest) {
+    MDCLoggingContext.setCollection(collection);
+    TraceUtils.setDbInstance(solrQueryRequest, collection);
+  }
+
+  public void disableResponseCaching() {
+    solrQueryResponse.setHttpCaching(false);
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/jersey/CoreContainerFactory.java b/solr/core/src/java/org/apache/solr/jersey/CoreContainerFactory.java
deleted file mode 100644
index 8b3f02ad326..00000000000
--- a/solr/core/src/java/org/apache/solr/jersey/CoreContainerFactory.java
+++ /dev/null
@@ -1,44 +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.jersey;
-
-import org.apache.solr.core.CoreContainer;
-import org.glassfish.hk2.api.Factory;
-
-/**
- * Allows the CoreContainer used by this Solr process to be injected into individual resource
- * classes at call-time.
- */
-public class CoreContainerFactory implements Factory<CoreContainer> {
-
-  private final CoreContainer singletonCC;
-
-  public CoreContainerFactory(CoreContainer singletonCC) {
-    this.singletonCC = singletonCC;
-  }
-
-  @Override
-  public CoreContainer provide() {
-    return singletonCC;
-  }
-
-  @Override
-  public void dispose(CoreContainer instance) {
-    /* No-op */
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/jersey/InjectionFactories.java b/solr/core/src/java/org/apache/solr/jersey/InjectionFactories.java
new file mode 100644
index 00000000000..ee430c86258
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/jersey/InjectionFactories.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.jersey;
+
+import javax.inject.Inject;
+import javax.ws.rs.container.ContainerRequestContext;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.glassfish.hk2.api.Factory;
+
+public class InjectionFactories {
+  public static class SolrQueryRequestFactory implements Factory<SolrQueryRequest> {
+
+    private final ContainerRequestContext containerRequestContext;
+
+    @Inject
+    public SolrQueryRequestFactory(ContainerRequestContext containerRequestContext) {
+      this.containerRequestContext = containerRequestContext;
+    }
+
+    @Override
+    public SolrQueryRequest provide() {
+      return (SolrQueryRequest)
+          containerRequestContext.getProperty(RequestContextKeys.SOLR_QUERY_REQUEST);
+    }
+
+    @Override
+    public void dispose(SolrQueryRequest instance) {}
+  }
+
+  public static class SolrQueryResponseFactory implements Factory<SolrQueryResponse> {
+
+    private final ContainerRequestContext containerRequestContext;
+
+    @Inject
+    public SolrQueryResponseFactory(ContainerRequestContext containerRequestContext) {
+      this.containerRequestContext = containerRequestContext;
+    }
+
+    @Override
+    public SolrQueryResponse provide() {
+      return (SolrQueryResponse)
+          containerRequestContext.getProperty(RequestContextKeys.SOLR_QUERY_RESPONSE);
+    }
+
+    @Override
+    public void dispose(SolrQueryResponse instance) {}
+  }
+
+  public static class SingletonFactory<T> implements Factory<T> {
+
+    private final T singletonVal;
+
+    public SingletonFactory(T singletonVal) {
+      this.singletonVal = singletonVal;
+    }
+
+    @Override
+    public T provide() {
+      return singletonVal;
+    }
+
+    @Override
+    public void dispose(T instance) {}
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java b/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
index 582c1f9fc01..7a4bb484e4b 100644
--- a/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
+++ b/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
@@ -23,8 +23,11 @@ import io.swagger.v3.oas.annotations.info.License;
 import javax.inject.Singleton;
 import org.apache.solr.core.PluginBag;
 import org.apache.solr.core.SolrCore;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.util.SolrVersion;
 import org.glassfish.hk2.utilities.binding.AbstractBinder;
+import org.glassfish.jersey.process.internal.RequestScoped;
 import org.glassfish.jersey.server.ResourceConfig;
 
 /**
@@ -69,7 +72,24 @@ public class JerseyApplications {
                   .in(Singleton.class);
             }
           });
-
+      register(
+          new AbstractBinder() {
+            @Override
+            protected void configure() {
+              bindFactory(InjectionFactories.SolrQueryRequestFactory.class)
+                  .to(SolrQueryRequest.class)
+                  .in(RequestScoped.class);
+            }
+          });
+      register(
+          new AbstractBinder() {
+            @Override
+            protected void configure() {
+              bindFactory(InjectionFactories.SolrQueryResponseFactory.class)
+                  .to(SolrQueryResponse.class)
+                  .in(RequestScoped.class);
+            }
+          });
       // Logging - disabled by default but useful for debugging Jersey execution
       //      setProperties(
       //          Map.of(
@@ -90,7 +110,9 @@ public class JerseyApplications {
           new AbstractBinder() {
             @Override
             protected void configure() {
-              bindFactory(new SolrCoreFactory(solrCore)).to(SolrCore.class).in(Singleton.class);
+              bindFactory(new InjectionFactories.SingletonFactory<>(solrCore))
+                  .to(SolrCore.class)
+                  .in(Singleton.class);
             }
           });
     }
diff --git a/solr/core/src/java/org/apache/solr/jersey/SolrCoreFactory.java b/solr/core/src/java/org/apache/solr/jersey/SolrCoreFactory.java
deleted file mode 100644
index ad51a132f15..00000000000
--- a/solr/core/src/java/org/apache/solr/jersey/SolrCoreFactory.java
+++ /dev/null
@@ -1,44 +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.jersey;
-
-import org.apache.solr.core.SolrCore;
-import org.glassfish.hk2.api.Factory;
-
-/**
- * Allows the SolrCore germane to a particular request to be injected into individual resource
- * instances at call-time.
- */
-public class SolrCoreFactory implements Factory<SolrCore> {
-
-  private final SolrCore solrCore;
-
-  public SolrCoreFactory(SolrCore solrCore) {
-    this.solrCore = solrCore;
-  }
-
-  @Override
-  public SolrCore provide() {
-    return solrCore;
-  }
-
-  @Override
-  public void dispose(SolrCore instance) {
-    /* No-op */
-  }
-}
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java b/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java
index 1a0f37312ae..95a15b6f799 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java
@@ -123,10 +123,7 @@ public class TestApiFramework extends SolrTestCaseJ4 {
     api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello", "POST", null, parts);
     assertConditions(
         api.getSpec(),
-        Map.of(
-            "/methods[0]", "POST",
-            "/commands/add-replica-property", NOT_NULL,
-            "/commands/delete-replica-property", NOT_NULL));
+        Map.of("/methods[0]", "POST", "/commands/delete-replica-property", NOT_NULL));
     assertEquals("hello", parts.get("collection"));
 
     api =
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java b/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
index 97a09a7816a..0db79432f51 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
@@ -199,13 +199,6 @@ public class TestCollectionAPIs extends SolrTestCaseJ4 {
     //        "Value of enum must be one of"
     //    );
 
-    compareOutput(
-        apiBag,
-        "/collections/collName",
-        POST,
-        "{add-replica-property : {name:propA , value: VALA, shard: shard1, replica:replica1}}",
-        "{collection: collName, shard: shard1, replica : replica1 , property : propA , operation : addreplicaprop, property.value : 'VALA'}");
-
     compareOutput(
         apiBag,
         "/collections/collName",
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/AddReplicaPropertyAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/AddReplicaPropertyAPITest.java
new file mode 100644
index 00000000000..e31e12497ea
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/AddReplicaPropertyAPITest.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.SHARD_UNIQUE;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.opentracing.noop.NoopSpan;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.cloud.OverseerSolrResponse;
+import org.apache.solr.cloud.api.collections.DistributedCollectionConfigSetCommandRunner;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+/** Unit tests for {@link AddReplicaPropertyAPI} */
+public class AddReplicaPropertyAPITest extends SolrTestCaseJ4 {
+
+  private static final AddReplicaPropertyAPI.AddReplicaPropertyRequestBody ANY_REQ_BODY =
+      new AddReplicaPropertyAPI.AddReplicaPropertyRequestBody("anyValue");
+
+  private CoreContainer mockCoreContainer;
+  private DistributedCollectionConfigSetCommandRunner mockCommandRunner;
+  private SolrQueryRequest mockQueryRequest;
+  private SolrQueryResponse queryResponse;
+  private ArgumentCaptor<ZkNodeProps> messageCapturer;
+
+  private AddReplicaPropertyAPI addReplicaPropApi;
+
+  @BeforeClass
+  public static void ensureWorkingMockito() {
+    assumeWorkingMockito();
+  }
+
+  @Before
+  public void setUp() throws Exception {
+    super.setUp();
+
+    mockCoreContainer = mock(CoreContainer.class);
+    mockCommandRunner = mock(DistributedCollectionConfigSetCommandRunner.class);
+    when(mockCoreContainer.getDistributedCollectionCommandRunner())
+        .thenReturn(Optional.of(mockCommandRunner));
+    when(mockCommandRunner.runCollectionCommand(any(), any(), anyLong()))
+        .thenReturn(new OverseerSolrResponse(new NamedList<>()));
+    mockQueryRequest = mock(SolrQueryRequest.class);
+    when(mockQueryRequest.getSpan()).thenReturn(NoopSpan.INSTANCE);
+    queryResponse = new SolrQueryResponse();
+    messageCapturer = ArgumentCaptor.forClass(ZkNodeProps.class);
+
+    addReplicaPropApi =
+        new AddReplicaPropertyAPI(mockCoreContainer, mockQueryRequest, queryResponse);
+  }
+
+  @Test
+  public void testReportsErrorWhenCalledInStandaloneMode() {
+    when(mockCoreContainer.isZooKeeperAware()).thenReturn(false);
+
+    final SolrException e =
+        expectThrows(
+            SolrException.class,
+            () -> {
+              addReplicaPropApi.addReplicaProperty(
+                  "someColl", "someShard", "someReplica", "somePropName", ANY_REQ_BODY);
+            });
+    assertEquals(400, e.code());
+    assertTrue(
+        "Exception message differed from expected: " + e.getMessage(),
+        e.getMessage().contains("not running in SolrCloud mode"));
+  }
+
+  @Test
+  public void testCreatesValidOverseerMessage() throws Exception {
+    when(mockCoreContainer.isZooKeeperAware()).thenReturn(true);
+
+    addReplicaPropApi.addReplicaProperty(
+        "someColl", "someShard", "someReplica", "somePropName", ANY_REQ_BODY);
+    verify(mockCommandRunner).runCollectionCommand(messageCapturer.capture(), any(), anyLong());
+
+    final ZkNodeProps createdMessage = messageCapturer.getValue();
+    final Map<String, Object> createdMessageProps = createdMessage.getProperties();
+    assertEquals(6, createdMessageProps.size());
+    assertEquals("addreplicaprop", createdMessageProps.get("operation"));
+    assertEquals("someColl", createdMessageProps.get("collection"));
+    assertEquals("someShard", createdMessageProps.get("shard"));
+    assertEquals("someReplica", createdMessageProps.get("replica"));
+    assertEquals("somePropName", createdMessageProps.get("property"));
+    assertEquals("anyValue", createdMessageProps.get("property.value"));
+    assertFalse(
+        createdMessageProps.containsKey(
+            SHARD_UNIQUE)); // Omitted since not specified on request body
+  }
+}
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionAPIMappingTest.java
index 5b845641a87..99e2f17c933 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionAPIMappingTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionAPIMappingTest.java
@@ -52,7 +52,6 @@ public class V2CollectionAPIMappingTest extends V2ApiMappingTest<CollectionsHand
   @Override
   public void populateApiBag() {
     final CollectionsHandler collectionsHandler = getRequestHandler();
-    apiBag.registerObject(new AddReplicaPropertyAPI(collectionsHandler));
     apiBag.registerObject(new BalanceShardUniqueAPI(collectionsHandler));
     apiBag.registerObject(new DeleteCollectionAPI(collectionsHandler));
     apiBag.registerObject(new DeleteReplicaPropertyAPI(collectionsHandler));
@@ -212,27 +211,6 @@ public class V2CollectionAPIMappingTest extends V2ApiMappingTest<CollectionsHand
     assertEquals(456, v1Params.getPrimitiveInt("maxWaitSeconds"));
   }
 
-  @Test
-  public void testAddReplicaPropertyAllProperties() throws Exception {
-    final SolrParams v1Params =
-        captureConvertedV1Params(
-            "/collections/collName",
-            "POST",
-            "{ 'add-replica-property': {"
-                + "'shard': 'someShardName', "
-                + "'replica': 'someReplicaName', "
-                + "'name': 'somePropertyName', "
-                + "'value': 'somePropertyValue'"
-                + "}}");
-
-    assertEquals(CollectionParams.CollectionAction.ADDREPLICAPROP.lowerName, v1Params.get(ACTION));
-    assertEquals("collName", v1Params.get(COLLECTION));
-    assertEquals("someShardName", v1Params.get("shard"));
-    assertEquals("someReplicaName", v1Params.get("replica"));
-    assertEquals("somePropertyName", v1Params.get("property"));
-    assertEquals("somePropertyValue", v1Params.get("property.value"));
-  }
-
   @Test
   public void testDeleteReplicaPropertyAllProperties() throws Exception {
     final SolrParams v1Params =
diff --git a/solr/core/src/test/org/apache/solr/handler/configsets/ListConfigSetsAPITest.java b/solr/core/src/test/org/apache/solr/handler/configsets/ListConfigSetsAPITest.java
index 14eba74bff7..dbd018a9ae2 100644
--- a/solr/core/src/test/org/apache/solr/handler/configsets/ListConfigSetsAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/configsets/ListConfigSetsAPITest.java
@@ -34,7 +34,7 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.ConfigSetService;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.CoreContainerFactory;
+import org.apache.solr.jersey.InjectionFactories;
 import org.apache.solr.jersey.SolrJacksonMapper;
 import org.glassfish.hk2.utilities.binding.AbstractBinder;
 import org.glassfish.jersey.server.ResourceConfig;
@@ -67,7 +67,7 @@ public class ListConfigSetsAPITest extends JerseyTest {
         new AbstractBinder() {
           @Override
           protected void configure() {
-            bindFactory(new CoreContainerFactory(mockCoreContainer))
+            bindFactory(new InjectionFactories.SingletonFactory<>(mockCoreContainer))
                 .to(CoreContainer.class)
                 .in(Singleton.class);
           }
diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/replica-management.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/replica-management.adoc
index 8e7bf30fa03..57381c362d6 100644
--- a/solr/solr-ref-guide/modules/deployment-guide/pages/replica-management.adoc
+++ b/solr/solr-ref-guide/modules/deployment-guide/pages/replica-management.adoc
@@ -610,14 +610,9 @@ http://localhost:8983/solr/admin/collections?action=ADDREPLICAPROP&collection=te
 
 [source,bash]
 ----
-curl -X POST http://localhost:8983/api/collections/techproducts -H 'Content-Type: application/json' -d '
+curl -X PUT http://localhost:8983/api/collections/techproducts/shards/shard1/replicas/core_node2/properties/preferredLeader -H 'Content-Type: application/json' -d '
   {
-    "add-replica-property":{
-      "shard":"shard1",
-      "replica":"core_node2",
-      "name":"preferredLeader",
-      "value": "true"
-    }
+    "value": "true"
   }
 '
 ----
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/AddReplicaPropertyPayload.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/AddReplicaPropertyPayload.java
deleted file mode 100644
index a821f5a5b5d..00000000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/AddReplicaPropertyPayload.java
+++ /dev/null
@@ -1,36 +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.solrj.request.beans;
-
-import org.apache.solr.common.annotation.JsonProperty;
-import org.apache.solr.common.util.ReflectMapWriter;
-
-public class AddReplicaPropertyPayload implements ReflectMapWriter {
-  @JsonProperty(required = true)
-  public String shard;
-
-  @JsonProperty(required = true)
-  public String replica;
-
-  @JsonProperty(required = true)
-  public String name;
-
-  @JsonProperty(required = true)
-  public String value;
-
-  @JsonProperty public Boolean shardUnique = null;
-}