runtimeProperties = configuration.getProperties();
ExecutorServiceProvider defaultAsyncExecutorProvider;
ScheduledExecutorServiceProvider defaultScheduledExecutorProvider;
@@ -94,12 +96,12 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) {
.named("ClientAsyncThreadPoolSize");
injectionManager.register(asyncThreadPoolSizeBinding);
- defaultAsyncExecutorProvider = new DefaultClientAsyncExecutorProvider(asyncThreadPoolSize);
+ defaultAsyncExecutorProvider = new DefaultClientAsyncExecutorProvider(asyncThreadPoolSize, configuration);
} else {
if (MANAGED_EXECUTOR_SERVICE != null) {
defaultAsyncExecutorProvider = new ClientExecutorServiceProvider(MANAGED_EXECUTOR_SERVICE);
} else {
- defaultAsyncExecutorProvider = new DefaultClientAsyncExecutorProvider(0);
+ defaultAsyncExecutorProvider = new DefaultClientAsyncExecutorProvider(0, configuration);
}
}
}
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java
index c5a920c0b4..88a3d017d7 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java
@@ -467,38 +467,45 @@ public final class ClientProperties {
public static final String QUERY_PARAM_STYLE = "jersey.config.client.uri.query.param.style";
/**
+ * Sets the {@link org.glassfish.jersey.client.spi.ConnectorProvider} class. Overrides the value from META-INF/services.
+ *
*
- * Most connectors support HOST header value to be used as an SNIHostName. However, the HOST header is restricted in JDK.
- * {@code HttpUrlConnector} and {@code JavaNetHttpConnector} need
- * to have an extra System Property set to allow HOST header.
- * As an option to HOST header, this property allows the HOST name to be pre-set on a Client and does not need to
- * be set on each request.
+ * The value MUST be an instance of {@code String}.
*
*
- * The value MUST be an instance of {@link java.lang.String}.
+ * The property is recognized by {@link ClientBuilder}.
*
*
* The name of the configuration property is {@value}.
*
- * @since 3.1.2
+ * @since 2.40
*/
- public static final String SNI_HOST_NAME = "jersey.config.client.sniHostName";
+ public static final String CONNECTOR_PROVIDER = "jersey.config.client.connector.provider";
/**
- * Sets the {@link org.glassfish.jersey.client.spi.ConnectorProvider} class. Overrides the value from META-INF/services.
- *
*
- * The value MUST be an instance of {@code String}.
+ * Sets the {@code hostName} to be used for calculating the {@link javax.net.ssl.SNIHostName} during the HTTPS request.
+ * Takes precedence over the HTTP HOST header, if set.
*
*
- * The property is recognized by {@link ClientBuilder}.
+ * By default, the {@code SNIHostName} is set when the HOST HTTP header differs from the HTTP request host.
+ * When the {@code hostName} matches the HTTPS request host, the {@code SNIHostName} is not set,
+ * and the HTTP HOST header is not used for setting the {@code SNIHostName}. This allows for Domain Fronting.
+ *
+ *
+ * Most connectors support HOST header value to be used as an SNIHostName. However, the HOST header is restricted in JDK.
+ * {@code HttpUrlConnector} and {@code JavaNetHttpConnector} need
+ * to have an extra System Property set to allow HOST header.
+ *
+ *
+ * The value MUST be an instance of {@link java.lang.String}.
*
*
* The name of the configuration property is {@value}.
*
- * @since 2.40
+ * @since 3.1.2
*/
- public static final String CONNECTOR_PROVIDER = "jersey.config.client.connector.provider";
+ public static final String SNI_HOST_NAME = "jersey.config.client.snihostname";
/**
* The {@link javax.net.ssl.SSLContext} {@link java.util.function.Supplier} to be used to set ssl context in the current
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/DefaultClientAsyncExecutorProvider.java b/core-client/src/main/java/org/glassfish/jersey/client/DefaultClientAsyncExecutorProvider.java
index 2f9fa71006..2a19b0d314 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/DefaultClientAsyncExecutorProvider.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/DefaultClientAsyncExecutorProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024 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
@@ -20,6 +20,9 @@
import jakarta.inject.Inject;
import jakarta.inject.Named;
+import jakarta.ws.rs.core.Configuration;
+import jakarta.ws.rs.core.Context;
+
import org.glassfish.jersey.client.internal.LocalizationMessages;
import org.glassfish.jersey.internal.util.collection.LazyValue;
@@ -46,8 +49,9 @@ class DefaultClientAsyncExecutorProvider extends ThreadPoolExecutorProvider {
* See also {@link org.glassfish.jersey.client.ClientProperties#ASYNC_THREADPOOL_SIZE}.
*/
@Inject
- public DefaultClientAsyncExecutorProvider(@Named("ClientAsyncThreadPoolSize") final int poolSize) {
- super("jersey-client-async-executor");
+ public DefaultClientAsyncExecutorProvider(@Named("ClientAsyncThreadPoolSize") final int poolSize,
+ @Context Configuration configuration) {
+ super("jersey-client-async-executor", configuration);
this.asyncThreadPoolSize = Values.lazy(new Value() {
@Override
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java
index a92adf5472..79dc60e34b 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java
@@ -18,24 +18,21 @@
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.ClientRequest;
+import org.glassfish.jersey.http.HttpHeaders;
import org.glassfish.jersey.internal.PropertiesResolver;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import jakarta.ws.rs.core.Configuration;
-import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.UriBuilder;
-import org.glassfish.jersey.internal.PropertiesResolver;
-import org.glassfish.jersey.internal.util.PropertiesHelper;
-
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Optional;
-import java.util.Properties;
/**
* A unified routines to configure {@link SSLParameters}.
@@ -49,11 +46,11 @@ public final class SSLParamConfigurator {
* Builder of the {@link SSLParamConfigurator} instance.
*/
public static final class Builder {
- private URI uri = null;
+ private URI uri;
private String sniHostNameHeader = null;
- private String sniHostNameProperty = null;
- private boolean setAlways = false;
private String sniHostPrecedence = null;
+ private boolean setAlways = false;
+
/**
* Sets the SNIHostName and {@link URI} from the {@link ClientRequest} instance.
@@ -62,7 +59,7 @@ public static final class Builder {
*/
public Builder request(ClientRequest clientRequest) {
this.sniHostNameHeader = getSniHostNameHeader(clientRequest.getHeaders());
- this.sniHostNameProperty = clientRequest.resolveProperty(ClientProperties.SNI_HOST_NAME, String.class);
+ this.sniHostPrecedence = resolveSniHostNameProperty(clientRequest);
this.uri = clientRequest.getUri();
return this;
}
@@ -73,7 +70,7 @@ public Builder request(ClientRequest clientRequest) {
* @return the builder instance
*/
public Builder configuration(Configuration configuration) {
- this.sniHostNameProperty = (String) configuration.getProperty(ClientProperties.SNI_HOST_NAME);
+ this.sniHostPrecedence = getSniHostNameProperty(configuration);
return this;
}
@@ -122,7 +119,7 @@ public Builder setSNIAlways(boolean setAlways) {
* @return the builder instance.
*/
public Builder setSNIHostName(String hostName) {
- sniHostPrecedence = trimSniHostName(hostName);
+ sniHostPrecedence = hostName;
return this;
}
@@ -142,7 +139,7 @@ public Builder setSNIHostName(String hostName) {
* @return the builder instance.
*/
public Builder setSNIHostName(Configuration configuration) {
- return setSNIHostName((String) configuration.getProperty(ClientProperties.SNI_HOST_NAME));
+ return setSNIHostName(getSniHostNameProperty(configuration));
}
/**
@@ -161,7 +158,7 @@ public Builder setSNIHostName(Configuration configuration) {
* @return the builder instance.
*/
public Builder setSNIHostName(PropertiesResolver resolver) {
- return setSNIHostName(resolver.resolveProperty(ClientProperties.SNI_HOST_NAME, String.class));
+ return setSNIHostName(resolveSniHostNameProperty(resolver));
}
/**
@@ -179,29 +176,34 @@ private static String getSniHostNameHeader(Map> httpHeaders
}
final String hostHeader = hostHeaders.get(0).toString();
- return trimSniHostName(hostHeader);
+ return hostHeader;
}
- private static String trimSniHostName(String sniHostName) {
- final String trimmedHeader;
- if (sniHostName != null) {
- int index = sniHostName.indexOf(':'); // RFC 7230 Host = uri-host [ ":" port ] ;
- final String trimmedHeader0 = index != -1 ? sniHostName.substring(0, index).trim() : sniHostName.trim();
- trimmedHeader = trimmedHeader0.isEmpty() ? sniHostName : trimmedHeader0;
- } else {
- trimmedHeader = null;
+ private static String resolveSniHostNameProperty(PropertiesResolver resolver) {
+ String property = resolver.resolveProperty(ClientProperties.SNI_HOST_NAME, String.class);
+ if (property == null) {
+ property = resolver.resolveProperty(ClientProperties.SNI_HOST_NAME.toLowerCase(Locale.ROOT), String.class);
}
+ return property;
+ }
- return trimmedHeader;
+ private static String getSniHostNameProperty(Configuration configuration) {
+ Object property = configuration.getProperty(ClientProperties.SNI_HOST_NAME);
+ if (property == null) {
+ property = configuration.getProperty(ClientProperties.SNI_HOST_NAME.toLowerCase(Locale.ROOT));
+ }
+ return (String) property;
}
}
private SSLParamConfigurator(SSLParamConfigurator.Builder builder) {
- String sniHostName = builder.sniHostNameHeader == null ? builder.sniHostNameProperty : builder.sniHostNameHeader;
uri = builder.uri;
- sniConfigurator = SniConfigurator.createWhenHostHeader(uri,
- builder.sniHostPrecedence != null ? builder.sniHostPrecedence : sniHostName,
- builder.setAlways);
+ if (builder.sniHostPrecedence == null) {
+ sniConfigurator = SniConfigurator.createWhenHostHeader(uri, builder.sniHostNameHeader, builder.setAlways);
+ } else {
+ // Do not set SNI always, the property can be used to turn the SNI off
+ sniConfigurator = SniConfigurator.createWhenHostHeader(uri, builder.sniHostPrecedence, false);
+ }
}
/**
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java
index d5d916db19..1ecdc59dd8 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java
@@ -50,27 +50,29 @@ String getHostName() {
}
/**
- * Create {@link SniConfigurator} when {@link HttpHeaders#HOST} is set different from the request URI host
+ * Create {@link SniConfigurator} when {@code sniHost} is set different from the request URI host
* (or {@code whenDiffer}.is false).
* @param hostUri the Uri of the HTTP request
- * @param sniHostName the SniHostName either from HttpHeaders or the
- * {@link org.glassfish.jersey.client.ClientProperties#SNI_HOST_NAME} property from Configuration object.
+ * @param sniHost the preferred host name to create the {@link SNIHostName}
* @param whenDiffer create {@SniConfigurator only when different from the request URI host}
- * @return Optional {@link SniConfigurator} or empty when {@link HttpHeaders#HOST} is equal to the requestHost
+ * @return Optional {@link SniConfigurator} or empty when {@code sniHost} is equal to the requestHost
*/
- static Optional createWhenHostHeader(URI hostUri, String sniHostName, boolean whenDiffer) {
- if (sniHostName == null) {
+ static Optional createWhenHostHeader(URI hostUri, String sniHost, boolean whenDiffer) {
+ final String trimmedHeader;
+ if (sniHost != null) {
+ int index = sniHost.indexOf(':'); // RFC 7230 Host = uri-host [ ":" port ] ;
+ final String trimmedHeader0 = index != -1 ? sniHost.substring(0, index).trim() : sniHost.trim();
+ trimmedHeader = trimmedHeader0.isEmpty() ? sniHost : trimmedHeader0;
+ } else {
return Optional.empty();
}
- if (hostUri != null) {
- final String hostUriString = hostUri.getHost();
- if (!whenDiffer && hostUriString.equals(sniHostName)) {
- return Optional.empty();
- }
+ final String hostUriString = hostUri.getHost();
+ if (!whenDiffer && hostUriString.equals(trimmedHeader)) {
+ return Optional.empty();
}
- return Optional.of(new SniConfigurator(sniHostName));
+ return Optional.of(new SniConfigurator(trimmedHeader));
}
/**
diff --git a/core-client/src/test/java/org/glassfish/jersey/client/innate/http/SSLParamConfiguratorTest.java b/core-client/src/test/java/org/glassfish/jersey/client/innate/http/SSLParamConfiguratorTest.java
new file mode 100644
index 0000000000..06dcdec4e3
--- /dev/null
+++ b/core-client/src/test/java/org/glassfish/jersey/client/innate/http/SSLParamConfiguratorTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2024 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.client.innate.http;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.client.ClientRequest;
+import org.glassfish.jersey.client.JerseyClient;
+import org.glassfish.jersey.http.HttpHeaders;
+import org.glassfish.jersey.internal.MapPropertiesDelegate;
+import org.glassfish.jersey.internal.PropertiesDelegate;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public class SSLParamConfiguratorTest {
+ @Test
+ public void testNoHost() {
+ final URI uri = URI.create("http://xxx.com:8080");
+ final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
+ final ClientConfig config = client.getConfiguration();
+ final PropertiesDelegate delegate = new MapPropertiesDelegate();
+ ClientRequest request = new ClientRequest(uri, config, delegate) {};
+ SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
+ MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(false));
+ }
+
+ @Test
+ public void testHostHeaderHasPrecedence() {
+ final URI uri = URI.create("http://xxx.com:8080");
+ final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
+ final ClientConfig config = client.getConfiguration();
+ final PropertiesDelegate delegate = new MapPropertiesDelegate();
+ ClientRequest request = new ClientRequest(uri, config, delegate) {};
+ request.getHeaders().add(HttpHeaders.HOST, "yyy.com");
+ SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
+ MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
+ MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("yyy.com"));
+ }
+
+ @Test
+ public void testPropertyOnClientHasPrecedence() {
+ final URI uri = URI.create("http://xxx.com:8080");
+ final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
+ final ClientConfig config = client.getConfiguration();
+ final PropertiesDelegate delegate = new MapPropertiesDelegate();
+ client.property(ClientProperties.SNI_HOST_NAME, "yyy.com");
+ ClientRequest request = new ClientRequest(uri, config, delegate) {};
+ SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
+ MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
+ MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("yyy.com"));
+ }
+
+ @Test
+ public void testPropertyOnDelegateHasPrecedence() {
+ final URI uri = URI.create("http://xxx.com:8080");
+ final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
+ final ClientConfig config = client.getConfiguration();
+ final PropertiesDelegate delegate = new MapPropertiesDelegate();
+ client.property(ClientProperties.SNI_HOST_NAME, "yyy.com");
+ delegate.setProperty(ClientProperties.SNI_HOST_NAME, "zzz.com");
+ ClientRequest request = new ClientRequest(uri, config, delegate) {};
+ SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
+ MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
+ MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("zzz.com"));
+ }
+
+ @Test
+ public void testPropertyOnDelegateHasPrecedenceOverHost() {
+ final URI uri = URI.create("http://xxx.com:8080");
+ final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
+ final ClientConfig config = client.getConfiguration();
+ final PropertiesDelegate delegate = new MapPropertiesDelegate();
+ client.property(ClientProperties.SNI_HOST_NAME, "yyy.com");
+ delegate.setProperty(ClientProperties.SNI_HOST_NAME, "zzz.com");
+ ClientRequest request = new ClientRequest(uri, config, delegate) {};
+ request.getHeaders().add(HttpHeaders.HOST, "www.com");
+ SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
+ MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
+ MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("zzz.com"));
+ }
+
+ @Test
+ public void testDisableSni() {
+ final URI uri = URI.create("http://xxx.com:8080");
+ final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
+ final ClientConfig config = client.getConfiguration();
+ final PropertiesDelegate delegate = new MapPropertiesDelegate();
+ client.property(ClientProperties.SNI_HOST_NAME, "yyy.com");
+ delegate.setProperty(ClientProperties.SNI_HOST_NAME, "xxx.com");
+ ClientRequest request = new ClientRequest(uri, config, delegate) {};
+ request.getHeaders().add(HttpHeaders.HOST, "www.com");
+ SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
+ MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(false));
+ MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("xxx.com"));
+ }
+
+ @Test
+ public void testLowerCasePropertyOnClientHasPrecedence() {
+ final URI uri = URI.create("http://xxx.com:8080");
+ final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
+ final ClientConfig config = client.getConfiguration();
+ final PropertiesDelegate delegate = new MapPropertiesDelegate();
+ client.property(ClientProperties.SNI_HOST_NAME.toLowerCase(Locale.ROOT), "yyy.com");
+ ClientRequest request = new ClientRequest(uri, config, delegate) {};
+ request.getHeaders().add(HttpHeaders.HOST, "www.com");
+ SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
+ MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
+ MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("yyy.com"));
+ }
+
+ @Test
+ public void testUriAndHeadersAndConfig() {
+ final URI uri = URI.create("http://xxx.com:8080");
+ final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
+ Map> httpHeaders = new MultivaluedHashMap<>();
+ httpHeaders.put(HttpHeaders.HOST, Collections.singletonList("www.com"));
+ SSLParamConfigurator configurator = SSLParamConfigurator.builder()
+ .uri(uri)
+ .headers(httpHeaders)
+ .configuration(client.getConfiguration())
+ .build();
+ MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
+ MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("www.com"));
+
+ client.property(ClientProperties.SNI_HOST_NAME, "yyy.com");
+ configurator = SSLParamConfigurator.builder()
+ .uri(uri)
+ .headers(httpHeaders)
+ .configuration(client.getConfiguration())
+ .build();
+ MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
+ MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("yyy.com"));
+ }
+}
diff --git a/core-common/pom.xml b/core-common/pom.xml
index 13ab2a5b8a..02713f1cc0 100644
--- a/core-common/pom.xml
+++ b/core-common/pom.xml
@@ -340,7 +340,7 @@
-
diff --git a/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java b/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java
index e71e5e14fc..d7f59859e6 100644
--- a/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java
+++ b/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2024 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
@@ -320,6 +320,30 @@ public final class CommonProperties {
*/
public static final String PARAM_CONVERTERS_THROW_IAE = "jersey.config.paramconverters.throw.iae";
+ /**
+ *
+ * Defines the {@link java.util.concurrent.ThreadFactory} to be used by internal default Executor Services.
+ *
+ *
+ * The default is {@link java.util.concurrent.Executors#defaultThreadFactory()} on platform threads and
+ * {@code Thread.ofVirtual().factory()} on virtual threads.
+ *
+ * @since 2.44
+ */
+ public static String THREAD_FACTORY = "jersey.config.threads.factory";
+
+ /**
+ *
+ * Defines whether the virtual threads should be used by Jersey on JDK 21+ when not using an exact number
+ * of threads by {@code FixedThreadPool}.
+ *
+ *
+ * The default is {@code false} for this version of Jersey, and {@code true} for Jersey 3.1+.
+ *
+ * @since 2.44
+ */
+ public static String USE_VIRTUAL_THREADS = "jersey.config.threads.use.virtual";
+
/**
* Prevent instantiation.
*/
diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/VirtualThreadUtil.java b/core-common/src/main/java/org/glassfish/jersey/innate/VirtualThreadUtil.java
new file mode 100644
index 0000000000..3511d8e8e8
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/innate/VirtualThreadUtil.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2024 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.innate;
+
+import org.glassfish.jersey.CommonProperties;
+import org.glassfish.jersey.innate.virtual.LoomishExecutors;
+
+import jakarta.ws.rs.core.Configuration;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Factory class to provide JDK specific implementation of bits related to the virtual thread support.
+ */
+public final class VirtualThreadUtil {
+
+ private static final boolean USE_VIRTUAL_THREADS_BY_DEFAULT = false;
+
+ /**
+ * Do not instantiate.
+ */
+ private VirtualThreadUtil() {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * Return an instance of {@link LoomishExecutors} based on a configuration property.
+ * @param config the {@link Configuration}
+ * @return the {@link LoomishExecutors} instance.
+ */
+ public static LoomishExecutors withConfig(Configuration config) {
+ return withConfig(config, USE_VIRTUAL_THREADS_BY_DEFAULT);
+ }
+
+ /**
+ * Return an instance of {@link LoomishExecutors} based on a configuration property.
+ * @param config the {@link Configuration}
+ * @param useVirtualByDefault the default use if not said otherwise by property
+ * @return the {@link LoomishExecutors} instance.
+ */
+ public static LoomishExecutors withConfig(Configuration config, boolean useVirtualByDefault) {
+ ThreadFactory tfThreadFactory = null;
+ boolean useVirtualThreads = useVirtualThreads(config, useVirtualByDefault);
+
+ if (config != null) {
+ Object threadFactory = config.getProperty(CommonProperties.THREAD_FACTORY);
+ if (threadFactory != null && ThreadFactory.class.isInstance(threadFactory)) {
+ tfThreadFactory = (ThreadFactory) threadFactory;
+ }
+ }
+
+ return tfThreadFactory == null
+ ? VirtualThreadSupport.allowVirtual(useVirtualThreads)
+ : VirtualThreadSupport.allowVirtual(useVirtualThreads, tfThreadFactory);
+ }
+
+ /**
+ * Check configuration if the use of the virtual threads is expected or return the default value if not.
+ * @param config the {@link Configuration}
+ * @param useByDefault the default expectation
+ * @return the expected
+ */
+ private static boolean useVirtualThreads(Configuration config, boolean useByDefault) {
+ boolean bUseVirtualThreads = useByDefault;
+ if (config != null) {
+ Object useVirtualThread = config.getProperty(CommonProperties.USE_VIRTUAL_THREADS);
+ if (useVirtualThread != null && Boolean.class.isInstance(useVirtualThread)) {
+ bUseVirtualThreads = (boolean) useVirtualThread;
+ }
+ if (useVirtualThread != null && String.class.isInstance(useVirtualThread)) {
+ bUseVirtualThreads = Boolean.parseBoolean(useVirtualThread.toString());
+ }
+ }
+ return bUseVirtualThreads;
+ }
+}
diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/virtual/LoomishExecutors.java b/core-common/src/main/java/org/glassfish/jersey/innate/virtual/LoomishExecutors.java
new file mode 100644
index 0000000000..906574623c
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/innate/virtual/LoomishExecutors.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2024 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.innate.virtual;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * {@link Executors} facade to support virtual threads.
+ */
+public interface LoomishExecutors {
+ /**
+ * Creates a thread pool that creates new threads as needed and uses virtual threads if available.
+ * @return the newly created thread pool
+ */
+ ExecutorService newCachedThreadPool();
+
+ /**
+ * Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue
+ * and uses virtual threads if available
+ * @param nThreads – the number of threads in the pool
+ * @return the newly created thread pool
+ */
+ ExecutorService newFixedThreadPool(int nThreads);
+
+ /**
+ * Returns thread factory used to create new threads
+ * @return thread factory used to create new threads
+ * @see Executors#defaultThreadFactory()
+ */
+ ThreadFactory getThreadFactory();
+
+ /**
+ * Return true if the virtual thread use is requested.
+ * @return whether the virtual thread use is requested.
+ */
+ boolean isVirtual();
+}
diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/virtual/package-info.java b/core-common/src/main/java/org/glassfish/jersey/innate/virtual/package-info.java
new file mode 100644
index 0000000000..c56f91ba04
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/innate/virtual/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2024 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
+ */
+
+/**
+ * Jersey innate packages. The innate packages will not be opened by JPMS outside of Jersey.
+ * Not for public use.
+ * This virtual package should contain only classes that do not have dependencies on Jersey, or the REST API to be buildable with
+ * ant for multi-release.
+ */
+package org.glassfish.jersey.innate.virtual;
diff --git a/core-common/src/main/java/org/glassfish/jersey/spi/AbstractThreadPoolProvider.java b/core-common/src/main/java/org/glassfish/jersey/spi/AbstractThreadPoolProvider.java
index 27cf44959c..79ac262a2f 100644
--- a/core-common/src/main/java/org/glassfish/jersey/spi/AbstractThreadPoolProvider.java
+++ b/core-common/src/main/java/org/glassfish/jersey/spi/AbstractThreadPoolProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2024 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
@@ -29,6 +29,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
+import org.glassfish.jersey.innate.VirtualThreadUtil;
import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
import org.glassfish.jersey.internal.util.ExtendedLogger;
@@ -37,6 +38,8 @@
import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+import jakarta.ws.rs.core.Configuration;
+
/**
* Abstract thread pool executor provider.
*
@@ -69,9 +72,7 @@ public abstract class AbstractThreadPoolProvider i
private final String name;
private final AtomicBoolean closed = new AtomicBoolean(false);
- private final LazyValue lazyExecutorServiceProvider =
- Values.lazy((Value) () -> createExecutor(getCorePoolSize(), createThreadFactory(), getRejectedExecutionHandler()));
-
+ private final LazyValue lazyExecutorServiceProvider;
/**
* Inheritance constructor.
*
@@ -79,7 +80,20 @@ public abstract class AbstractThreadPoolProvider i
* provided thread pool executor.
*/
protected AbstractThreadPoolProvider(final String name) {
+ this(name, null);
+ }
+
+ /**
+ * Inheritance constructor.
+ *
+ * @param name name of the provided thread pool executor. Will be used in the names of threads created & used by the
+ * provided thread pool executor.
+ * @param configuration {@link Configuration} properties.
+ */
+ protected AbstractThreadPoolProvider(final String name, Configuration configuration) {
this.name = name;
+ lazyExecutorServiceProvider = Values.lazy((Value) () ->
+ createExecutor(getCorePoolSize(), createThreadFactory(configuration), getRejectedExecutionHandler()));
}
/**
@@ -208,9 +222,10 @@ protected ThreadFactory getBackingThreadFactory() {
return null;
}
- private ThreadFactory createThreadFactory() {
+ private ThreadFactory createThreadFactory(Configuration configuration) {
final ThreadFactoryBuilder factoryBuilder = new ThreadFactoryBuilder()
.setNameFormat(name + "-%d")
+ .setThreadFactory(VirtualThreadUtil.withConfig(configuration).getThreadFactory())
.setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler());
final ThreadFactory backingThreadFactory = getBackingThreadFactory();
diff --git a/core-common/src/main/java/org/glassfish/jersey/spi/ScheduledThreadPoolExecutorProvider.java b/core-common/src/main/java/org/glassfish/jersey/spi/ScheduledThreadPoolExecutorProvider.java
index e805b237b2..348db2b1bc 100644
--- a/core-common/src/main/java/org/glassfish/jersey/spi/ScheduledThreadPoolExecutorProvider.java
+++ b/core-common/src/main/java/org/glassfish/jersey/spi/ScheduledThreadPoolExecutorProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2024 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
@@ -23,6 +23,7 @@
import java.util.concurrent.ThreadFactory;
import jakarta.annotation.PreDestroy;
+import jakarta.ws.rs.core.Configuration;
/**
* Default implementation of the Jersey {@link org.glassfish.jersey.spi.ScheduledExecutorServiceProvider
@@ -66,6 +67,17 @@ public ScheduledThreadPoolExecutorProvider(final String name) {
super(name);
}
+ /**
+ * Create a new instance of the scheduled thread pool executor provider.
+ *
+ * @param name provider name. The name will be used to name the threads created & used by the
+ * provisioned scheduled thread pool executor.
+ * @@param configuration {@link Configuration} properties.
+ */
+ public ScheduledThreadPoolExecutorProvider(final String name, Configuration configuration) {
+ super(name, configuration);
+ }
+
@Override
public ScheduledExecutorService getExecutorService() {
return super.getExecutor();
diff --git a/core-common/src/main/java/org/glassfish/jersey/spi/ThreadPoolExecutorProvider.java b/core-common/src/main/java/org/glassfish/jersey/spi/ThreadPoolExecutorProvider.java
index 09e0770e06..df3dd09974 100644
--- a/core-common/src/main/java/org/glassfish/jersey/spi/ThreadPoolExecutorProvider.java
+++ b/core-common/src/main/java/org/glassfish/jersey/spi/ThreadPoolExecutorProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2024 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
@@ -26,6 +26,7 @@
import java.util.concurrent.TimeUnit;
import jakarta.annotation.PreDestroy;
+import jakarta.ws.rs.core.Configuration;
/**
* Default implementation of the Jersey {@link org.glassfish.jersey.spi.ExecutorServiceProvider executor service provider SPI}.
@@ -61,6 +62,17 @@ public ThreadPoolExecutorProvider(final String name) {
super(name);
}
+ /**
+ * Create a new instance of the thread pool executor provider.
+ *
+ * @param name provider name. The name will be used to name the threads created & used by the
+ * provisioned thread pool executor.
+ * @param configuration {@link Configuration} properties.
+ */
+ public ThreadPoolExecutorProvider(final String name, Configuration configuration) {
+ super(name, configuration);
+ }
+
@Override
public ExecutorService getExecutorService() {
return super.getExecutor();
diff --git a/core-common/src/main/java20-/org/glassfish/jersey/innate/VirtualThreadSupport.java b/core-common/src/main/java20-/org/glassfish/jersey/innate/VirtualThreadSupport.java
index 90cafba6a2..867a65be87 100644
--- a/core-common/src/main/java20-/org/glassfish/jersey/innate/VirtualThreadSupport.java
+++ b/core-common/src/main/java20-/org/glassfish/jersey/innate/VirtualThreadSupport.java
@@ -16,11 +16,19 @@
package org.glassfish.jersey.innate;
+import org.glassfish.jersey.innate.virtual.LoomishExecutors;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
/**
* Utility class for the virtual thread support.
*/
public final class VirtualThreadSupport {
+ private static final LoomishExecutors NON_VIRTUAL = new NonLoomishExecutors(Executors.defaultThreadFactory());
+
/**
* Do not instantiate.
*/
@@ -35,4 +43,51 @@ private VirtualThreadSupport() {
public static boolean isVirtualThread() {
return false;
}
+
+ /**
+ * Return an instance of {@link LoomishExecutors} based on a permission to use virtual threads.
+ * @param allow whether to allow virtual threads.
+ * @return the {@link LoomishExecutors} instance.
+ */
+ public static LoomishExecutors allowVirtual(boolean allow) {
+ return NON_VIRTUAL;
+ }
+
+ /**
+ * Return an instance of {@link LoomishExecutors} based on a permission to use virtual threads.
+ * @param allow whether to allow virtual threads.
+ * @param threadFactory the thread factory to be used by a the {@link ExecutorService}.
+ * @return the {@link LoomishExecutors} instance.
+ */
+ public static LoomishExecutors allowVirtual(boolean allow, ThreadFactory threadFactory) {
+ return new NonLoomishExecutors(threadFactory);
+ }
+
+ private static final class NonLoomishExecutors implements LoomishExecutors {
+ private final ThreadFactory threadFactory;
+
+ private NonLoomishExecutors(ThreadFactory threadFactory) {
+ this.threadFactory = threadFactory;
+ }
+
+ @Override
+ public ExecutorService newCachedThreadPool() {
+ return Executors.newCachedThreadPool();
+ }
+
+ @Override
+ public ExecutorService newFixedThreadPool(int nThreads) {
+ return Executors.newFixedThreadPool(nThreads);
+ }
+
+ @Override
+ public ThreadFactory getThreadFactory() {
+ return threadFactory;
+ }
+
+ @Override
+ public boolean isVirtual() {
+ return false;
+ }
+ }
}
diff --git a/core-common/src/main/java21/org/glassfish/jersey/innate/VirtualThreadSupport.java b/core-common/src/main/java21/org/glassfish/jersey/innate/VirtualThreadSupport.java
index 74f58ba9da..0e7d6959ea 100644
--- a/core-common/src/main/java21/org/glassfish/jersey/innate/VirtualThreadSupport.java
+++ b/core-common/src/main/java21/org/glassfish/jersey/innate/VirtualThreadSupport.java
@@ -16,11 +16,20 @@
package org.glassfish.jersey.innate;
+import org.glassfish.jersey.innate.virtual.LoomishExecutors;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
/**
* Utility class for the virtual thread support.
*/
public final class VirtualThreadSupport {
+ private static final LoomishExecutors VIRTUAL_THREADS = new Java21LoomishExecutors(Thread.ofVirtual().factory());
+ private static final LoomishExecutors NON_VIRTUAL_THREADS = new NonLoomishExecutors(Executors.defaultThreadFactory());
+
/**
* Do not instantiate.
*/
@@ -35,4 +44,80 @@ private VirtualThreadSupport() {
public static boolean isVirtualThread() {
return Thread.currentThread().isVirtual();
}
+
+ /**
+ * Return an instance of {@link LoomishExecutors} based on a permission to use virtual threads.
+ * @param allow whether to allow virtual threads.
+ * @return the {@link LoomishExecutors} instance.
+ */
+ public static LoomishExecutors allowVirtual(boolean allow) {
+ return allow ? VIRTUAL_THREADS : NON_VIRTUAL_THREADS;
+ }
+
+ /**
+ * Return an instance of {@link LoomishExecutors} based on a permission to use virtual threads.
+ * @param allow whether to allow virtual threads.
+ * @param threadFactory the thread factory to be used by a the {@link ExecutorService}.
+ * @return the {@link LoomishExecutors} instance.
+ */
+ public static LoomishExecutors allowVirtual(boolean allow, ThreadFactory threadFactory) {
+ return allow ? new Java21LoomishExecutors(threadFactory) : new NonLoomishExecutors(threadFactory);
+ }
+
+ private static class NonLoomishExecutors implements LoomishExecutors {
+ private final ThreadFactory threadFactory;
+
+ private NonLoomishExecutors(ThreadFactory threadFactory) {
+ this.threadFactory = threadFactory;
+ }
+
+ @Override
+ public ExecutorService newCachedThreadPool() {
+ return Executors.newCachedThreadPool(getThreadFactory());
+ }
+
+ @Override
+ public ExecutorService newFixedThreadPool(int nThreads) {
+ return Executors.newFixedThreadPool(nThreads, getThreadFactory());
+ }
+
+ @Override
+ public ThreadFactory getThreadFactory() {
+ return threadFactory;
+ }
+
+ @Override
+ public boolean isVirtual() {
+ return false;
+ }
+ }
+
+ private static class Java21LoomishExecutors implements LoomishExecutors {
+ private final ThreadFactory threadFactory;
+
+ private Java21LoomishExecutors(ThreadFactory threadFactory) {
+ this.threadFactory = threadFactory;
+ }
+
+ @Override
+ public ExecutorService newCachedThreadPool() {
+ return Executors.newThreadPerTaskExecutor(getThreadFactory());
+ }
+
+ @Override
+ public ExecutorService newFixedThreadPool(int nThreads) {
+ ThreadFactory threadFactory = this == VIRTUAL_THREADS ? Executors.defaultThreadFactory() : getThreadFactory();
+ return Executors.newFixedThreadPool(nThreads, threadFactory);
+ }
+
+ @Override
+ public ThreadFactory getThreadFactory() {
+ return threadFactory;
+ }
+
+ @Override
+ public boolean isVirtual() {
+ return true;
+ }
+ }
}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerExecutorProvidersConfigurator.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerExecutorProvidersConfigurator.java
index 77974f1946..d6209586ee 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/ServerExecutorProvidersConfigurator.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerExecutorProvidersConfigurator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2024 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
@@ -21,13 +21,14 @@
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InstanceBinding;
import org.glassfish.jersey.model.internal.ComponentBag;
-import org.glassfish.jersey.model.internal.ManagedObjectsFinalizer;
import org.glassfish.jersey.process.internal.AbstractExecutorProvidersConfigurator;
import org.glassfish.jersey.spi.ExecutorServiceProvider;
import org.glassfish.jersey.spi.ScheduledExecutorServiceProvider;
import org.glassfish.jersey.spi.ScheduledThreadPoolExecutorProvider;
import org.glassfish.jersey.spi.ThreadPoolExecutorProvider;
+import jakarta.ws.rs.core.Configuration;
+
/**
* Configurator which initializes and register {@link org.glassfish.jersey.spi.ExecutorServiceProvider} and
* {@link org.glassfish.jersey.spi.ScheduledExecutorServiceProvider}.
@@ -43,7 +44,7 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) {
ComponentBag componentBag = runtimeConfig.getComponentBag();
// TODO: Do we need to register DEFAULT Executor and ScheduledExecutor to InjectionManager?
- ScheduledExecutorServiceProvider defaultScheduledExecutorProvider = new DefaultBackgroundSchedulerProvider();
+ ScheduledExecutorServiceProvider defaultScheduledExecutorProvider = new DefaultBackgroundSchedulerProvider(runtimeConfig);
InstanceBinding schedulerBinding = Bindings
.service(defaultScheduledExecutorProvider)
.to(ScheduledExecutorServiceProvider.class)
@@ -67,8 +68,8 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) {
@BackgroundScheduler
private static class DefaultBackgroundSchedulerProvider extends ScheduledThreadPoolExecutorProvider {
- public DefaultBackgroundSchedulerProvider() {
- super("jersey-background-task-scheduler");
+ public DefaultBackgroundSchedulerProvider(Configuration configuration) {
+ super("jersey-background-task-scheduler", configuration);
}
@Override
diff --git a/docs/src/main/docbook/appendix-properties.xml b/docs/src/main/docbook/appendix-properties.xml
index 9a1b29dab1..45595294a7 100644
--- a/docs/src/main/docbook/appendix-properties.xml
+++ b/docs/src/main/docbook/appendix-properties.xml
@@ -202,6 +202,39 @@
+
+ &jersey.common.CommonProperties.THREAD_FACTORY;(Jersey 2.44 or later)
+
+
+ jersey.config.threads.factory
+
+
+
+ Defines the java.util.concurrent.ThreadFactory to be used by internal default
+ ExecutorServices.
+
+
+ The default is java.util.concurrent.Executors#defaultThreadFactory() on
+ platform threads andThread.ofVirtual().factory() on virtual threads.
+
+
+
+
+ &jersey.common.CommonProperties.USE_VIRTUAL_THREADS;(Jersey 2.44 or later)
+
+
+ jersey.config.threads.use.virtual
+
+
+
+ Defines whether the virtual threads should be used by Jersey on JDK 21+ when not using an exact number
+ of threads by FixedThreadPool.
+
+
+ The default is &lit.false; for this version of Jersey, and &lit.true; for Jersey 3.1+.
+
+
+
&jersey.logging.LoggingFeature.LOGGING_FEATURE_LOGGER_NAME;
@@ -1172,7 +1205,7 @@
&jersey.client.ClientProperties.SNI_HOST_NAME; (Jersey 2.43 or later)
- jersey.config.client.snihostname
+ jersey.config.client.sniHostName
Sets the host name to be used for calculating the javax.net.ssl.SNIHostName
@@ -2296,6 +2329,42 @@
+
+ &jersey.netty.NettyClientProperties.MAX_HEADER_SIZE;
+ jersey.config.client.netty.maxHeaderSize
+
+
+ Parameter which allows extending of the header size for the Netty connector
+
+ Default header size is 8192b.
+ Since 2.44
+
+
+
+
+ &jersey.netty.NettyClientProperties.MAX_CHUNK_SIZE;
+ jersey.config.client.netty.maxChunkSize
+
+
+ Parameter which allows extending of the chunk size for the Netty connector
+
+ Default chunk size is 8192b.
+ Since 2.44
+
+
+
+
+ &jersey.netty.NettyClientProperties.MAX_INITIAL_LINE_LENGTH;
+ jersey.config.client.netty.maxInitialLineLength
+
+
+ Parameter which allows extending of the initial line length for the Netty connector
+
+ Default initial line length is 4096b.
+ Since 2.44
+
+
+
diff --git a/docs/src/main/docbook/dependencies.xml b/docs/src/main/docbook/dependencies.xml
index 889eceab93..5583564290 100644
--- a/docs/src/main/docbook/dependencies.xml
+++ b/docs/src/main/docbook/dependencies.xml
@@ -77,6 +77,21 @@
+
+ Virtual Threads and Thread Factories
+
+ With JDK 21 and above, Jersey (since 2.44) has the ability to use virtual threads instead of
+ the CachedThreadPool in the internal ExecutorServices.
+ Jersey also has the ability to specify the backing ThreadFactory for the
+ default ExecutorServices (the default ExecutorServices
+ can be overridden by the &jersey.common.spi.ExecutorServiceProvider; SPI).
+
+
+ To enable virtual threads and/or specify the ThreadFactory, use
+ &jersey.common.CommonProperties.USE_VIRTUAL_THREADS; and/or &jersey.common.CommonProperties.THREAD_FACTORY;
+ properties, respectively. See also the in appendix for property details.
+
+
Introduction to Jersey dependencies
diff --git a/docs/src/main/docbook/jersey.ent b/docs/src/main/docbook/jersey.ent
index 10d0b15c2e..1893612e5c 100644
--- a/docs/src/main/docbook/jersey.ent
+++ b/docs/src/main/docbook/jersey.ent
@@ -416,6 +416,8 @@
CommonProperties.JSON_JACKSON_DISABLED_MODULES_CLIENT" >
CommonProperties.JSON_JACKSON_DISABLED_MODULES_SERVER" >
CommonProperties.PARAM_CONVERTERS_THROW_IAE" >
+CommonProperties.THREAD_FACTORY" >
+CommonProperties.USE_VIRTUAL_THREADS" >
DisposableSupplier">
InjectionManager">
InjectionResolver">
@@ -592,6 +594,9 @@
NettyClientProperties.MAX_REDIRECTS" >
NettyClientProperties.PRESERVE_METHOD_ON_REDIRECT" >
NettyClientProperties.EXPECT_100_CONTINUE_TIMEOUT" >
+NettyClientProperties.MAX_HEADER_SIZE" >
+NettyClientProperties.MAX_INITIAL_LINE_LENGTH" >
+NettyClientProperties.MAX_CHUNK_SIZE" >
NettyConnectorProvider">
ApplicationHandler">
@BackgroundScheduler">
diff --git a/etc/jenkins/jenkins_build.sh b/etc/jenkins/jenkins_build.sh
index 9983a05bf8..3543e90d35 100644
--- a/etc/jenkins/jenkins_build.sh
+++ b/etc/jenkins/jenkins_build.sh
@@ -2,4 +2,4 @@
export DEBUG=true
-mvn -V -U -B -e -Pstaging clean install glassfish-copyright:check -Dcopyright.quiet=false
\ No newline at end of file
+mvn -V -U -B -e -Pstaging clean install glassfish-copyright:check -Dcopyright.quiet=false -DskipSBOM
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
index 1e81f529e6..2b7425f6dc 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2021 Payara Foundation and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
@@ -33,7 +33,6 @@
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.logging.Level;
@@ -66,6 +65,7 @@
import org.glassfish.jersey.client.Initializable;
import org.glassfish.jersey.client.spi.ConnectorProvider;
import org.glassfish.jersey.ext.cdi1x.internal.CdiUtil;
+import org.glassfish.jersey.innate.VirtualThreadUtil;
import org.glassfish.jersey.internal.ServiceFinder;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InjectionManagerSupplier;
@@ -111,7 +111,7 @@ class RestClientBuilderImpl implements RestClientBuilder {
asyncInterceptorFactories = new ArrayList<>();
config = ConfigProvider.getConfig();
configWrapper = new ConfigWrapper(clientBuilder.getConfiguration());
- executorService = Executors::newCachedThreadPool;
+ executorService = () -> VirtualThreadUtil.withConfig(configWrapper).newCachedThreadPool();
}
@Override
diff --git a/pom.xml b/pom.xml
index 6045c837e6..66f4d9f2d1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -428,12 +428,10 @@
junit.jupiter.execution.parallel.mode.default=same_thread
-
-
- jersey.config.test.container.port
- ${jersey.config.test.container.port}
-
-
+
+ ${jersey.config.test.container.port}
+
+
124
@@ -635,12 +633,10 @@
${skip.tests}
${skip.tests}
${failsafe.coverage.argline}
-
-
- jersey.config.test.container.port
- ${jersey.config.test.container.port}
-
-
+
+ ${jersey.config.test.container.port}
+
+
@@ -1103,6 +1099,37 @@
+
+ sbom
+
+
+ !skipSBOM
+
+
+
+
+
+ org.cyclonedx
+ cyclonedx-maven-plugin
+ ${cyclonedx.mvn.plugin.version}
+ true
+
+
+ package
+
+ makeAggregateBom
+
+
+
+ framework
+ true
+
+
+
+
+
+
+
sonar
@@ -2061,6 +2088,7 @@
but the jersey-common module which has to have the separate version for OSGi reasons.
-->
3.9.0
+ 2.8.0
3.6.1
3.1.2
3.3.0
diff --git a/test-framework/core/src/main/java/org/glassfish/jersey/test/JerseyTest.java b/test-framework/core/src/main/java/org/glassfish/jersey/test/JerseyTest.java
index 243466caa2..fe85e9c76f 100644
--- a/test-framework/core/src/main/java/org/glassfish/jersey/test/JerseyTest.java
+++ b/test-framework/core/src/main/java/org/glassfish/jersey/test/JerseyTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2024 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
@@ -621,6 +621,7 @@ public void setUp() throws Exception {
if (!isConcurrent() || activeThreadCount.getAndIncrement() == 0) {
registerLogHandlerIfEnabled();
final TestContainer testContainer = createTestContainer(context);
+ testContainer.configureContainer();
// Set current instance of test container and start it.
setTestContainer(testContainer);
diff --git a/test-framework/core/src/main/java/org/glassfish/jersey/test/spi/TestContainer.java b/test-framework/core/src/main/java/org/glassfish/jersey/test/spi/TestContainer.java
index 68a5bf8df0..d2a0030f4d 100644
--- a/test-framework/core/src/main/java/org/glassfish/jersey/test/spi/TestContainer.java
+++ b/test-framework/core/src/main/java/org/glassfish/jersey/test/spi/TestContainer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2024 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
@@ -54,4 +54,13 @@ public interface TestContainer {
* Stop the container.
*/
public void stop();
+
+ /**
+ * optional method to configure container before it's being started
+ *
+ * @since 2.44
+ */
+ default void configureContainer() {
+
+ }
}
diff --git a/test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerProperties.java b/test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerProperties.java
new file mode 100644
index 0000000000..beabef2a8b
--- /dev/null
+++ b/test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerProperties.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2024 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.test.jetty;
+
+import org.glassfish.jersey.internal.util.PropertiesClass;
+
+/**
+ * Properties which relates only to Jetty test container configuration
+ *
+ * @since 2.44
+ */
+@PropertiesClass
+public class JettyTestContainerProperties {
+
+ /**
+ * Parameter which allows settings custom header size for request and response.
+ *
+ * @since 2.44
+ */
+ public static final String HEADER_SIZE = "jersey.test.jetty.container.header.size";
+
+}
diff --git a/tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk21/ThreadFactoryUsageTest.java b/tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk21/ThreadFactoryUsageTest.java
new file mode 100644
index 0000000000..6739ba98cc
--- /dev/null
+++ b/tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk21/ThreadFactoryUsageTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2024 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.tests.e2e.jdk21;
+
+import org.glassfish.jersey.CommonProperties;
+import org.glassfish.jersey.innate.VirtualThreadSupport;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.core.Response;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+public class ThreadFactoryUsageTest {
+ @Test
+ public void testThreadFactory() throws ExecutionException, InterruptedException {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ ThreadFactory threadFactory = VirtualThreadSupport.allowVirtual(true).getThreadFactory();
+ ThreadFactory countDownThreadFactory = r -> {
+ countDownLatch.countDown();
+ return threadFactory.newThread(r);
+ };
+
+ CompletionStage r = ClientBuilder.newClient()
+ .property(CommonProperties.THREAD_FACTORY, countDownThreadFactory)
+ .property(CommonProperties.USE_VIRTUAL_THREADS, true)
+ .register((ClientRequestFilter) requestContext -> requestContext.abortWith(Response.ok().build()))
+ .target("http://localhost:58080/test").request().rx().get();
+
+ MatcherAssert.assertThat(r.toCompletableFuture().get().getStatus(), Matchers.is(200));
+ countDownLatch.await(10, TimeUnit.SECONDS);
+ MatcherAssert.assertThat(countDownLatch.getCount(), Matchers.is(0L));
+ }
+}