HTTP/2 for JNH connector

Signed-off-by: Maxim Nesen <maxim.nesen@oracle.com>
diff --git a/connectors/jnh-connector/src/main/java/org/glassfish/jersey/jnh/connector/JavaNetHttpClientProperties.java b/connectors/jnh-connector/src/main/java/org/glassfish/jersey/jnh/connector/JavaNetHttpClientProperties.java
index a964200..a9c0248 100644
--- a/connectors/jnh-connector/src/main/java/org/glassfish/jersey/jnh/connector/JavaNetHttpClientProperties.java
+++ b/connectors/jnh-connector/src/main/java/org/glassfish/jersey/jnh/connector/JavaNetHttpClientProperties.java
@@ -86,6 +86,17 @@
             "jersey.config.jnh.client.disableCookies";
 
     /**
+     * HTTP version - if null or instance of HttpClient.Version.HTTP_1_1 the version will be set to HTTP_1_1
+     * if version is HttpClient.Version.HTTP_2 the client will attempt to perform each request using HTTP_2 protocol
+     * but if not supported by server, the protocol will be still HTTP_1_1
+     *
+     * @since 3.1.4
+     */
+    public static final String HTTP_VERSION =
+            "jersey.config.jnh.client.httpVersion";
+
+
+    /**
      * Prevent this class from instantiation.
      */
     private JavaNetHttpClientProperties() {}
