Allow for providing Jetty HttpClient to Jetty Connector (#4089)

Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java
index 68886e7..8346683 100644
--- a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java
+++ b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -28,6 +28,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Future;
@@ -136,18 +137,28 @@
      * @param config client configuration.
      */
     JettyConnector(final Client jaxrsClient, final Configuration config) {
-        final SSLContext sslContext = jaxrsClient.getSslContext();
-        final SslContextFactory sslContextFactory = new SslContextFactory();
-        sslContextFactory.setSslContext(sslContext);
+        HttpClient httpClient = null;
+        if (config.isRegistered(JettyHttpClientSupplier.class)) {
+            Optional<Object> contract = config.getInstances().stream()
+                    .filter(a-> JettyHttpClientSupplier.class.isInstance(a)).findFirst();
+            if (contract.isPresent()) {
+                httpClient = ((JettyHttpClientSupplier) contract.get()).getHttpClient();
+            }
+        }
+        if (httpClient == null) {
+            final SSLContext sslContext = jaxrsClient.getSslContext();
+            final SslContextFactory sslContextFactory = new SslContextFactory();
+            sslContextFactory.setSslContext(sslContext);
+            httpClient = new HttpClient(sslContextFactory);
+        }
+        this.client = httpClient;
 
         Boolean enableHostnameVerification = (Boolean) config.getProperties()
-                                                             .get(JettyClientProperties.ENABLE_SSL_HOSTNAME_VERIFICATION);
+                .get(JettyClientProperties.ENABLE_SSL_HOSTNAME_VERIFICATION);
         if (enableHostnameVerification != null && enableHostnameVerification) {
-            sslContextFactory.setEndpointIdentificationAlgorithm("https");
+            client.getSslContextFactory().setEndpointIdentificationAlgorithm("https");
         }
 
-        this.client = new HttpClient(sslContextFactory);
-
         final Object connectTimeout = config.getProperties().get(ClientProperties.CONNECT_TIMEOUT);
         if (connectTimeout != null && connectTimeout instanceof Integer && (Integer) connectTimeout > 0) {
             client.setConnectTimeout((Integer) connectTimeout);
diff --git a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java
new file mode 100644
index 0000000..7547cd9
--- /dev/null
+++ b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.connector;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * A contract that allows for an optional registration of user predefined Jetty {@code HttpClient}
+ * that is consequently used by {@link JettyConnector}
+ */
+@Contract
+public interface JettyHttpClientContract {
+    /**
+     * Supply a user predefined HttpClient
+     * @return a user predefined HttpClient
+     */
+    HttpClient getHttpClient();
+}
diff --git a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java
new file mode 100644
index 0000000..681165d
--- /dev/null
+++ b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+package org.glassfish.jersey.jetty.connector;
+
+import org.eclipse.jetty.client.HttpClient;
+
+/**
+ * Jetty HttpClient supplier to be registered into Jersey configuration to be used by {@link JettyConnector}.
+ * Not every possible configuration option is covered by the Jetty Connector and this supplier offers a way to provide
+ * an HttpClient that has configured the options not covered by the Jetty Connector.
+ * <p>
+ *     Typical usage:
+ * </p>
+ * <pre>
+ * {@code
+ * HttpClient httpClient = ...
+ *
+ * ClientConfig config = new ClientConfig();
+ * config.connectorProvider(new JettyConnectorProvider());
+ * config.register(new JettyHttpClientSupplier(httpClient));
+ * Client client = ClientBuilder.newClient(config);
+ * }
+ * </pre>
+ * <p>
+ *     The {@code HttpClient} is configured as if it was created by {@link JettyConnector} the usual way.
+ * </p>
+ */
+public class JettyHttpClientSupplier implements JettyHttpClientContract {
+    private final HttpClient httpClient;
+
+    /**
+     * {@code HttpClient} supplier to be optionally registered to a {@link org.glassfish.jersey.client.ClientConfig}
+     * @param httpClient a HttpClient to be supplied when {@link JettyConnector#getHttpClient()} is called.
+     */
+    public JettyHttpClientSupplier(HttpClient httpClient) {
+        this.httpClient = httpClient;
+    }
+
+    @Override
+    public HttpClient getHttpClient() {
+        return httpClient;
+    }
+}
diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/UnderlyingHttpClientAccessTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/UnderlyingHttpClientAccessTest.java
index 7cefc2b..fc60fc7 100644
--- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/UnderlyingHttpClientAccessTest.java
+++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/UnderlyingHttpClientAccessTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -24,8 +24,11 @@
 
 import org.eclipse.jetty.client.HttpClient;
 import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
 
 /**
  * Test of access to the underlying HTTP client instance used by the connector.
@@ -54,4 +57,18 @@
                 hcOnClient, hcOnTarget
         );
     }
+
+    @Test
+    public void testGetProvidedClientInstance() {
+        final HttpClient httpClient = new HttpClient();
+        final ClientConfig clientConfig = new ClientConfig()
+                .connectorProvider(new JettyConnectorProvider())
+                .register(new JettyHttpClientSupplier(httpClient));
+        final Client client = ClientBuilder.newClient(clientConfig);
+        final WebTarget target = client.target("http://localhost/");
+        final HttpClient hcOnTarget = JettyConnectorProvider.getHttpClient(target);
+
+        assertThat("Instance provided to a ClientConfig differs from instance provided by JettyProvider",
+                httpClient, is(hcOnTarget));
+    }
 }