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

jdyer 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 72cc2756a7b SOLR-17516: LBHttpSolrClient: support HttpJdkSolrClient 
(Generic Version) (#2828)
72cc2756a7b is described below

commit 72cc2756a7befcdb1e9fff5275261695054bd152
Author: James Dyer <[email protected]>
AuthorDate: Tue Nov 19 09:20:25 2024 -0600

    SOLR-17516: LBHttpSolrClient: support HttpJdkSolrClient (Generic Version) 
(#2828)
---
 solr/CHANGES.txt                                   |   4 +-
 .../solr/handler/component/HttpShardHandler.java   |   3 +-
 .../handler/component/HttpShardHandlerFactory.java |   4 +-
 .../client/solrj/impl/CloudHttp2SolrClient.java    |   6 +-
 .../solr/client/solrj/impl/Http2SolrClient.java    |   4 -
 .../solr/client/solrj/impl/HttpJdkSolrClient.java  |  28 +++--
 .../solr/client/solrj/impl/HttpSolrClientBase.java |   4 +
 .../solr/client/solrj/impl/LBHttp2SolrClient.java  |  33 +++---
 .../solr/client/solrj/impl/LBSolrClient.java       |  13 ++-
 .../client/solrj/impl/HttpJdkSolrClientTest.java   | 102 ++++--------------
 .../LBHttp2SolrClientIntegrationTest.java}         | 112 +++++++++++++-------
 .../client/solrj/impl/LBHttp2SolrClientTest.java   | 115 +++++++++++++++++++--
 .../solr/client/solrj/impl/MockTrustManager.java   | 100 ++++++++++++++++++
 13 files changed, 363 insertions(+), 165 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 93e737a543d..05574f64371 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -32,6 +32,8 @@ Improvements
 
 * SOLR-17495: Change Solr CLI delete command to not delete configs by default. 
 Decouple lifecycle of collections from configsets. (Eric Pugh)
 
+* SOLR-17516: `LBHttp2SolrClient` is now generic, adding support for 
`HttpJdkSolrClient`. (James Dyer)
+
 Optimizations
 ---------------------
 (No changes)
@@ -71,7 +73,7 @@ Deprecation Removals
   in WordBreakSolrSpellChecker (Andrey Bozhko via Eric Pugh)
 
 * SOLR-14763: Remove deprecated asynchronous request methods from 
`Http2SolrClient`, `HttpJdkSolrClient` and `LBHttp2SolrClient`
-  in favor of the new CompletableFuture based methods.  Remove the related 
deprecated interfaces `AsyncListener` and ``Cancellable`
+  in favor of the new CompletableFuture based methods.  Remove the related 
deprecated interfaces `AsyncListener` and `Cancellable`
   (James Dyer)
 
 * SOLR-14115: Remove deprecated zkcli script in favour of equivalent bin/solr 
sub commmands. (Eric Pugh)
diff --git 
a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java 
b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
index 219197d8cd8..7592eed86fc 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
@@ -31,6 +31,7 @@ import java.util.function.BiConsumer;
 import net.jcip.annotations.NotThreadSafe;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.client.solrj.impl.Http2SolrClient;
 import org.apache.solr.client.solrj.impl.LBHttp2SolrClient;
 import org.apache.solr.client.solrj.impl.LBSolrClient;
 import org.apache.solr.client.solrj.request.QueryRequest;
@@ -113,7 +114,7 @@ public class HttpShardHandler extends ShardHandler {
   protected AtomicInteger pending;
 
   private final Map<String, List<String>> shardToURLs;
-  protected LBHttp2SolrClient lbClient;
+  protected LBHttp2SolrClient<Http2SolrClient> lbClient;
 
   public HttpShardHandler(HttpShardHandlerFactory httpShardHandlerFactory) {
     this.httpShardHandlerFactory = httpShardHandlerFactory;
diff --git 
a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
 
b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
index 2bfc4cb236a..ac7dc0cf8e0 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
@@ -84,7 +84,7 @@ public class HttpShardHandlerFactory extends 
ShardHandlerFactory
 
   protected volatile Http2SolrClient defaultClient;
   protected InstrumentedHttpListenerFactory httpListenerFactory;
-  protected LBHttp2SolrClient loadbalancer;
+  protected LBHttp2SolrClient<Http2SolrClient> loadbalancer;
 
   int corePoolSize = 0;
   int maximumPoolSize = Integer.MAX_VALUE;
@@ -314,7 +314,7 @@ public class HttpShardHandlerFactory extends 
ShardHandlerFactory
             .withMaxConnectionsPerHost(maxConnectionsPerHost)
             .build();
     this.defaultClient.addListenerFactory(this.httpListenerFactory);
-    this.loadbalancer = new LBHttp2SolrClient.Builder(defaultClient).build();
+    this.loadbalancer = new 
LBHttp2SolrClient.Builder<Http2SolrClient>(defaultClient).build();
 
     initReplicaListTransformers(getParameter(args, "replicaRouting", null, 
sb));
 
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java
index 92da218d15c..523c46b844c 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java
@@ -40,7 +40,7 @@ import org.apache.solr.common.SolrException;
 public class CloudHttp2SolrClient extends CloudSolrClient {
 
   private final ClusterStateProvider stateProvider;
-  private final LBHttp2SolrClient lbClient;
+  private final LBHttp2SolrClient<Http2SolrClient> lbClient;
   private final Http2SolrClient myClient;
   private final boolean clientIsInternal;
 
@@ -73,7 +73,7 @@ public class CloudHttp2SolrClient extends CloudSolrClient {
     // locks.
     this.locks = objectList(builder.parallelCacheRefreshesLocks);
 
-    this.lbClient = new LBHttp2SolrClient.Builder(myClient).build();
+    this.lbClient = new 
LBHttp2SolrClient.Builder<Http2SolrClient>(myClient).build();
   }
 
   private Http2SolrClient createOrGetHttpClientFromBuilder(Builder builder) {
@@ -149,7 +149,7 @@ public class CloudHttp2SolrClient extends CloudSolrClient {
   }
 
   @Override
-  public LBHttp2SolrClient getLbClient() {
+  public LBHttp2SolrClient<Http2SolrClient> getLbClient() {
     return lbClient;
   }
 
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
index e3f974e41e3..fb2eb1a123f 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
@@ -831,10 +831,6 @@ public class Http2SolrClient extends HttpSolrClientBase {
         .collect(Collectors.joining(", "));
   }
 
-  protected RequestWriter getRequestWriter() {
-    return requestWriter;
-  }
-
   /**
    * An Http2SolrClient that doesn't close or cleanup any resources
    *
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
index d3610f8d544..1127b3fd1a1 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
@@ -138,7 +138,7 @@ public class HttpJdkSolrClient extends HttpSolrClientBase {
   public CompletableFuture<NamedList<Object>> requestAsync(
       final SolrRequest<?> solrRequest, String collection) {
     try {
-      PreparedRequest pReq = prepareRequest(solrRequest, collection);
+      PreparedRequest pReq = prepareRequest(solrRequest, collection, null);
       return httpClient
           .sendAsync(pReq.reqb.build(), 
HttpResponse.BodyHandlers.ofInputStream())
           .thenApply(
@@ -157,10 +157,10 @@ public class HttpJdkSolrClient extends HttpSolrClientBase 
{
     }
   }
 
-  @Override
-  public NamedList<Object> request(SolrRequest<?> solrRequest, String 
collection)
+  protected NamedList<Object> requestWithBaseUrl(
+      String baseUrl, SolrRequest<?> solrRequest, String collection)
       throws SolrServerException, IOException {
-    PreparedRequest pReq = prepareRequest(solrRequest, collection);
+    PreparedRequest pReq = prepareRequest(solrRequest, collection, baseUrl);
     HttpResponse<InputStream> response = null;
     try {
       response = httpClient.send(pReq.reqb.build(), 
HttpResponse.BodyHandlers.ofInputStream());
@@ -173,8 +173,8 @@ public class HttpJdkSolrClient extends HttpSolrClientBase {
           "Timeout occurred while waiting response from server at: " + 
pReq.url, e);
     } catch (SolrException se) {
       throw se;
-    } catch (RuntimeException re) {
-      throw new SolrServerException(re);
+    } catch (RuntimeException e) {
+      throw new SolrServerException(e);
     } finally {
       if (pReq.contentWritingFuture != null) {
         pReq.contentWritingFuture.cancel(true);
@@ -192,13 +192,25 @@ public class HttpJdkSolrClient extends HttpSolrClientBase 
{
     }
   }
 
-  private PreparedRequest prepareRequest(SolrRequest<?> solrRequest, String 
collection)
+  @Override
+  public NamedList<Object> request(SolrRequest<?> solrRequest, String 
collection)
+      throws SolrServerException, IOException {
+    return requestWithBaseUrl(null, solrRequest, collection);
+  }
+
+  private PreparedRequest prepareRequest(
+      SolrRequest<?> solrRequest, String collection, String overrideBaseUrl)
       throws SolrServerException, IOException {
     checkClosed();
     if (ClientUtils.shouldApplyDefaultCollection(collection, solrRequest)) {
       collection = defaultCollection;
     }
-    String url = getRequestUrl(solrRequest, collection);
+    String url;
+    if (overrideBaseUrl != null) {
+      url = ClientUtils.buildRequestUrl(solrRequest, overrideBaseUrl, 
collection);
+    } else {
+      url = getRequestUrl(solrRequest, collection);
+    }
     ResponseParser parserToUse = responseParser(solrRequest);
     ModifiableSolrParams queryParams = initializeSolrParams(solrRequest, 
parserToUse);
     var reqb = HttpRequest.newBuilder();
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 27e56375c2d..8683b965ad0 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
@@ -120,6 +120,10 @@ public abstract class HttpSolrClientBase extends 
SolrClient {
     return solrRequest.getResponseParser() == null ? this.parser : 
solrRequest.getResponseParser();
   }
 
+  protected RequestWriter getRequestWriter() {
+    return requestWriter;
+  }
+
   // TODO: Remove this for 10.0, there is a typo in the method name
   @Deprecated(since = "9.8", forRemoval = true)
   protected ModifiableSolrParams initalizeSolrParams(
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttp2SolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttp2SolrClient.java
index cb06c9d0c27..2c926a26261 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttp2SolrClient.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttp2SolrClient.java
@@ -37,9 +37,8 @@ import org.apache.solr.common.util.NamedList;
 import org.slf4j.MDC;
 
 /**
- * LBHttp2SolrClient or "LoadBalanced LBHttp2SolrClient" is a load balancing 
wrapper around {@link
- * Http2SolrClient}. This is useful when you have multiple Solr endpoints and 
requests need to be
- * Load Balanced among them.
+ * This "LoadBalanced Http Solr Client" is a load balancing wrapper around a 
Http Solr Client. This
+ * is useful when you have multiple Solr endpoints and requests need to be 
Load Balanced among them.
  *
  * <p>Do <b>NOT</b> use this class for indexing in leader/follower scenarios 
since documents must be
  * sent to the correct leader; no inter-node routing is done.
@@ -95,12 +94,14 @@ import org.slf4j.MDC;
  *
  * @since solr 8.0
  */
-public class LBHttp2SolrClient extends LBSolrClient {
-  private final Http2SolrClient solrClient;
+public class LBHttp2SolrClient<C extends HttpSolrClientBase> extends 
LBSolrClient {
 
-  private LBHttp2SolrClient(Builder builder) {
+  protected final C solrClient;
+
+  @SuppressWarnings("unchecked")
+  private LBHttp2SolrClient(Builder<?> builder) {
     super(Arrays.asList(builder.solrEndpoints));
-    this.solrClient = builder.http2SolrClient;
+    this.solrClient = (C) builder.solrClient;
     this.aliveCheckIntervalMillis = builder.aliveCheckIntervalMillis;
     this.defaultCollection = builder.defaultCollection;
   }
@@ -289,16 +290,16 @@ public class LBHttp2SolrClient extends LBSolrClient {
     }
   }
 
-  public static class Builder {
+  public static class Builder<C extends HttpSolrClientBase> {
 
-    private final Http2SolrClient http2SolrClient;
-    private final Endpoint[] solrEndpoints;
+    private final C solrClient;
+    private final LBSolrClient.Endpoint[] solrEndpoints;
     private long aliveCheckIntervalMillis =
         TimeUnit.MILLISECONDS.convert(60, TimeUnit.SECONDS); // 1 minute 
between checks
     protected String defaultCollection;
 
-    public Builder(Http2SolrClient http2Client, Endpoint... endpoints) {
-      this.http2SolrClient = http2Client;
+    public Builder(C solrClient, Endpoint... endpoints) {
+      this.solrClient = solrClient;
       this.solrEndpoints = endpoints;
     }
 
@@ -308,7 +309,7 @@ public class LBHttp2SolrClient extends LBSolrClient {
      *
      * @param aliveCheckInterval how often to ping for aliveness
      */
-    public LBHttp2SolrClient.Builder setAliveCheckInterval(int 
aliveCheckInterval, TimeUnit unit) {
+    public Builder<C> setAliveCheckInterval(int aliveCheckInterval, TimeUnit 
unit) {
       if (aliveCheckInterval <= 0) {
         throw new IllegalArgumentException(
             "Alive check interval must be " + "positive, specified value = " + 
aliveCheckInterval);
@@ -318,13 +319,13 @@ public class LBHttp2SolrClient extends LBSolrClient {
     }
 
     /** Sets a default for core or collection based requests. */
-    public LBHttp2SolrClient.Builder withDefaultCollection(String 
defaultCoreOrCollection) {
+    public Builder<C> withDefaultCollection(String defaultCoreOrCollection) {
       this.defaultCollection = defaultCoreOrCollection;
       return this;
     }
 
-    public LBHttp2SolrClient build() {
-      return new LBHttp2SolrClient(this);
+    public LBHttp2SolrClient<C> build() {
+      return new LBHttp2SolrClient<C>(this);
     }
   }
 }
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBSolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBSolrClient.java
index e31f3c08393..6e691b802f0 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBSolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBSolrClient.java
@@ -500,6 +500,8 @@ public abstract class LBSolrClient extends SolrClient {
     if (solrClient instanceof Http2SolrClient) {
       final var httpSolrClient = (Http2SolrClient) solrClient;
       return httpSolrClient.requestWithBaseUrl(baseUrl, (c) -> 
c.request(solrRequest, collection));
+    } else if (solrClient instanceof HttpJdkSolrClient) {
+      return ((HttpJdkSolrClient) solrClient).requestWithBaseUrl(baseUrl, 
solrRequest, collection);
     }
 
     // Assume provided client already uses 'baseUrl'
@@ -730,11 +732,20 @@ public abstract class LBSolrClient extends SolrClient {
         if (e.getRootCause() instanceof IOException) {
           ex = e;
           moveAliveToDead(wrapper);
-          if (justFailed == null) justFailed = new HashMap<>();
+          if (justFailed == null) {
+            justFailed = new HashMap<>();
+          }
           justFailed.put(endpoint.getUrl(), wrapper);
         } else {
           throw e;
         }
+      } catch (IOException e) {
+        ex = e;
+        moveAliveToDead(wrapper);
+        if (justFailed == null) {
+          justFailed = new HashMap<>();
+        }
+        justFailed.put(endpoint.getUrl(), wrapper);
       } catch (Exception e) {
         throw new SolrServerException(e);
       }
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpJdkSolrClientTest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpJdkSolrClientTest.java
index c3fb5609476..b3980ad44bc 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpJdkSolrClientTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpJdkSolrClientTest.java
@@ -20,10 +20,7 @@ package org.apache.solr.client.solrj.impl;
 import java.io.IOException;
 import java.net.CookieHandler;
 import java.net.CookieManager;
-import java.net.Socket;
 import java.net.http.HttpClient;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Objects;
@@ -31,51 +28,23 @@ import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509ExtendedTrustManager;
 import org.apache.lucene.util.NamedThreadFactory;
-import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.ResponseParser;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.request.RequestWriter;
 import org.apache.solr.client.solrj.response.SolrPingResponse;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.common.util.ExecutorUtil;
-import org.apache.solr.util.SSLTestConfig;
 import org.junit.After;
-import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class HttpJdkSolrClientTest extends HttpSolrClientTestBase {
 
-  private static SSLContext allTrustingSslContext;
-
-  @BeforeClass
-  public static void beforeClass() {
-    try {
-      KeyManagerFactory keyManagerFactory =
-          
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
-      SSLTestConfig stc = SolrTestCaseJ4.sslConfig;
-      keyManagerFactory.init(stc.defaultKeyStore(), 
stc.defaultKeyStorePassword().toCharArray());
-
-      SSLContext sslContext = SSLContext.getInstance("SSL");
-      sslContext.init(
-          keyManagerFactory.getKeyManagers(),
-          new TrustManager[] {MOCK_TRUST_MANAGER},
-          stc.notSecureSecureRandom());
-      allTrustingSslContext = sslContext;
-    } catch (Exception e) {
-      throw new RuntimeException(e);
-    }
-  }
-
   @After
   public void workaroundToReleaseThreads_noClosableUntilJava21() {
     Thread[] threads = new 
Thread[Thread.currentThread().getThreadGroup().activeCount()];
@@ -185,6 +154,25 @@ public class HttpJdkSolrClientTest extends 
HttpSolrClientTestBase {
     }
   }
 
+  @Test
+  public void testRequestWithBaseUrl() throws Exception {
+    DebugServlet.clear();
+    DebugServlet.addResponseHeader("Content-Type", "application/octet-stream");
+    DebugServlet.responseBodyByQueryFragment.put("", javabinResponse());
+    String someOtherUrl = getBaseUrl() + "/some/other/base/url";
+    String intendedUrl = getBaseUrl() + DEBUG_SERVLET_PATH;
+    SolrQuery q = new SolrQuery("foo");
+    q.setParam("a", MUST_ENCODE);
+
+    HttpJdkSolrClient.Builder b =
+        builder(someOtherUrl).withResponseParser(new BinaryResponseParser());
+    try (HttpJdkSolrClient client = b.build()) {
+      client.requestWithBaseUrl(intendedUrl, new QueryRequest(q, 
SolrRequest.METHOD.GET), null);
+      assertEquals(
+          client.getParser().getVersion(), 
DebugServlet.parameters.get(CommonParams.VERSION)[0]);
+    }
+  }
+
   @Test
   public void testGetById() throws Exception {
     DebugServlet.clear();
@@ -541,7 +529,7 @@ public class HttpJdkSolrClientTest extends 
HttpSolrClientTestBase {
             .withConnectionTimeout(connectionTimeout, TimeUnit.MILLISECONDS)
             .withIdleTimeout(socketTimeout, TimeUnit.MILLISECONDS)
             .withDefaultCollection(DEFAULT_CORE)
-            .withSSLContext(allTrustingSslContext);
+            .withSSLContext(MockTrustManager.ALL_TRUSTING_SSL_CONTEXT);
     return (B) b;
   }
 
@@ -574,52 +562,4 @@ public class HttpJdkSolrClientTest extends 
HttpSolrClientTestBase {
           + "6f 6e 21 32 e0 28 72 65 73 "
           + "70 6f 6e 73 65 0c 84 60 60 "
           + "00 01 80";
-
-  /**
-   * Taken from: https://www.baeldung.com/java-httpclient-ssl sec 4.1, 
2024/02/12. This is an
-   * all-trusting Trust Manager. Works with self-signed certificates.
-   */
-  private static final TrustManager MOCK_TRUST_MANAGER =
-      new X509ExtendedTrustManager() {
-        @Override
-        public void checkClientTrusted(X509Certificate[] chain, String 
authType, Socket socket)
-            throws CertificateException {
-          // no-op
-        }
-
-        @Override
-        public void checkServerTrusted(X509Certificate[] chain, String 
authType, Socket socket)
-            throws CertificateException {
-          // no-op
-        }
-
-        @Override
-        public void checkClientTrusted(X509Certificate[] chain, String 
authType, SSLEngine engine)
-            throws CertificateException {
-          // no-op
-        }
-
-        @Override
-        public void checkServerTrusted(X509Certificate[] chain, String 
authType, SSLEngine engine)
-            throws CertificateException {
-          // no-op
-        }
-
-        @Override
-        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
-          return new java.security.cert.X509Certificate[0];
-        }
-
-        @Override
-        public void checkClientTrusted(X509Certificate[] chain, String 
authType)
-            throws CertificateException {
-          // no-op
-        }
-
-        @Override
-        public void checkServerTrusted(java.security.cert.X509Certificate[] 
chain, String authType)
-            throws CertificateException {
-          // no-op
-        }
-      };
 }
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/TestLBHttp2SolrClient.java 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientIntegrationTest.java
similarity index 77%
rename from 
solr/solrj/src/test/org/apache/solr/client/solrj/TestLBHttp2SolrClient.java
rename to 
solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientIntegrationTest.java
index c3464b73a82..a4cf3292e81 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/TestLBHttp2SolrClient.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientIntegrationTest.java
@@ -14,10 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.solr.client.solrj;
+package org.apache.solr.client.solrj.impl;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.lang.invoke.MethodHandles;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -29,9 +30,9 @@ import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import org.apache.lucene.util.IOUtils;
 import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.client.solrj.impl.Http2SolrClient;
-import org.apache.solr.client.solrj.impl.LBHttp2SolrClient;
-import org.apache.solr.client.solrj.impl.LBSolrClient;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.client.solrj.response.SolrResponseBase;
 import org.apache.solr.common.SolrInputDocument;
@@ -49,12 +50,11 @@ import org.slf4j.LoggerFactory;
  *
  * @since solr 1.4
  */
-public class TestLBHttp2SolrClient extends SolrTestCaseJ4 {
+public class LBHttp2SolrClientIntegrationTest extends SolrTestCaseJ4 {
 
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   SolrInstance[] solr = new SolrInstance[3];
-  Http2SolrClient httpClient;
 
   // TODO: fix this test to not require FSDirectory
   static String savedFactory;
@@ -79,11 +79,6 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4 {
   @Override
   public void setUp() throws Exception {
     super.setUp();
-    httpClient =
-        new Http2SolrClient.Builder()
-            .withConnectionTimeout(1000, TimeUnit.MILLISECONDS)
-            .withIdleTimeout(2000, TimeUnit.MILLISECONDS)
-            .build();
 
     for (int i = 0; i < solr.length; i++) {
       solr[i] =
@@ -119,22 +114,46 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4 
{
         aSolr.tearDown();
       }
     }
-    httpClient.close();
     super.tearDown();
   }
 
+  private LBClientHolder client(LBSolrClient.Endpoint... baseSolrEndpoints) {
+    if (random().nextBoolean()) {
+      var delegateClient =
+          new Http2SolrClient.Builder()
+              .withConnectionTimeout(1000, TimeUnit.MILLISECONDS)
+              .withIdleTimeout(2000, TimeUnit.MILLISECONDS)
+              .build();
+      var lbClient =
+          new LBHttp2SolrClient.Builder<>(delegateClient, baseSolrEndpoints)
+              .withDefaultCollection(solr[0].getDefaultCollection())
+              .setAliveCheckInterval(500, TimeUnit.MILLISECONDS)
+              .build();
+      return new LBClientHolder(lbClient, delegateClient);
+    } else {
+      var delegateClient =
+          new HttpJdkSolrClient.Builder()
+              .withConnectionTimeout(1000, TimeUnit.MILLISECONDS)
+              .withIdleTimeout(2000, TimeUnit.MILLISECONDS)
+              .withSSLContext(MockTrustManager.ALL_TRUSTING_SSL_CONTEXT)
+              .build();
+      var lbClient =
+          new LBHttp2SolrClient.Builder<>(delegateClient, baseSolrEndpoints)
+              .withDefaultCollection(solr[0].getDefaultCollection())
+              .setAliveCheckInterval(500, TimeUnit.MILLISECONDS)
+              .build();
+      return new LBClientHolder(lbClient, delegateClient);
+    }
+  }
+
   public void testSimple() throws Exception {
     final var baseSolrEndpoints = bootstrapBaseSolrEndpoints(solr.length);
-    try (LBHttp2SolrClient client =
-        new LBHttp2SolrClient.Builder(httpClient, baseSolrEndpoints)
-            .withDefaultCollection(solr[0].getDefaultCollection())
-            .setAliveCheckInterval(500, TimeUnit.MILLISECONDS)
-            .build()) {
+    try (var h = client(baseSolrEndpoints)) {
       SolrQuery solrQuery = new SolrQuery("*:*");
       Set<String> names = new HashSet<>();
       QueryResponse resp = null;
       for (int i = 0; i < solr.length; i++) {
-        resp = client.query(solrQuery);
+        resp = h.lbClient.query(solrQuery);
         assertEquals(10, resp.getResults().getNumFound());
         names.add(resp.getResults().get(0).getFieldValue("name").toString());
       }
@@ -145,7 +164,7 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4 {
       solr[1].jetty = null;
       names.clear();
       for (int i = 0; i < solr.length; i++) {
-        resp = client.query(solrQuery);
+        resp = h.lbClient.query(solrQuery);
         assertEquals(10, resp.getResults().getNumFound());
         names.add(resp.getResults().get(0).getFieldValue("name").toString());
       }
@@ -158,7 +177,7 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4 {
       Thread.sleep(1200);
       names.clear();
       for (int i = 0; i < solr.length; i++) {
-        resp = client.query(solrQuery);
+        resp = h.lbClient.query(solrQuery);
         assertEquals(10, resp.getResults().getNumFound());
         names.add(resp.getResults().get(0).getFieldValue("name").toString());
       }
@@ -168,19 +187,15 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4 
{
 
   public void testTwoServers() throws Exception {
     final var baseSolrEndpoints = bootstrapBaseSolrEndpoints(2);
-    try (LBHttp2SolrClient client =
-        new LBHttp2SolrClient.Builder(httpClient, baseSolrEndpoints)
-            .withDefaultCollection(solr[0].getDefaultCollection())
-            .setAliveCheckInterval(500, TimeUnit.MILLISECONDS)
-            .build()) {
+    try (var h = client(baseSolrEndpoints)) {
       SolrQuery solrQuery = new SolrQuery("*:*");
       QueryResponse resp = null;
       solr[0].jetty.stop();
       solr[0].jetty = null;
-      resp = client.query(solrQuery);
+      resp = h.lbClient.query(solrQuery);
       String name = resp.getResults().get(0).getFieldValue("name").toString();
       assertEquals("solr/collection11", name);
-      resp = client.query(solrQuery);
+      resp = h.lbClient.query(solrQuery);
       name = resp.getResults().get(0).getFieldValue("name").toString();
       assertEquals("solr/collection11", name);
       solr[1].jetty.stop();
@@ -188,11 +203,11 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4 
{
       solr[0].startJetty();
       Thread.sleep(1200);
       try {
-        resp = client.query(solrQuery);
+        resp = h.lbClient.query(solrQuery);
       } catch (SolrServerException e) {
         // try again after a pause in case the error is lack of time to start 
server
         Thread.sleep(3000);
-        resp = client.query(solrQuery);
+        resp = h.lbClient.query(solrQuery);
       }
       name = resp.getResults().get(0).getFieldValue("name").toString();
       assertEquals("solr/collection10", name);
@@ -201,30 +216,28 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4 
{
 
   public void testReliability() throws Exception {
     final var baseSolrEndpoints = bootstrapBaseSolrEndpoints(solr.length);
-
-    try (LBHttp2SolrClient client =
-        new LBHttp2SolrClient.Builder(httpClient, baseSolrEndpoints)
-            .withDefaultCollection(solr[0].getDefaultCollection())
-            .setAliveCheckInterval(500, TimeUnit.MILLISECONDS)
-            .build()) {
+    try (var h = client(baseSolrEndpoints)) {
 
       // Kill a server and test again
       solr[1].jetty.stop();
       solr[1].jetty = null;
 
       // query the servers
-      for (int i = 0; i < solr.length; i++) client.query(new SolrQuery("*:*"));
+      for (int i = 0; i < solr.length; i++) {
+        h.lbClient.query(new SolrQuery("*:*"));
+      }
 
       // Start the killed server once again
       solr[1].startJetty();
       // Wait for the alive check to complete
-      waitForServer(30, client, 3, solr[1].name);
+      waitForServer(30, h.lbClient, 3, solr[1].name);
     }
   }
 
   // wait maximum ms for serverName to come back up
   private void waitForServer(
-      int maxSeconds, LBHttp2SolrClient client, int nServers, String 
serverName) throws Exception {
+      int maxSeconds, LBHttp2SolrClient<?> client, int nServers, String 
serverName)
+      throws Exception {
     final TimeOut timeout = new TimeOut(maxSeconds, TimeUnit.SECONDS, 
TimeSource.NANO_TIME);
     while (!timeout.hasTimedOut()) {
       QueryResponse resp;
@@ -337,8 +350,27 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4 {
         fail("TESTING FAILURE: could not grab requested port.");
       }
       this.port = newPort;
-      //      System.out.println("waiting.........");
-      //      Thread.sleep(5000);
+    }
+  }
+
+  private static class LBClientHolder implements AutoCloseable {
+
+    final LBHttp2SolrClient<?> lbClient;
+    final HttpSolrClientBase delegate;
+
+    LBClientHolder(LBHttp2SolrClient<?> lbClient, HttpSolrClientBase delegate) 
{
+      this.lbClient = lbClient;
+      this.delegate = delegate;
+    }
+
+    @Override
+    public void close() {
+      lbClient.close();
+      try {
+        delegate.close();
+      } catch (IOException ioe) {
+        throw new UncheckedIOException(ioe);
+      }
     }
   }
 }
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientTest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientTest.java
index 3a93488e726..9d2019309b0 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientTest.java
@@ -53,8 +53,8 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
 
     try (Http2SolrClient http2SolrClient =
             new 
Http2SolrClient.Builder(url).withTheseParamNamesInTheUrl(urlParamNames).build();
-        LBHttp2SolrClient testClient =
-            new LBHttp2SolrClient.Builder(http2SolrClient, new 
LBSolrClient.Endpoint(url))
+        LBHttp2SolrClient<Http2SolrClient> testClient =
+            new LBHttp2SolrClient.Builder<>(http2SolrClient, new 
LBSolrClient.Endpoint(url))
                 .build()) {
 
       assertArrayEquals(
@@ -68,6 +68,90 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
     }
   }
 
+  @Test
+  public void testSynchronous() throws Exception {
+    LBSolrClient.Endpoint ep1 = new 
LBSolrClient.Endpoint("http://endpoint.one";);
+    LBSolrClient.Endpoint ep2 = new 
LBSolrClient.Endpoint("http://endpoint.two";);
+    List<LBSolrClient.Endpoint> endpointList = List.of(ep1, ep2);
+
+    Http2SolrClient.Builder b =
+        new 
Http2SolrClient.Builder("http://base.url";).withConnectionTimeout(10, 
TimeUnit.SECONDS);
+    ;
+    try (MockHttpSolrClient client = new MockHttpSolrClient("http://base.url";, 
b);
+        LBHttp2SolrClient<MockHttpSolrClient> testClient =
+            new LBHttp2SolrClient.Builder<>(client, ep1, ep2).build()) {
+
+      String lastEndpoint = null;
+      for (int i = 0; i < 10; i++) {
+        String qValue = "Query Number: " + i;
+        QueryRequest queryRequest = new QueryRequest(new 
MapSolrParams(Map.of("q", qValue)));
+        LBSolrClient.Req req = new LBSolrClient.Req(queryRequest, 
endpointList);
+        LBSolrClient.Rsp response = testClient.request(req);
+
+        String expectedEndpoint =
+            ep1.toString().equals(lastEndpoint) ? ep2.toString() : 
ep1.toString();
+        assertEquals(
+            "There should be round-robin load balancing.", expectedEndpoint, 
response.server);
+        checkSynchonousResponseContent(response, qValue);
+      }
+    }
+  }
+
+  @Test
+  public void testSynchronousWithFalures() throws Exception {
+    LBSolrClient.Endpoint ep1 = new 
LBSolrClient.Endpoint("http://endpoint.one";);
+    LBSolrClient.Endpoint ep2 = new 
LBSolrClient.Endpoint("http://endpoint.two";);
+    List<LBSolrClient.Endpoint> endpointList = List.of(ep1, ep2);
+
+    Http2SolrClient.Builder b =
+        new 
Http2SolrClient.Builder("http://base.url";).withConnectionTimeout(10, 
TimeUnit.SECONDS);
+    ;
+    try (MockHttpSolrClient client = new MockHttpSolrClient("http://base.url";, 
b);
+        LBHttp2SolrClient<MockHttpSolrClient> testClient =
+            new LBHttp2SolrClient.Builder<>(client, ep1, ep2).build()) {
+
+      client.basePathToFail = ep1.getBaseUrl();
+      String basePathToSucceed = ep2.getBaseUrl();
+      String qValue = "First time";
+
+      for (int i = 0; i < 5; i++) {
+        LBSolrClient.Req req =
+            new LBSolrClient.Req(
+                new QueryRequest(new MapSolrParams(Map.of("q", qValue))), 
endpointList);
+        LBSolrClient.Rsp response = testClient.request(req);
+        assertEquals(
+            "The healthy node 'endpoint two' should have served the request: " 
+ i,
+            basePathToSucceed,
+            response.server);
+        checkSynchonousResponseContent(response, qValue);
+      }
+
+      client.basePathToFail = ep2.getBaseUrl();
+      basePathToSucceed = ep1.getBaseUrl();
+      qValue = "Second time";
+
+      for (int i = 0; i < 5; i++) {
+        LBSolrClient.Req req =
+            new LBSolrClient.Req(
+                new QueryRequest(new MapSolrParams(Map.of("q", qValue))), 
endpointList);
+        LBSolrClient.Rsp response = testClient.request(req);
+        assertEquals(
+            "The healthy node 'endpoint one' should have served the request: " 
+ i,
+            basePathToSucceed,
+            response.server);
+        checkSynchonousResponseContent(response, qValue);
+      }
+    }
+  }
+
+  private void checkSynchonousResponseContent(LBSolrClient.Rsp response, 
String qValue) {
+    assertEquals("There should be one element in the respnse.", 1, 
response.getResponse().size());
+    assertEquals(
+        "The response key 'response' should echo the query.",
+        qValue,
+        response.getResponse().get("response"));
+  }
+
   @Test
   public void testAsyncWithFailures() {
 
@@ -81,8 +165,9 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
     Http2SolrClient.Builder b =
         new 
Http2SolrClient.Builder("http://base.url";).withConnectionTimeout(10, 
TimeUnit.SECONDS);
     ;
-    try (MockHttp2SolrClient client = new 
MockHttp2SolrClient("http://base.url";, b);
-        LBHttp2SolrClient testClient = new LBHttp2SolrClient.Builder(client, 
ep1, ep2).build()) {
+    try (MockHttpSolrClient client = new MockHttpSolrClient("http://base.url";, 
b);
+        LBHttp2SolrClient<MockHttpSolrClient> testClient =
+            new LBHttp2SolrClient.Builder<>(client, ep1, ep2).build()) {
 
       for (int j = 0; j < 2; j++) {
         // first time Endpoint One will return error code 500.
@@ -144,8 +229,9 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
 
     Http2SolrClient.Builder b =
         new 
Http2SolrClient.Builder("http://base.url";).withConnectionTimeout(10, 
TimeUnit.SECONDS);
-    try (MockHttp2SolrClient client = new 
MockHttp2SolrClient("http://base.url";, b);
-        LBHttp2SolrClient testClient = new LBHttp2SolrClient.Builder(client, 
ep1, ep2).build()) {
+    try (MockHttpSolrClient client = new MockHttpSolrClient("http://base.url";, 
b);
+        LBHttp2SolrClient<MockHttpSolrClient> testClient =
+            new LBHttp2SolrClient.Builder<>(client, ep1, ep2).build()) {
 
       int limit = 10; // For simplicity use an even limit
       List<CompletableFuture<LBSolrClient.Rsp>> responses = new ArrayList<>();
@@ -200,7 +286,7 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
     }
   }
 
-  public static class MockHttp2SolrClient extends Http2SolrClient {
+  public static class MockHttpSolrClient extends Http2SolrClient {
 
     public List<SolrRequest<?>> lastSolrRequests = new ArrayList<>();
 
@@ -212,13 +298,26 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
 
     public String tmpBaseUrl = null;
 
-    protected MockHttp2SolrClient(String serverBaseUrl, Builder builder) {
+    protected MockHttpSolrClient(String serverBaseUrl, Builder builder) {
+
       // TODO: Consider creating an interface for Http*SolrClient
       // so mocks can Implement, not Extend, and not actually need to
       // build an (unused) client
       super(serverBaseUrl, builder);
     }
 
+    @Override
+    public NamedList<Object> request(final SolrRequest<?> request, String 
collection)
+        throws SolrServerException, IOException {
+      lastSolrRequests.add(request);
+      lastBasePaths.add(tmpBaseUrl);
+      lastCollections.add(collection);
+      if (tmpBaseUrl.equals(basePathToFail)) {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "We 
should retry this.");
+      }
+      return generateResponse(request);
+    }
+
     @Override
     public <R> R requestWithBaseUrl(
         String baseUrl, SolrClientFunction<Http2SolrClient, R> clientFunction)
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/MockTrustManager.java 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/MockTrustManager.java
new file mode 100644
index 00000000000..c7e1351d01d
--- /dev/null
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/MockTrustManager.java
@@ -0,0 +1,100 @@
+/*
+ * 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.impl;
+
+import java.net.Socket;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509ExtendedTrustManager;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.util.SSLTestConfig;
+
+/**
+ * Taken from: https://www.baeldung.com/java-httpclient-ssl sec 4.1, 
2024/02/12. This is an
+ * all-trusting Trust Manager. Works with self-signed certificates.
+ */
+public class MockTrustManager extends X509ExtendedTrustManager {
+
+  public static final SSLContext ALL_TRUSTING_SSL_CONTEXT;
+
+  private static final MockTrustManager INSTANCE = new MockTrustManager();
+
+  static {
+    try {
+      KeyManagerFactory keyManagerFactory =
+          
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+      SSLTestConfig stc = SolrTestCaseJ4.sslConfig;
+      keyManagerFactory.init(stc.defaultKeyStore(), 
stc.defaultKeyStorePassword().toCharArray());
+
+      SSLContext sslContext = SSLContext.getInstance("SSL");
+      sslContext.init(
+          keyManagerFactory.getKeyManagers(),
+          new TrustManager[] {INSTANCE},
+          stc.notSecureSecureRandom());
+      ALL_TRUSTING_SSL_CONTEXT = sslContext;
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private MockTrustManager() {}
+
+  @Override
+  public void checkClientTrusted(X509Certificate[] chain, String authType, 
Socket socket)
+      throws CertificateException {
+    // no-op
+  }
+
+  @Override
+  public void checkServerTrusted(X509Certificate[] chain, String authType, 
Socket socket)
+      throws CertificateException {
+    // no-op
+  }
+
+  @Override
+  public void checkClientTrusted(X509Certificate[] chain, String authType, 
SSLEngine engine)
+      throws CertificateException {
+    // no-op
+  }
+
+  @Override
+  public void checkServerTrusted(X509Certificate[] chain, String authType, 
SSLEngine engine)
+      throws CertificateException {
+    // no-op
+  }
+
+  @Override
+  public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+    return new java.security.cert.X509Certificate[0];
+  }
+
+  @Override
+  public void checkClientTrusted(X509Certificate[] chain, String authType)
+      throws CertificateException {
+    // no-op
+  }
+
+  @Override
+  public void checkServerTrusted(java.security.cert.X509Certificate[] chain, 
String authType)
+      throws CertificateException {
+    // no-op
+  }
+}


Reply via email to