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 2024/09/06 17:15:07 UTC

(solr) branch main updated: SOLR-17044: Expose api "version" on SolrRequest objects (#2456)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new eeb17223188 SOLR-17044: Expose api "version" on SolrRequest objects (#2456)
eeb17223188 is described below

commit eeb172231887a4e3c4eebd7e81ecbd16372036e6
Author: Jason Gerlowski <ge...@apache.org>
AuthorDate: Fri Sep 6 10:15:02 2024 -0700

    SOLR-17044: Expose api "version" on SolrRequest objects (#2456)
    
    Solr uses different API roots for its v1 and v2 APIs: "/solr" for v1,
    and "/api" for v2.  Adding a method for each SolrRequest to indicate
    which API version it belongs to will allow SolrJ to build the full
    request path more reliably without relying on fragile 'instanceof'
    checks.
---
 .../apache/solr/packagemanager/PackageManager.java |  9 ++--
 .../apache/solr/packagemanager/PackageUtils.java   | 52 ++++++-------------
 .../solr/packagemanager/RepositoryManager.java     |  3 +-
 .../org/apache/solr/client/solrj/SolrRequest.java  | 24 +++++++++
 .../solr/client/solrj/impl/CloudSolrClient.java    |  5 +-
 .../solr/client/solrj/impl/HttpSolrClient.java     |  3 +-
 .../solr/client/solrj/impl/HttpSolrClientBase.java |  3 +-
 .../client/solrj/request/GenericV2SolrRequest.java | 58 ++++++++++++++++++++++
 .../solr/client/solrj/request/V2Request.java       |  5 ++
 .../apache/solr/client/solrj/util/ClientUtils.java | 24 ++++++---
 .../solrj/src/resources/java-template/api.mustache |  5 ++
 11 files changed, 135 insertions(+), 56 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java b/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
index 15b6def6ecb..a751d20472f 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
@@ -47,6 +47,7 @@ import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.SolrZkClientTimeout;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.request.GenericV2SolrRequest;
 import org.apache.solr.client.solrj.request.V2Request;
 import org.apache.solr.client.solrj.request.beans.PackagePayload;
 import org.apache.solr.client.solrj.request.beans.PluginMeta;
@@ -237,7 +238,7 @@ public class PackageManager implements Closeable {
     try {
       NamedList<Object> result =
           solrClient.request(
-              new GenericSolrRequest(
+              new GenericV2SolrRequest(
                       SolrRequest.METHOD.GET,
                       PackageUtils.getCollectionParamsPath(collection) + "/PKG_VERSIONS")
                   .setRequiresCollection(
@@ -278,7 +279,7 @@ public class PackageManager implements Closeable {
     try {
       NamedList<Object> response =
           solrClient.request(
-              new GenericSolrRequest(SolrRequest.METHOD.GET, PackageUtils.CLUSTERPROPS_PATH));
+              new GenericV2SolrRequest(SolrRequest.METHOD.GET, PackageUtils.CLUSTERPROPS_PATH));
       Integer statusCode = (Integer) response.findRecursive("responseHeader", "status");
       if (statusCode == null || statusCode == ErrorCode.NOT_FOUND.code) {
         // Cluster props doesn't exist, that means there are no cluster level plugins installed.
@@ -421,7 +422,7 @@ public class PackageManager implements Closeable {
         boolean packageParamsExist =
             solrClient
                 .request(
-                    new GenericSolrRequest(
+                    new GenericV2SolrRequest(
                             SolrRequest.METHOD.GET,
                             PackageUtils.getCollectionParamsPath(collection) + "/packages")
                         .setRequiresCollection(
@@ -723,7 +724,7 @@ public class PackageManager implements Closeable {
     try {
       NamedList<Object> response =
           solrClient.request(
-              new GenericSolrRequest(
+              new GenericV2SolrRequest(
                       SolrRequest.METHOD.GET,
                       PackageUtils.getCollectionParamsPath(collection) + "/packages")
                   .setRequiresCollection(
diff --git a/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java b/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
index b3749d5cf8b..26b294b4906 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
@@ -25,9 +25,7 @@ import com.jayway.jsonpath.spi.json.JsonProvider;
 import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
 import com.jayway.jsonpath.spi.mapper.MappingProvider;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.util.List;
@@ -40,11 +38,11 @@ import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
+import org.apache.solr.client.solrj.request.FileStoreApi;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
-import org.apache.solr.client.solrj.request.RequestWriter;
+import org.apache.solr.client.solrj.request.GenericV2SolrRequest;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
-import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
@@ -54,6 +52,7 @@ import org.apache.solr.filestore.DistribFileStore;
 import org.apache.solr.filestore.FileStoreAPI;
 import org.apache.solr.packagemanager.SolrPackage.Manifest;
 import org.apache.solr.util.SolrJacksonAnnotationInspector;
+import org.apache.zookeeper.server.ByteBufferInputStream;
 
 public class PackageUtils {
 
@@ -91,39 +90,18 @@ public class PackageUtils {
    */
   public static void postFile(SolrClient client, ByteBuffer buffer, String name, String sig)
       throws SolrServerException, IOException {
-    String resource = "/api/cluster/files" + name;
-    ModifiableSolrParams params = new ModifiableSolrParams();
-    if (sig != null) {
-      params.add("sig", sig);
-    }
-    GenericSolrRequest request =
-        new GenericSolrRequest(SolrRequest.METHOD.PUT, resource, params) {
-          @Override
-          public RequestWriter.ContentWriter getContentWriter(String expectedType) {
-            return new RequestWriter.ContentWriter() {
-              public final ByteBuffer payload = buffer;
-
-              @Override
-              public void write(OutputStream os) throws IOException {
-                if (payload == null) return;
-                Channels.newChannel(os).write(payload);
-              }
+    try (final var stream = new ByteBufferInputStream(buffer)) {
+      final var uploadReq = new FileStoreApi.UploadFile(name, stream);
+      if (sig != null) {
+        uploadReq.setSig(List.of(sig));
+      }
 
-              @Override
-              public String getContentType() {
-                return "application/octet-stream";
-              }
-            };
-          }
-        };
-    NamedList<Object> rsp = client.request(request);
-    if (!name.equals(rsp.get(CommonParams.FILE))) {
-      throw new SolrException(
-          ErrorCode.BAD_REQUEST,
-          "Mismatch in file uploaded. Uploaded: "
-              + rsp.get(CommonParams.FILE)
-              + ", Original: "
-              + name);
+      final var uploadRsp = uploadReq.process(client).getParsed();
+      if (!name.equals(uploadRsp.file)) {
+        throw new SolrException(
+            ErrorCode.BAD_REQUEST,
+            "Mismatch in file uploaded. Uploaded: " + uploadRsp.file + ", Original: " + name);
+      }
     }
   }
 
@@ -205,7 +183,7 @@ public class PackageUtils {
       SolrClient solrClient, String manifestFilePath, String expectedSHA512)
       throws IOException, SolrServerException {
     GenericSolrRequest request =
-        new GenericSolrRequest(SolrRequest.METHOD.GET, "/api/node/files" + manifestFilePath);
+        new GenericV2SolrRequest(SolrRequest.METHOD.GET, "/api/node/files" + manifestFilePath);
     request.setResponseParser(new JsonMapResponseParser());
     NamedList<Object> response = solrClient.request(request);
     String manifestJson = (String) response.get("response");
diff --git a/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java b/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
index 03605f26337..c855f52961e 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
@@ -45,6 +45,7 @@ import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.request.GenericV2SolrRequest;
 import org.apache.solr.client.solrj.request.RequestWriter;
 import org.apache.solr.client.solrj.request.beans.PackagePayload;
 import org.apache.solr.common.SolrException;
@@ -237,7 +238,7 @@ public class RepositoryManager {
       add.manifestSHA512 = manifestSHA512;
 
       GenericSolrRequest request =
-          new GenericSolrRequest(SolrRequest.METHOD.POST, PackageUtils.PACKAGE_PATH) {
+          new GenericV2SolrRequest(SolrRequest.METHOD.POST, PackageUtils.PACKAGE_PATH) {
             @Override
             public RequestWriter.ContentWriter getContentWriter(String expectedType) {
               return new RequestWriter.StringPayloadContentWriter(
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
index 4bb077065ce..a615d478b98 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
@@ -52,6 +52,21 @@ public abstract class SolrRequest<T extends SolrResponse> implements Serializabl
     DELETE
   };
 
+  public enum ApiVersion {
+    V1("/solr"),
+    V2("/api");
+
+    private final String apiPrefix;
+
+    ApiVersion(String apiPrefix) {
+      this.apiPrefix = apiPrefix;
+    }
+
+    public String getApiPrefix() {
+      return apiPrefix;
+    }
+  }
+
   public enum SolrRequestType {
     QUERY,
     UPDATE,
@@ -188,6 +203,15 @@ public abstract class SolrRequest<T extends SolrResponse> implements Serializabl
     return false;
   }
 
+  /**
+   * Indicates which API version this request will make
+   *
+   * <p>Defaults implementation returns 'V1'.
+   */
+  public ApiVersion getApiVersion() {
+    return ApiVersion.V1;
+  }
+
   /**
    * @deprecated Please use {@link SolrRequest#getContentWriter(String)} instead.
    */
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
index e08e904703f..666f5662627 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
@@ -852,7 +852,8 @@ public abstract class CloudSolrClient extends SolrClient {
       // or request is v2 api and its method is not GET
       if (inputCollections.isEmpty()
           || isAdmin
-          || (request instanceof V2Request && request.getMethod() != SolrRequest.METHOD.GET)) {
+          || (request.getApiVersion() == SolrRequest.ApiVersion.V2
+              && request.getMethod() != SolrRequest.METHOD.GET)) {
         if (exc instanceof SolrServerException) {
           throw (SolrServerException) exc;
         } else if (exc instanceof IOException) {
@@ -1026,7 +1027,7 @@ public abstract class CloudSolrClient extends SolrClient {
     final List<LBSolrClient.Endpoint> requestEndpoints =
         new ArrayList<>(); // we populate this as follows...
 
-    if (request instanceof V2Request) {
+    if (request.getApiVersion() == SolrRequest.ApiVersion.V2) {
       if (!liveNodes.isEmpty()) {
         List<String> liveNodesList = new ArrayList<>(liveNodes);
         Collections.shuffle(liveNodesList, rand);
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java
index 60804ebcb4b..86f8b932f37 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java
@@ -75,7 +75,6 @@ import org.apache.solr.client.solrj.ResponseParser;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.request.RequestWriter;
-import org.apache.solr.client.solrj.request.V2Request;
 import org.apache.solr.client.solrj.util.ClientUtils;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CommonParams;
@@ -256,7 +255,7 @@ public class HttpSolrClient extends BaseHttpSolrClient {
   }
 
   private boolean isV2ApiRequest(final SolrRequest<?> request) {
-    return request instanceof V2Request || request.getPath().contains("/____v2");
+    return request.getApiVersion() == SolrRequest.ApiVersion.V2;
   }
 
   private void setBasicAuthHeader(SolrRequest<?> request, HttpRequestBase method)
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
index a395c681dd9..3debb681378 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
@@ -40,7 +40,6 @@ import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.request.RequestWriter;
-import org.apache.solr.client.solrj.request.V2Request;
 import org.apache.solr.client.solrj.util.ClientUtils;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CommonParams;
@@ -388,7 +387,7 @@ public abstract class HttpSolrClientBase extends SolrClient {
   }
 
   public boolean isV2ApiRequest(final SolrRequest<?> request) {
-    return request instanceof V2Request || request.getPath().contains("/____v2");
+    return request.getApiVersion() == SolrRequest.ApiVersion.V2;
   }
 
   public String getBaseURL() {
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/GenericV2SolrRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/GenericV2SolrRequest.java
new file mode 100644
index 00000000000..f4267ab7815
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/GenericV2SolrRequest.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+import org.apache.solr.common.params.SolrParams;
+
+/** A {@link GenericSolrRequest} implementation intended for v2 APIs */
+public class GenericV2SolrRequest extends GenericSolrRequest {
+
+  /**
+   * @param m the HTTP method to use for this request
+   * @param path the HTTP path to use for this request. Path may include the v2 API root path (i.e.
+   *     "/api"), but does not need to. If users are making a collection-aware request (i.e. {@link
+   *     #setRequiresCollection(boolean)} is called with 'true'), only the section of the API path
+   *     following the collection or core should be provided here.
+   */
+  public GenericV2SolrRequest(METHOD m, String path) {
+    super(m, removeLeadingApiRoot(path));
+  }
+
+  /**
+   * @param m the HTTP method to use for this request
+   * @param path the HTTP path to use for this request. If users are making a collection-aware
+   *     request (i.e. {@link #setRequiresCollection(boolean)} is called with 'true'), only the
+   *     section of the API path following the collection or core should be provided here.
+   * @param params query parameter names and values for making this request.
+   */
+  public GenericV2SolrRequest(METHOD m, String path, SolrParams params) {
+    super(m, path);
+    this.params = params;
+  }
+
+  @Override
+  public ApiVersion getApiVersion() {
+    return ApiVersion.V2;
+  }
+
+  private static String removeLeadingApiRoot(String path) {
+    if (path.startsWith("/api")) {
+      return path.replaceFirst("/api", "");
+    }
+    return path;
+  }
+}
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/V2Request.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/V2Request.java
index 242b24591f3..bcef4488560 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/V2Request.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/V2Request.java
@@ -111,6 +111,11 @@ public class V2Request extends SolrRequest<V2Response> implements MapWriter {
     return collection;
   }
 
+  @Override
+  public ApiVersion getApiVersion() {
+    return ApiVersion.V2;
+  }
+
   @Override
   protected V2Response createResponse(SolrClient client) {
     return new V2Response();
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java b/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
index 78f369fb995..6f0ff47ee47 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
@@ -84,11 +84,11 @@ public class ClientUtils {
       throws MalformedURLException {
     String basePath = solrRequest.getBasePath() == null ? serverRootUrl : solrRequest.getBasePath();
 
-    if (solrRequest instanceof V2Request) {
-      if (System.getProperty("solr.v2RealPath") == null) {
-        basePath = changeV2RequestEndpoint(basePath);
-      } else {
+    if (solrRequest.getApiVersion() == SolrRequest.ApiVersion.V2) {
+      if (solrRequest instanceof V2Request && System.getProperty("solr.v2RealPath") != null) {
         basePath = serverRootUrl + "/____v2";
+      } else {
+        basePath = addNormalV2ApiRoot(basePath);
       }
     }
 
@@ -102,10 +102,18 @@ public class ClientUtils {
     return basePath + path;
   }
 
-  private static String changeV2RequestEndpoint(String basePath) throws MalformedURLException {
-    URI oldURI = URI.create(basePath);
-    String newPath = oldURI.getPath().replaceFirst("/solr", "/api");
-    return oldURI.resolve(newPath).toString();
+  private static String addNormalV2ApiRoot(String basePath) throws MalformedURLException {
+    final var oldURI = URI.create(basePath);
+    final var revisedPath = buildReplacementV2Path(oldURI.getPath());
+    return oldURI.resolve(revisedPath).toString();
+  }
+
+  private static String buildReplacementV2Path(String existingPath) {
+    if (existingPath.contains("/solr")) {
+      return existingPath.replaceFirst("/solr", "/api");
+    } else {
+      return existingPath + "/api";
+    }
   }
 
   // ------------------------------------------------------------------------
diff --git a/solr/solrj/src/resources/java-template/api.mustache b/solr/solrj/src/resources/java-template/api.mustache
index bbb300023ed..c3961cf50b1 100644
--- a/solr/solrj/src/resources/java-template/api.mustache
+++ b/solr/solrj/src/resources/java-template/api.mustache
@@ -185,6 +185,11 @@ public class {{classname}} {
               return SolrRequestType.ADMIN.toString();
             }
 
+            @Override
+            public ApiVersion getApiVersion() {
+              return ApiVersion.V2;
+            }
+
             @Override
             public SolrParams getParams() {
               final ModifiableSolrParams params = new ModifiableSolrParams();