diff --git a/connectors/jnh-connector/src/main/java/org/glassfish/jersey/jnh/connector/JavaNetHttpConnector.java b/connectors/jnh-connector/src/main/java/org/glassfish/jersey/jnh/connector/JavaNetHttpConnector.java
index fb0f613..69d25eb 100644
--- a/connectors/jnh-connector/src/main/java/org/glassfish/jersey/jnh/connector/JavaNetHttpConnector.java
+++ b/connectors/jnh-connector/src/main/java/org/glassfish/jersey/jnh/connector/JavaNetHttpConnector.java
@@ -83,8 +83,10 @@
      * @param configuration the configuration properties for this connector
      */
     public JavaNetHttpConnector(final Client client, final Configuration configuration) {
-        HttpClient.Builder httpClientBuilder = HttpClient.newBuilder();
-        httpClientBuilder.version(HttpClient.Version.HTTP_1_1);
+        final HttpClient.Builder httpClientBuilder = HttpClient.newBuilder();
+        final HttpClient.Version version =
+                getPropertyOrNull(configuration, JavaNetHttpClientProperties.HTTP_VERSION, HttpClient.Version.class);
+        httpClientBuilder.version(version == null ? HttpClient.Version.HTTP_1_1 : version);
         SSLContext sslContext = client.getSslContext();
         if (sslContext != null) {
             httpClientBuilder.sslContext(sslContext);
@@ -230,6 +232,9 @@
         if (propertyObject == null) {
             return null;
         }
+        if (resultClass.isEnum() && propertyObject instanceof String) {
+            return (T) Enum.valueOf(resultClass.asSubclass(Enum.class), (String) propertyObject);
+        }
         if (!resultClass.isInstance(propertyObject)) {
             LOGGER.warning(LocalizationMessages.ERROR_INVALID_CLASS(propertyKey, resultClass.getName()));
             return null;
diff --git a/connectors/jnh-connector/src/test/java/org/glassfish/jersey/jnh/connector/Http2PresenceTest.java b/connectors/jnh-connector/src/test/java/org/glassfish/jersey/jnh/connector/Http2PresenceTest.java
new file mode 100644
index 0000000..a2eaa3b
--- /dev/null
+++ b/connectors/jnh-connector/src/test/java/org/glassfish/jersey/jnh/connector/Http2PresenceTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2023 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.jnh.connector;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.HttpHeaders;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.spi.ConnectorProvider;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import java.net.http.HttpClient;
+import java.util.List;
+import java.util.logging.Logger;
+
+import static java.net.http.HttpClient.Version.HTTP_2;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests the HTTP2 presence.
+ *
+ */
+public class Http2PresenceTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(Http2PresenceTest.class.getName());
+
+    @Path("/test")
+    public static class HttpMethodResource {
+
+        @GET
+        public String testUserAgent(@Context HttpHeaders httpHeaders) {
+            final List<String> requestHeader = httpHeaders.getRequestHeader(HttpHeaders.USER_AGENT);
+            if (requestHeader.size() != 1) {
+                return "FAIL";
+            }
+            return requestHeader.get(0);
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(HttpMethodResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.property(JavaNetHttpClientProperties.HTTP_VERSION, HTTP_2).connectorProvider(new JavaNetHttpConnectorProvider());
+    }
+
+    @Test
+    public void testHttp2Presence() {
+        final ConnectorProvider provider = ((ClientConfig) target().getConfiguration()).getConnectorProvider();
+        assertTrue(provider instanceof JavaNetHttpConnectorProvider);
+
+        final HttpClient client = ((JavaNetHttpConnectorProvider) provider).getHttpClient(target());
+        assertEquals(HTTP_2, client.version());
+    }
+
+    /**
+     * Test, that {@code User-agent} header is as set by Jersey, not by underlying Jetty client.
+     */
+    @Test
+    public void testUserAgent() {
+        String response = target().path("test").request().get(String.class);
+        assertTrue(response.startsWith("Jersey"), "User-agent header should start with 'Jersey', but was " + response);
+    }
+}
\ No newline at end of file
diff --git a/docs/src/main/docbook/appendix-properties.xml b/docs/src/main/docbook/appendix-properties.xml
index d8dad09..78bfb96 100644
--- a/docs/src/main/docbook/appendix-properties.xml
+++ b/docs/src/main/docbook/appendix-properties.xml
@@ -2119,6 +2119,24 @@
                             </para>
                         </entry>
                     </row>
+                    <row>
+                        <entry>&jersey.jnh.JavaNetHttpClientProperties.HTTP_VERSION;</entry>
+                        <entry><literal>jersey.config.jnh.client.httpVersion</literal></entry>
+                        <entry>
+                            <para>
+                                HTTP version - if null or instance of HttpClient.Version.HTTP_1_1
+                                the version will be set to HTTP_1_1. If version is HttpClient.Version.HTTP_2
+                                the client will attempt to perform each request using HTTP_2 protocol
+                                but if not supported by server, the protocol will be still HTTP_1_1
+                            </para>
+                            <para>
+                                The value MUST be an instance of <literal>java.net.http.HttpClient.Version</literal>.
+                            </para>
+                            <para>
+                                The default value is &lit.null;.
+                            </para>
+                        </entry>
+                    </row>
                 </tbody>
             </tgroup>
         </table>
diff --git a/docs/src/main/docbook/jersey.ent b/docs/src/main/docbook/jersey.ent
index 4a81897..ab13afb 100644
--- a/docs/src/main/docbook/jersey.ent
+++ b/docs/src/main/docbook/jersey.ent
@@ -493,6 +493,7 @@
 <!ENTITY jersey.jnh.JavaNetHttpClientProperties.SSL_PARAMETERS "<link xlink:href='&jersey.javadoc.uri.prefix;/jnh/connector/JavaNetHttpClientProperties.html#SSL_PARAMETERS'>JavaNetHttpClientProperties.SSL_PARAMETERS</link>">
 <!ENTITY jersey.jnh.JavaNetHttpClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION "<link xlink:href='&jersey.javadoc.uri.prefix;/jnh/connector/JavaNetHttpClientProperties.html#PREEMPTIVE_BASIC_AUTHENTICATION'>JavaNetHttpClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION</link>">
 <!ENTITY jersey.jnh.JavaNetHttpClientProperties.DISABLE_COOKIES "<link xlink:href='&jersey.javadoc.uri.prefix;/jnh/connector/JavaNetHttpClientProperties.html#DISABLE_COOKIES'>JavaNetHttpClientProperties.DISABLE_COOKIES</link>">
+<!ENTITY jersey.jnh.JavaNetHttpClientProperties.HTTP_VERSION "<link xlink:href='&jersey.javadoc.uri.prefix;/jnh/connector/JavaNetHttpClientProperties.html#HTTP_VERSION'>JavaNetHttpClientProperties.HTTP_VERSION</link>">
 <!ENTITY jersey.linking.DeclarativeLinkingFeature "<link xlink:href='&jersey.javadoc.uri.prefix;/linking/DeclarativeLinkingFeature.html'>DeclarativeLinkingFeature</link>">
 <!ENTITY jersey.logging.LoggingFeature "<link xlink:href='&jersey.javadoc.uri.prefix;/logging/LoggingFeature.html'>LoggingFeature</link>">
 <!ENTITY jersey.logging.LoggingFeature.DEFAULT_LOGGER_NAME "<link xlink:href='&jersey.javadoc.uri.prefix;/logging/LoggingFeature.html#DEFAULT_LOGGER_NAME'>LoggingFeature.DEFAULT_LOGGER_NAME</link>">