merge of the actual 3.0 into the 3.1
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java b/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java
index 306e336..dd96928 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java
@@ -21,9 +21,6 @@
import java.net.Proxy;
import java.net.URL;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import jakarta.ws.rs.client.Client;
@@ -290,16 +287,12 @@
* @throws java.io.IOException in case the connection cannot be provided.
*/
default HttpURLConnection getConnection(URL url, Proxy proxy) throws IOException {
- synchronized (this){
- return (proxy == null) ? getConnection(url) : (HttpURLConnection) url.openConnection(proxy);
- }
+ return (proxy == null) ? getConnection(url) : (HttpURLConnection) url.openConnection(proxy);
}
}
private static class DefaultConnectionFactory implements ConnectionFactory {
- private final ConcurrentHashMap<URL, Lock> locks = new ConcurrentHashMap<>();
-
@Override
public HttpURLConnection getConnection(final URL url) throws IOException {
return connect(url, null);
@@ -311,13 +304,7 @@
}
private HttpURLConnection connect(URL url, Proxy proxy) throws IOException {
- Lock lock = locks.computeIfAbsent(url, u -> new ReentrantLock());
- lock.lock();
- try {
- return (proxy == null) ? (HttpURLConnection) url.openConnection() : (HttpURLConnection) url.openConnection(proxy);
- } finally {
- lock.unlock();
- }
+ return (proxy == null) ? (HttpURLConnection) url.openConnection() : (HttpURLConnection) url.openConnection(proxy);
}
}
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java
index 8fb151a..09d8d88 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java
@@ -17,6 +17,7 @@
package org.glassfish.jersey.client.innate.inject;
import org.glassfish.jersey.client.internal.LocalizationMessages;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.Binder;
import org.glassfish.jersey.internal.inject.Binding;
import org.glassfish.jersey.internal.inject.ClassBinding;
@@ -52,10 +53,12 @@
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
-import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import java.util.logging.Level;
@@ -75,8 +78,6 @@
private final MultivaluedMap<Type, SupplierInstanceBinding<?>> supplierTypeInstanceBindings = new MultivaluedHashMap<>();
private final MultivaluedMap<Type, SupplierClassBinding<?>> supplierTypeClassBindings = new MultivaluedHashMap<>();
- private final MultivaluedMap<DisposableSupplier, Object> disposableSupplierObjects = new MultivaluedHashMap<>();
-
private final Instances instances = new Instances();
private final Types types = new Types();
@@ -89,11 +90,12 @@
* @param <TYPE> the type for which the instance is created, either Class, or ParametrizedType (for instance
* Provider<SomeClass>).
*/
- private class TypedInstances<TYPE> {
+ private class TypedInstances<TYPE extends Type> {
private final MultivaluedMap<TYPE, InstanceContext<?>> singletonInstances = new MultivaluedHashMap<>();
private ThreadLocal<MultivaluedMap<TYPE, InstanceContext<?>>> threadInstances = new ThreadLocal<>();
- private final List<Object> threadPredestroyables = Collections.synchronizedList(new LinkedList<>());
private final ReentrantLock singletonInstancesLock = new ReentrantLock();
+ private ThreadLocal<MultivaluedMap<DisposableSupplier, Object>> disposableSupplierObjects =
+ ThreadLocal.withInitial(() -> new MultivaluedHashMap<>());
private <T> List<InstanceContext<?>> _getSingletons(TYPE clazz) {
List<InstanceContext<?>> si;
@@ -107,7 +109,7 @@
}
@SuppressWarnings("unchecked")
- <T> T _addSingleton(TYPE clazz, T instance, Binding<?, ?> binding, Annotation[] qualifiers) {
+ <T> T _addSingleton(TYPE clazz, T instance, Binding<?, ?> binding, Annotation[] qualifiers, boolean destroy) {
singletonInstancesLock.lock();
try {
// check existing singleton with a qualifier already created by another thread io a meantime
@@ -121,8 +123,8 @@
return (T) qualified.get(0).instance;
}
}
- singletonInstances.add(clazz, new InstanceContext<>(instance, binding, qualifiers));
- threadPredestroyables.add(instance);
+ InstanceContext<?> instanceContext = new InstanceContext<>(instance, binding, qualifiers, !destroy);
+ singletonInstances.add(clazz, instanceContext);
return instance;
} finally {
singletonInstancesLock.unlock();
@@ -131,11 +133,11 @@
@SuppressWarnings("unchecked")
<T> T addSingleton(TYPE clazz, T t, Binding<?, ?> binding, Annotation[] instanceQualifiers) {
- T t2 = _addSingleton(clazz, t, binding, instanceQualifiers);
+ T t2 = _addSingleton(clazz, t, binding, instanceQualifiers, true);
if (t2 == t) {
for (Type contract : binding.getContracts()) {
if (!clazz.equals(contract) && isClass(contract)) {
- _addSingleton((TYPE) contract, t, binding, instanceQualifiers);
+ _addSingleton((TYPE) contract, t, binding, instanceQualifiers, false);
}
}
}
@@ -151,21 +153,22 @@
return list;
}
- private <T> void _addThreadInstance(TYPE clazz, T instance, Binding<T, ?> binding, Annotation[] qualifiers) {
+ private <T> void _addThreadInstance(
+ TYPE clazz, T instance, Binding<T, ?> binding, Annotation[] qualifiers, boolean destroy) {
MultivaluedMap<TYPE, InstanceContext<?>> map = threadInstances.get();
if (map == null) {
map = new MultivaluedHashMap<>();
threadInstances.set(map);
}
- map.add(clazz, new InstanceContext<>(instance, binding, qualifiers));
- threadPredestroyables.add(instance);
+ InstanceContext<?> instanceContext = new InstanceContext<>(instance, binding, qualifiers, !destroy);
+ map.add(clazz, instanceContext);
}
<T> void addThreadInstance(TYPE clazz, T t, Binding<T, ?> binding, Annotation[] instanceQualifiers) {
- _addThreadInstance(clazz, t, binding, instanceQualifiers);
+ _addThreadInstance(clazz, t, binding, instanceQualifiers, true);
for (Type contract : binding.getContracts()) {
if (!clazz.equals(contract) && isClass(contract)) {
- _addThreadInstance((TYPE) contract, t, binding, instanceQualifiers);
+ _addThreadInstance((TYPE) contract, t, binding, instanceQualifiers, false);
}
}
}
@@ -183,28 +186,78 @@
private <T> List<InstanceContext<?>> _getContexts(TYPE clazz) {
List<InstanceContext<?>> si = _getSingletons(clazz);
List<InstanceContext<?>> ti = _getThreadInstances(clazz);
- if (si == null && ti != null) {
- si = ti;
- } else if (ti != null) {
- si.addAll(ti);
- }
- return si;
+ return InstanceContext.merge(si, ti);
}
<T> T getInstance(TYPE clazz, Annotation[] annotations) {
List<T> i = getInstances(clazz, annotations);
if (i != null) {
checkUnique(i);
- return i.get(0);
+ return instanceOrSupply(clazz, i.get(0));
}
return null;
}
+ private <T> T instanceOrSupply(TYPE clazz, T t) {
+ if (!Class.class.isInstance(clazz) || ((Class) clazz).isInstance(t)) {
+ return t;
+ } else if (Supplier.class.isInstance(t)) {
+ return (T) registerDisposableSupplierAndGet((Supplier) t, this);
+ } else if (Provider.class.isInstance(t)) {
+ return (T) ((Provider) t).get();
+ } else {
+ return t;
+ }
+ }
+
void dispose() {
singletonInstances.forEach((clazz, instances) -> instances.forEach(instance -> preDestroy(instance.getInstance())));
- threadPredestroyables.forEach(NonInjectionManager.this::preDestroy);
+ disposeThreadInstances(true);
/* The java.lang.ThreadLocal$ThreadLocalMap$Entry[] keeps references to this NonInjectionManager */
threadInstances = null;
+ disposableSupplierObjects = null;
+ }
+
+ void disposeThreadInstances(boolean allThreadInstances) {
+ MultivaluedMap<TYPE, InstanceContext<?>> ti = threadInstances.get();
+ if (ti == null) {
+ return;
+ }
+ Set<Map.Entry<TYPE, List<InstanceContext<?>>>> tiSet = ti.entrySet();
+ Iterator<Map.Entry<TYPE, List<InstanceContext<?>>>> tiSetIt = tiSet.iterator();
+ while (tiSetIt.hasNext()) {
+ Map.Entry<TYPE, List<InstanceContext<?>>> entry = tiSetIt.next();
+ Iterator<InstanceContext<?>> listIt = entry.getValue().iterator();
+ while (listIt.hasNext()) {
+ InstanceContext<?> instanceContext = listIt.next();
+ if (allThreadInstances || instanceContext.getBinding().getScope() != PerThread.class) {
+ listIt.remove();
+ if (DisposableSupplier.class.isInstance(instanceContext.getInstance())) {
+ MultivaluedMap<DisposableSupplier, Object> disposeMap = disposableSupplierObjects.get();
+ Iterator<Map.Entry<DisposableSupplier, List<Object>>> disposeMapIt = disposeMap.entrySet().iterator();
+ while (disposeMapIt.hasNext()) {
+ Map.Entry<DisposableSupplier, List<Object>> disposeMapEntry = disposeMapIt.next();
+ if (disposeMapEntry.getKey() == /* identity */ instanceContext.getInstance()) {
+ Iterator<Object> disposeMapEntryIt = disposeMapEntry.getValue().iterator();
+ while (disposeMapEntryIt.hasNext()) {
+ Object disposeInstance = disposeMapEntryIt.next();
+ ((DisposableSupplier) instanceContext.getInstance()).dispose(disposeInstance);
+ disposeMapEntryIt.remove();
+ }
+ }
+ if (disposeMapEntry.getValue().isEmpty()) {
+ disposeMapIt.remove();
+ }
+ }
+ }
+ instanceContext.destroy(NonInjectionManager.this);
+ }
+ if (entry.getValue().isEmpty()) {
+ tiSetIt.remove();
+ }
+ }
+ }
+ disposableSupplierObjects.remove();
}
}
@@ -215,9 +268,19 @@
}
public NonInjectionManager() {
+ Binding binding = new AbstractBinder() {
+ @Override
+ protected void configure() {
+ bind(NonInjectionRequestScope.class).to(RequestScope.class).in(Singleton.class);
+ }
+ }.getBindings().iterator().next();
+ RequestScope scope = new NonInjectionRequestScope(this);
+ instances.addSingleton(RequestScope.class, scope, binding, null);
+ types.addSingleton(RequestScope.class, scope, binding, null);
}
public NonInjectionManager(boolean warning) {
+ this();
if (warning) {
logger.warning(LocalizationMessages.NONINJECT_FALLBACK());
} else {
@@ -227,20 +290,21 @@
@Override
public void completeRegistration() {
- instances._addSingleton(InjectionManager.class, this, new InjectionManagerBinding(), null);
+ instances._addSingleton(InjectionManager.class, this, new InjectionManagerBinding(), null, false);
}
@Override
public void shutdown() {
shutdown = true;
-
- disposableSupplierObjects.forEach((supplier, objects) -> objects.forEach(supplier::dispose));
- disposableSupplierObjects.clear();
-
instances.dispose();
types.dispose();
}
+ void disposeRequestScopedInstances() {
+ instances.disposeThreadInstances(false);
+ types.disposeThreadInstances(false);
+ }
+
@Override
public boolean isShutdown() {
return shutdown;
@@ -419,12 +483,7 @@
return (T) this;
}
if (RequestScope.class.equals(createMe)) {
- if (!isRequestScope) {
- isRequestScope = true;
- return (T) new NonInjectionRequestScope();
- } else {
- throw new IllegalStateException(LocalizationMessages.NONINJECT_REQUESTSCOPE_CREATED());
- }
+ throw new IllegalStateException(LocalizationMessages.NONINJECT_REQUESTSCOPE_CREATED());
}
ClassBindings<T> classBindings = classBindings(createMe);
@@ -439,12 +498,7 @@
return (T) this;
}
if (RequestScope.class.equals(createMe)) {
- if (!isRequestScope) {
- isRequestScope = true;
- return (T) new NonInjectionRequestScope();
- } else {
- throw new IllegalStateException(LocalizationMessages.NONINJECT_REQUESTSCOPE_CREATED());
- }
+ throw new IllegalStateException(LocalizationMessages.NONINJECT_REQUESTSCOPE_CREATED());
}
ClassBindings<T> classBindings = classBindings(createMe);
@@ -543,7 +597,9 @@
@Override
public void preDestroy(Object preDestroyMe) {
- Method preDestroy = getAnnotatedMethod(preDestroyMe, PreDestroy.class);
+ Method preDestroy = Method.class.isInstance(preDestroyMe)
+ ? (Method) preDestroyMe
+ : getAnnotatedMethod(preDestroyMe, PreDestroy.class);
if (preDestroy != null) {
ensureAccessible(preDestroy);
try {
@@ -575,20 +631,27 @@
* @return The proxy for the instance supplied by a supplier or the instance if not required to be proxied.
*/
@SuppressWarnings("unchecked")
- private <T> T createSupplierProxyIfNeeded(Boolean createProxy, Class<T> iface, Supplier<T> supplier) {
+ private <T> T createSupplierProxyIfNeeded(
+ Boolean createProxy, Class<T> iface, Supplier<Supplier<T>> supplier, TypedInstances<?> typedInstances) {
if (createProxy != null && createProxy && iface.isInterface()) {
T proxy = (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[]{iface}, new InvocationHandler() {
- final SingleRegisterSupplier<T> singleSupplierRegister = new SingleRegisterSupplier<>(supplier);
+ final Set<Object> instances = new HashSet<>();
+
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- T t = singleSupplierRegister.get();
+ Supplier<T> supplierT = supplier.get();
+ T t = supplierT.get();
+ if (DisposableSupplier.class.isInstance(supplierT) && !instances.contains(t)) {
+ MultivaluedMap<DisposableSupplier, Object> map = typedInstances.disposableSupplierObjects.get();
+ map.add((DisposableSupplier) supplierT, t);
+ }
Object ret = method.invoke(t, args);
return ret;
}
});
return proxy;
} else {
- return registerDisposableSupplierAndGet(supplier);
+ return registerDisposableSupplierAndGet(supplier.get(), typedInstances);
}
}
@@ -600,8 +663,8 @@
private class SingleRegisterSupplier<T> {
private final LazyValue<T> once;
- private SingleRegisterSupplier(Supplier<T> supplier) {
- once = Values.lazy((Value<T>) () -> registerDisposableSupplierAndGet(supplier));
+ private SingleRegisterSupplier(Supplier<T> supplier, TypedInstances<?> instances) {
+ once = Values.lazy((Value<T>) () -> registerDisposableSupplierAndGet(supplier, instances));
}
T get() {
@@ -609,10 +672,10 @@
}
}
- private <T> T registerDisposableSupplierAndGet(Supplier<T> supplier) {
+ private <T> T registerDisposableSupplierAndGet(Supplier<T> supplier, TypedInstances<?> typedInstances) {
T instance = supplier.get();
if (DisposableSupplier.class.isInstance(supplier)) {
- disposableSupplierObjects.add((DisposableSupplier<T>) supplier, instance);
+ typedInstances.disposableSupplierObjects.get().add((DisposableSupplier<T>) supplier, instance);
}
return instance;
}
@@ -686,7 +749,7 @@
* @param <X> The expected return type for the TYPE.
* @param <TYPE> The Type for which a {@link Binding} has been created.
*/
- private abstract class XBindings<X, TYPE> {
+ private abstract class XBindings<X, TYPE extends Type> {
protected final List<InstanceBinding<X>> instanceBindings = new LinkedList<>();
protected final List<SupplierInstanceBinding<X>> supplierInstanceBindings = new LinkedList<>();
@@ -767,12 +830,8 @@
private X _create(SupplierInstanceBinding<X> binding) {
Supplier<X> supplier = binding.getSupplier();
- X t = registerDisposableSupplierAndGet(supplier);
- if (Singleton.class.equals(binding.getScope())) {
- _addInstance(t, binding);
- } else if (_isPerThread(binding.getScope())) {
- _addThreadInstance(t, binding);
- }
+ X t = registerDisposableSupplierAndGet(supplier, instances);
+ t = addInstance(type, t, binding);
return t;
}
@@ -836,20 +895,17 @@
protected abstract X _createAndStore(ClassBinding<X> binding);
- protected <T> T _addInstance(TYPE type, T instance, Binding<?, ?> binding) {
+ protected <T> T _addSingletonInstance(TYPE type, T instance, Binding<?, ?> binding) {
return instances.addSingleton(type, instance, binding, instancesQualifiers);
}
- protected void _addThreadInstance(TYPE type, Object instance, Binding binding) {
- instances.addThreadInstance(type, instance, binding, instancesQualifiers);
- }
-
- protected <T> T _addInstance(T instance, Binding<?, ?> binding) {
- return instances.addSingleton(type, instance, binding, instancesQualifiers);
- }
-
- protected void _addThreadInstance(Object instance, Binding binding) {
- instances.addThreadInstance(type, instance, binding, instancesQualifiers);
+ protected <T> T addInstance(TYPE type, T instance, Binding binding) {
+ if (Singleton.class.equals(binding.getScope())) {
+ instance = instances.addSingleton(type, instance, binding, instancesQualifiers);
+ } else if (_isPerThread(binding.getScope())) {
+ instances.addThreadInstance(type, instance, binding, instancesQualifiers);
+ }
+ return instance;
}
}
@@ -909,28 +965,27 @@
}
protected T _create(SupplierClassBinding<T> binding) {
- Supplier<T> supplier = instances.getInstance(binding.getSupplierClass(), null);
- if (supplier == null) {
- supplier = justCreate(binding.getSupplierClass());
- if (Singleton.class.equals(binding.getSupplierScope())) {
- supplier = instances.addSingleton(binding.getSupplierClass(), supplier, binding, null);
- } else if (_isPerThread(binding.getSupplierScope())) {
- instances.addThreadInstance(binding.getSupplierClass(), supplier, binding, null);
+ Supplier<Supplier<T>> supplierSupplier = () -> {
+ Supplier<T> supplier = instances.getInstance(binding.getSupplierClass(), null);
+ if (supplier == null) {
+ supplier = justCreate(binding.getSupplierClass());
+ if (Singleton.class.equals(binding.getSupplierScope())) {
+ supplier = instances.addSingleton(binding.getSupplierClass(), supplier, binding, null);
+ } else if (_isPerThread(binding.getSupplierScope()) || binding.getSupplierScope() == null) {
+ instances.addThreadInstance(binding.getSupplierClass(), supplier, binding, null);
+ }
}
- }
+ return supplier;
+ };
- T t = createSupplierProxyIfNeeded(binding.isProxiable(), (Class<T>) type, supplier);
- if (Singleton.class.equals(binding.getScope())) {
- t = _addInstance(type, t, binding);
- } else if (_isPerThread(binding.getScope())) {
- _addThreadInstance(type, t, binding);
- }
+ T t = createSupplierProxyIfNeeded(binding.isProxiable(), (Class<T>) type, supplierSupplier, instances);
+// t = addInstance(type, t, binding); The supplier here creates instances that ought not to be registered as beans
return t;
}
protected T _createAndStore(ClassBinding<T> binding) {
T result = justCreate(binding.getService());
- result = _addInstance(binding.getService(), result, binding);
+ result = addInstance(binding.getService(), result, binding);
return result;
}
}
@@ -943,19 +998,15 @@
protected T _create(SupplierClassBinding<T> binding) {
Supplier<T> supplier = justCreate(binding.getSupplierClass());
- T t = registerDisposableSupplierAndGet(supplier);
- if (Singleton.class.equals(binding.getScope())) {
- t = _addInstance(type, t, binding);
- } else if (_isPerThread(binding.getScope())) {
- _addThreadInstance(type, t, binding);
- }
+ T t = registerDisposableSupplierAndGet(supplier, instances);
+ t = addInstance(type, t, binding);
return t;
}
@Override
protected T _createAndStore(ClassBinding<T> binding) {
T result = justCreate(binding.getService());
- result = _addInstance(type, result, binding);
+ result = addInstance(type, result, binding);
return result;
}
@@ -976,7 +1027,7 @@
return NonInjectionManager.this.getInstance(actualTypeArgument);
}
}
- });
+ }, instances);
@Override
public Object get() {
@@ -999,13 +1050,16 @@
private final T instance;
private final Binding<?, ?> binding;
private final Annotation[] createdWithQualifiers;
+ private boolean destroyed = false;
- private InstanceContext(T instance, Binding<?, ?> binding, Annotation[] qualifiers) {
+ private InstanceContext(T instance, Binding<?, ?> binding, Annotation[] qualifiers, boolean destroyed) {
this.instance = instance;
this.binding = binding;
this.createdWithQualifiers = qualifiers;
+ this.destroyed = destroyed;
}
+
public Binding<?, ?> getBinding() {
return binding;
}
@@ -1014,6 +1068,13 @@
return instance;
}
+ public void destroy(NonInjectionManager nonInjectionManager) {
+ if (!destroyed) {
+ destroyed = true;
+ nonInjectionManager.preDestroy(instance);
+ }
+ }
+
@SuppressWarnings("unchecked")
static <T> List<T> toInstances(List<InstanceContext<?>> instances, Annotation[] qualifiers) {
return instances != null
@@ -1032,6 +1093,15 @@
: null;
}
+ private static List<InstanceContext<?>> merge(List<InstanceContext<?>> i1, List<InstanceContext<?>> i2) {
+ if (i1 == null) {
+ i1 = i2;
+ } else if (i2 != null) {
+ i1.addAll(i2);
+ }
+ return i1;
+ }
+
private boolean hasQualifiers(Annotation[] requested) {
if (requested != null) {
classLoop:
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionRequestScope.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionRequestScope.java
index 258780d..b8b23b2 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionRequestScope.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionRequestScope.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 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,14 +20,20 @@
import org.glassfish.jersey.internal.util.LazyUid;
import org.glassfish.jersey.process.internal.RequestScope;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
public class NonInjectionRequestScope extends RequestScope {
+
+ private final NonInjectionManager nonInjectionManager;
+
+ public NonInjectionRequestScope(NonInjectionManager nonInjectionManager) {
+ this.nonInjectionManager = nonInjectionManager;
+ }
+
@Override
public org.glassfish.jersey.process.internal.RequestContext createContext() {
- return new Instance();
+ return new Instance(nonInjectionManager);
}
/**
@@ -35,6 +41,8 @@
*/
public static final class Instance implements org.glassfish.jersey.process.internal.RequestContext {
+ private final NonInjectionManager injectionManager;
+
private static final ExtendedLogger logger = new ExtendedLogger(Logger.getLogger(Instance.class.getName()), Level.FINEST);
/*
@@ -48,10 +56,11 @@
/**
* Holds the number of snapshots of this scope.
*/
- private final AtomicInteger referenceCounter;
+ private int referenceCounter;
- private Instance() {
- this.referenceCounter = new AtomicInteger(1);
+ private Instance(NonInjectionManager injectionManager) {
+ this.injectionManager = injectionManager;
+ this.referenceCounter = 1;
}
/**
@@ -65,7 +74,7 @@
@Override
public NonInjectionRequestScope.Instance getReference() {
// TODO: replace counter with a phantom reference + reference queue-based solution
- referenceCounter.incrementAndGet();
+ referenceCounter++;
return this;
}
@@ -77,7 +86,9 @@
*/
@Override
public void release() {
- referenceCounter.decrementAndGet();
+ if (0 == --referenceCounter) {
+ injectionManager.disposeRequestScopedInstances();
+ }
}
@Override
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java
index ca73f4c..630cb96 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java
@@ -84,7 +84,7 @@
private static final String ALLOW_RESTRICTED_HEADERS_SYSTEM_PROPERTY = "sun.net.http.allowRestrictedHeaders";
// Avoid multi-thread uses of HttpsURLConnection.getDefaultSSLSocketFactory() because it does not implement a
// proper lazy-initialization. See https://github.com/jersey/jersey/issues/3293
- private static final Value<SSLSocketFactory> DEFAULT_SSL_SOCKET_FACTORY =
+ private static final LazyValue<SSLSocketFactory> DEFAULT_SSL_SOCKET_FACTORY =
Values.lazy((Value<SSLSocketFactory>) () -> HttpsURLConnection.getDefaultSSLSocketFactory());
// The list of restricted headers is extracted from sun.net.www.protocol.http.HttpURLConnection
private static final String[] restrictedHeaders = {
@@ -387,6 +387,10 @@
sniUri = request.getUri();
}
+ if (!DEFAULT_SSL_SOCKET_FACTORY.isInitialized() && "HTTPS".equalsIgnoreCase(sniUri.getScheme())) {
+ DEFAULT_SSL_SOCKET_FACTORY.get();
+ }
+
proxy.ifPresent(clientProxy -> ClientProxy.setBasicAuthorizationHeader(request.getHeaders(), proxy.get()));
uc = this.connectionFactory.getConnection(sniUri.toURL(), proxy.isPresent() ? proxy.get().proxy() : null);
uc.setDoInput(true);
diff --git a/core-common/pom.xml b/core-common/pom.xml
index 2c32a3f..e927538 100644
--- a/core-common/pom.xml
+++ b/core-common/pom.xml
@@ -418,6 +418,9 @@
<profile>
<id>securityOff</id>
+ <activation>
+ <jdk>[24,)</jdk>
+ </activation>
<properties>
<surefire.security.argline />
</properties>
@@ -426,12 +429,17 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <excludes>
- <exclude>**/SecurityManagerConfiguredTest.java</exclude>
- <exclude>**/ReflectionHelperTest.java</exclude>
- </excludes>
- </configuration>
+ <executions>
+ <execution>
+ <id>default-test</id>
+ <configuration>
+ <excludes>
+ <exclude>**/SecurityManagerConfiguredTest.java</exclude>
+ <exclude>**/ReflectionHelperTest.java</exclude>
+ </excludes>
+ </configuration>
+ </execution>
+ </executions>
</plugin>
</plugins>
</build>
diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactoryTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactoryTest.java
index 6f1f7b1..ff3e9ef 100644
--- a/core-common/src/test/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactoryTest.java
+++ b/core-common/src/test/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactoryTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 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
@@ -30,11 +30,14 @@
public class ExternalPropertiesConfigurationFactoryTest {
+ private static boolean isSecurityManager;
+
/**
* Predefine some properties to be read from config
*/
@BeforeAll
public static void setUp() {
+ isSecurityManager = System.getSecurityManager() != null;
System.setProperty(CommonProperties.ALLOW_SYSTEM_PROPERTIES_PROVIDER, Boolean.TRUE.toString());
System.setProperty("jersey.config.server.provider.scanning.recursive", "PASSED");
@@ -53,7 +56,11 @@
public void readSystemPropertiesTest() {
final Object result =
readExternalPropertiesMap().get("jersey.config.server.provider.scanning.recursive");
- Assertions.assertNull(result);
+ if (isSecurityManager) {
+ Assertions.assertNull(result);
+ } else {
+ Assertions.assertEquals("PASSED", result);
+ }
Assertions.assertEquals(Boolean.TRUE,
getConfig().isProperty(CommonProperties.JSON_PROCESSING_FEATURE_DISABLE));
Assertions.assertEquals(Boolean.TRUE,
@@ -81,8 +88,11 @@
inputProperties.put("org.jersey.microprofile.config.added", "ADDED");
getConfig().mergeProperties(inputProperties);
final Object result = readExternalPropertiesMap().get("jersey.config.server.provider.scanning.recursive");
- Assertions.assertNull(result);
- Assertions.assertNull(readExternalPropertiesMap().get("org.jersey.microprofile.config.added"));
+ final Object resultAdded = readExternalPropertiesMap().get("org.jersey.microprofile.config.added");
+ if (isSecurityManager) {
+ Assertions.assertNull(result);
+ Assertions.assertNull(resultAdded);
+ }
}
}
diff --git a/core-server/pom.xml b/core-server/pom.xml
index 7a09453..a99a0d1 100644
--- a/core-server/pom.xml
+++ b/core-server/pom.xml
@@ -260,6 +260,9 @@
<profiles>
<profile>
<id>securityOff</id>
+ <activation>
+ <jdk>[24,)</jdk>
+ </activation>
<properties>
<surefire.security.argline />
</properties>
diff --git a/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/ParamConverterDateTest.java b/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/ParamConverterDateTest.java
index 1f14c63..91acc2a 100644
--- a/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/ParamConverterDateTest.java
+++ b/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/ParamConverterDateTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2022 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
@@ -40,6 +40,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ParamConverterDateTest extends AbstractTest {
+ private final String format = "EEE MMM dd HH:mm:ss Z yyyy";
+ private final SimpleDateFormat formatter = new SimpleDateFormat(format, new Locale("US"));
@Path("/")
public static class DateResource {
@@ -55,7 +57,7 @@
public void testDateResource() throws ExecutionException, InterruptedException {
initiateWebApplication(getBinder(), ParamConverterDateTest.DateResource.class);
final ContainerResponse responseContext = getResponseContext(UriBuilder.fromPath("/")
- .queryParam("d", new Date()).build().toString());
+ .queryParam("d", formatter.format(new Date())).build().toString());
assertEquals(200, responseContext.getStatus());
}
@@ -80,8 +82,6 @@
);
}
try {
- final String format = "EEE MMM dd HH:mm:ss Z yyyy";
- final SimpleDateFormat formatter = new SimpleDateFormat(format, new Locale("US"));
return rawType.cast(formatter.parse(value));
} catch (final ParseException ex) {
throw new ExtractorException(ex);
diff --git a/examples/groovy/pom.xml b/examples/groovy/pom.xml
index 77d2821..0766899 100644
--- a/examples/groovy/pom.xml
+++ b/examples/groovy/pom.xml
@@ -43,6 +43,14 @@
<artifactId>junit-jupiter-api</artifactId>
</exclusion>
<exclusion>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.junit.platform</groupId>
+ <artifactId>junit-platform-commons</artifactId>
+ </exclusion>
+ <exclusion>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
</exclusion>
@@ -117,10 +125,12 @@
<goal>removeTestStubs</goal>
<goal>groovydoc</goal>
</goals>
+ <configuration>
+ <targetBytecode>11</targetBytecode>
+ </configuration>
</execution>
</executions>
</plugin>
-
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
diff --git a/examples/osgi-helloworld-webapp/pom.xml b/examples/osgi-helloworld-webapp/pom.xml
index f25be4e..531d0b0 100644
--- a/examples/osgi-helloworld-webapp/pom.xml
+++ b/examples/osgi-helloworld-webapp/pom.xml
@@ -25,16 +25,21 @@
<name>jersey-examples-osgi-helloworld-webapp</name>
<packaging>pom</packaging>
- <modules>
- <module>war-bundle</module>
- <module>functional-test</module>
- <module>lib-bundle</module>
- <module>additional-bundle</module>
- <module>alternate-version-bundle</module>
- </modules>
-
<profiles>
<profile>
+ <id>securityOn</id>
+ <activation>
+ <jdk>[11,24)</jdk>
+ </activation>
+ <modules>
+ <module>war-bundle</module>
+ <module>functional-test</module>
+ <module>lib-bundle</module>
+ <module>additional-bundle</module>
+ <module>alternate-version-bundle</module>
+ </modules>
+ </profile>
+ <profile>
<id>pre-release</id>
<build>
<plugins>
diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/ObservationRequestEventListener.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/ObservationRequestEventListener.java
index 953944b..2db4cd1 100644
--- a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/ObservationRequestEventListener.java
+++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/ObservationRequestEventListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -65,7 +65,7 @@
switch (event.getType()) {
case ON_EXCEPTION:
- if (!isNotFoundException(event)) {
+ if (!isClientError(event) || observations.get(containerRequest) != null) {
break;
}
startObservation(event);
@@ -102,13 +102,14 @@
observations.put(event.getContainerRequest(), new ObservationScopeAndContext(scope, jerseyContext));
}
- private boolean isNotFoundException(RequestEvent event) {
+ private boolean isClientError(RequestEvent event) {
Throwable t = event.getException();
if (t == null) {
return false;
}
- String className = t.getClass().getCanonicalName();
- return className.equals("jakarta.ws.rs.NotFoundException") || className.equals("jakarta.ws.rs.NotFoundException");
+ String className = t.getClass().getSuperclass().getCanonicalName();
+ return className.equals("jakarta.ws.rs.ClientErrorException")
+ || className.equals("javax.ws.rs.ClientErrorException");
}
private static class ObservationScopeAndContext {
diff --git a/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/observation/AbstractObservationRequestEventListenerTest.java b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/observation/AbstractObservationRequestEventListenerTest.java
index 0ae7ab6..d46e855 100644
--- a/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/observation/AbstractObservationRequestEventListenerTest.java
+++ b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/observation/AbstractObservationRequestEventListenerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -20,6 +20,7 @@
import java.util.logging.Logger;
import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.MediaType;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
@@ -38,6 +39,7 @@
import io.micrometer.tracing.test.simple.SpansAssert;
import org.glassfish.jersey.micrometer.server.ObservationApplicationEventListener;
import org.glassfish.jersey.micrometer.server.ObservationRequestEventListener;
+import org.glassfish.jersey.micrometer.server.mapper.ResourceGoneExceptionMapper;
import org.glassfish.jersey.micrometer.server.resources.TestResource;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
@@ -85,6 +87,7 @@
final ResourceConfig config = new ResourceConfig();
config.register(listener);
config.register(TestResource.class);
+ config.register(ResourceGoneExceptionMapper.class);
return config;
}
@@ -131,6 +134,53 @@
.hasTag("outcome", "SUCCESS")
.hasTag("status", "200")
.hasTag("uri", "/sub-resource/sub-hello/{name}");
+ assertThat(observationRegistry.getCurrentObservation()).isNull();
+ }
+
+ @Test
+ void errorResourcesAreTimed() {
+ try {
+ target("throws-exception").request().get();
+ }
+ catch (Exception ignored) {
+ }
+ try {
+ target("throws-webapplication-exception").request().get();
+ }
+ catch (Exception ignored) {
+ }
+ try {
+ target("throws-mappable-exception").request().get();
+ }
+ catch (Exception ignored) {
+ }
+ try {
+ target("produces-text-plain").request(MediaType.APPLICATION_JSON).get();
+ }
+ catch (Exception ignored) {
+ }
+
+ assertThat(registry.get(METRIC_NAME)
+ .tags(tagsFrom("/throws-exception", "500", "SERVER_ERROR", "IllegalArgumentException"))
+ .timer()
+ .count()).isEqualTo(1);
+
+ assertThat(registry.get(METRIC_NAME)
+ .tags(tagsFrom("/throws-webapplication-exception", "401", "CLIENT_ERROR", "NotAuthorizedException"))
+ .timer()
+ .count()).isEqualTo(1);
+
+ assertThat(registry.get(METRIC_NAME)
+ .tags(tagsFrom("/throws-mappable-exception", "410", "CLIENT_ERROR", "ResourceGoneException"))
+ .timer()
+ .count()).isEqualTo(1);
+
+ assertThat(registry.get(METRIC_NAME)
+ .tags(tagsFrom("UNKNOWN", "406", "CLIENT_ERROR", "NotAcceptableException"))
+ .timer()
+ .count()).isEqualTo(1);
+
+ assertThat(observationRegistry.getCurrentObservation()).isNull();
}
private static Iterable<Tag> tagsFrom(String uri, String status, String outcome, String exception) {
diff --git a/incubator/pom.xml b/incubator/pom.xml
index 609fd98..528eb08 100644
--- a/incubator/pom.xml
+++ b/incubator/pom.xml
@@ -39,7 +39,6 @@
<module>cdi-inject-weld</module>
<module>declarative-linking</module>
<module>gae-integration</module>
- <module>html-json</module>
<module>injectless-client</module>
<module>kryo</module>
<module>open-tracing</module>
@@ -53,4 +52,16 @@
<scope>test</scope>
</dependency>
</dependencies>
+
+ <profiles>
+ <profile>
+ <id>HTML-JSON-FOR-PRE-JDK24</id>
+ <activation>
+ <jdk>[11, 24)</jdk>
+ </activation>
+ <modules>
+ <module>html-json</module>
+ </modules>
+ </profile>
+ </profiles>
</project>
diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JacksonFeature.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JacksonFeature.java
index 5411720..9391bc6 100644
--- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JacksonFeature.java
+++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JacksonFeature.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2023 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
@@ -29,8 +29,10 @@
import org.glassfish.jersey.jackson.internal.DefaultJacksonJaxbJsonProvider;
import org.glassfish.jersey.jackson.internal.FilteringJacksonJaxbJsonProvider;
import org.glassfish.jersey.jackson.internal.JacksonFilteringFeature;
+import org.glassfish.jersey.jackson.internal.JaxrsFeatureBag;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.JsonMappingExceptionMapper;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.JsonParseExceptionMapper;
+import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import org.glassfish.jersey.message.MessageProperties;
import org.glassfish.jersey.message.filtering.EntityFilteringFeature;
@@ -41,7 +43,7 @@
* @author Stepan Kopriva
* @author Michal Gajdos
*/
-public class JacksonFeature implements Feature {
+public class JacksonFeature extends JaxrsFeatureBag<JacksonFeature> implements Feature {
/**
* Define whether to use Jackson's exception mappers ore not
@@ -100,6 +102,16 @@
return this;
}
+ /**
+ * Register {@link JaxRSFeature} with the Jackson providers.
+ * @param feature the {@link JaxRSFeature} to be enabled or disabled.
+ * @param state {@code true} for enabling the feature, {@code false} for disabling.
+ * @return JacksonFeature with {@link JaxRSFeature} registered to be set on a created Jackson provider.
+ */
+ public JacksonFeature jaxrsFeature(JaxRSFeature feature, boolean state) {
+ return super.jaxrsFeature(feature, state);
+ }
+
private static final String JSON_FEATURE = JacksonFeature.class.getSimpleName();
@Override
@@ -138,6 +150,10 @@
context.property(MessageProperties.JSON_MAX_STRING_LENGTH, maxStringLength);
}
+ if (hasJaxrsFeature()) {
+ context.property(JaxrsFeatureBag.JAXRS_FEATURE, this);
+ }
+
return true;
}
}
diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JaxRSFeatureObjectMapper.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JaxRSFeatureObjectMapper.java
new file mode 100644
index 0000000..d759e56
--- /dev/null
+++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JaxRSFeatureObjectMapper.java
@@ -0,0 +1,67 @@
+/*
+ * 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.jackson;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.glassfish.jersey.jackson.internal.AbstractObjectMapper;
+import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature;
+
+
+/**
+ * The Jackson {@link ObjectMapper} supporting {@link JaxRSFeature}s.
+ */
+public class JaxRSFeatureObjectMapper extends AbstractObjectMapper {
+
+ public JaxRSFeatureObjectMapper() {
+ super();
+ }
+
+ /**
+ * Method for changing state of an on/off {@link org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature}
+ * features.
+ */
+ public ObjectMapper configure(JaxRSFeature f, boolean state) {
+ jaxrsFeatureBag.jaxrsFeature(f, state);
+ return this;
+ }
+
+ /**
+ * Method for enabling specified {@link org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature}s
+ * for parser instances this object mapper creates.
+ */
+ public ObjectMapper enable(JaxRSFeature... features) {
+ if (features != null) {
+ for (JaxRSFeature f : features) {
+ jaxrsFeatureBag.jaxrsFeature(f, true);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Method for disabling specified {@link org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature}s
+ * for parser instances this object mapper creates.
+ */
+ public ObjectMapper disable(JaxRSFeature... features) {
+ if (features != null) {
+ for (JaxRSFeature f : features) {
+ jaxrsFeatureBag.jaxrsFeature(f, false);
+ }
+ }
+ return this;
+ }
+}
diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/AbstractObjectMapper.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/AbstractObjectMapper.java
new file mode 100644
index 0000000..2f331cf
--- /dev/null
+++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/AbstractObjectMapper.java
@@ -0,0 +1,29 @@
+/*
+ * 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.jackson.internal;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Internal ObjectMapper with {@link JaxrsFeatureBag}.
+ */
+public abstract class AbstractObjectMapper extends ObjectMapper {
+ protected AbstractObjectMapper() {
+
+ }
+ protected JaxrsFeatureBag jaxrsFeatureBag = new JaxrsFeatureBag();
+}
diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java
index ac0b5c9..0b9222c 100644
--- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java
+++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java
@@ -18,7 +18,6 @@
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.StreamReadConstraints;
-import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.json.PackageVersion;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.Module;
@@ -41,6 +40,7 @@
import jakarta.inject.Singleton;
import jakarta.ws.rs.core.Configuration;
import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.ext.Providers;
/**
@@ -53,9 +53,7 @@
@Inject
public DefaultJacksonJaxbJsonProvider(@Context Providers providers, @Context Configuration config) {
- super(new JacksonMapperConfigurator(null, DEFAULT_ANNOTATIONS));
- this.commonConfig = config;
- _providers = providers;
+ this(providers, config, DEFAULT_ANNOTATIONS);
}
//do not register JaxbAnnotationModule because it brakes default annotations processing
@@ -65,6 +63,20 @@
super(new JacksonMapperConfigurator(null, annotationsToUse));
this.commonConfig = config;
_providers = providers;
+
+ Object jaxrsFeatureBag = config.getProperty(JaxrsFeatureBag.JAXRS_FEATURE);
+ if (jaxrsFeatureBag != null && (JaxrsFeatureBag.class.isInstance(jaxrsFeatureBag))) {
+ ((JaxrsFeatureBag) jaxrsFeatureBag).configureJaxrsFeatures(this);
+ }
+ }
+
+ @Override
+ protected ObjectMapper _locateMapperViaProvider(Class<?> type, MediaType mediaType) {
+ ObjectMapper mapper = super._locateMapperViaProvider(type, mediaType);
+ if (AbstractObjectMapper.class.isInstance(mapper)) {
+ ((AbstractObjectMapper) mapper).jaxrsFeatureBag.configureJaxrsFeatures(this);
+ }
+ return mapper;
}
@Override
diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/JaxrsFeatureBag.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/JaxrsFeatureBag.java
new file mode 100644
index 0000000..4711b56
--- /dev/null
+++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/JaxrsFeatureBag.java
@@ -0,0 +1,58 @@
+/*
+ * 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.jackson.internal;
+
+import org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.ProviderBase;
+import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Internal holder class for {@link JaxRSFeature} settings and their values.
+ */
+public class JaxrsFeatureBag<T extends JaxrsFeatureBag> {
+ protected static final String JAXRS_FEATURE = "jersey.config.jackson.jaxrs.feature";
+
+ private static class JaxRSFeatureState {
+ /* package */ final JaxRSFeature feature;
+ /* package */ final boolean state;
+ public JaxRSFeatureState(JaxRSFeature feature, boolean state) {
+ this.feature = feature;
+ this.state = state;
+ }
+ }
+
+ private Optional<List<JaxRSFeatureState>> jaxRSFeature = Optional.empty();
+
+ public T jaxrsFeature(JaxRSFeature feature, boolean state) {
+ if (!jaxRSFeature.isPresent()) {
+ jaxRSFeature = Optional.of(new ArrayList<>());
+ }
+ jaxRSFeature.ifPresent(list -> list.add(new JaxrsFeatureBag.JaxRSFeatureState(feature, state)));
+ return (T) this;
+ }
+
+ protected boolean hasJaxrsFeature() {
+ return jaxRSFeature.isPresent();
+ }
+
+ /* package */ void configureJaxrsFeatures(ProviderBase providerBase) {
+ jaxRSFeature.ifPresent(list -> list.stream().forEach(state -> providerBase.configure(state.feature, state.state)));
+ }
+}
diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/JsonMapperConfigurator.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/JsonMapperConfigurator.java
index f4bec3e..fd5d9aa 100644
--- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/JsonMapperConfigurator.java
+++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/JsonMapperConfigurator.java
@@ -129,7 +129,7 @@
}
return _jaxbIntrospectorClass.newInstance();
} catch (Exception e) {
- throw new IllegalStateException("Failed to instantiate JaxbAnnotationIntrospector: "+e.getMessage(), e);
+ throw new IllegalStateException("Failed to instantiate JakartaXmlBindAnnotationIntrospector: "+e.getMessage(), e);
}
default:
throw new IllegalStateException();
diff --git a/media/json-jackson/src/test/java/org/glassfish/jersey/jackson/internal/JaxRSFeatureTest.java b/media/json-jackson/src/test/java/org/glassfish/jersey/jackson/internal/JaxRSFeatureTest.java
new file mode 100644
index 0000000..86b6200
--- /dev/null
+++ b/media/json-jackson/src/test/java/org/glassfish/jersey/jackson/internal/JaxRSFeatureTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.jackson.internal;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.jackson.JaxRSFeatureObjectMapper;
+import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ContextResolver;
+import jakarta.ws.rs.ext.Providers;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+public class JaxRSFeatureTest {
+ @Test
+ public void testJaxrsFeatureOnJacksonFeature() {
+ Client client = ClientBuilder.newClient()
+ .register(new JacksonFeature().jaxrsFeature(JaxRSFeature.READ_FULL_STREAM, false))
+ .register(JaxrsFeatureFilter.class);
+
+ try (Response r = client.target("http://xxx.yyy").request().get()) {
+ MatcherAssert.assertThat(r.getStatus(), Matchers.is(200));
+ }
+ }
+
+ @Test
+ public void testJaxrsFeatureOnContextResolver() {
+ Client client = ClientBuilder.newClient()
+ .register(JacksonFeature.class)
+ .register(JaxrsFetureContextResolver.class)
+ .register(JaxrsFeatureFilter.class);
+
+ try (Response r = client.target("http://xxx.yyy").request().get()) {
+ MatcherAssert.assertThat(r.getStatus(), Matchers.is(200));
+ }
+ }
+
+
+ public static class JaxrsFeatureFilter implements ClientRequestFilter {
+ private final DefaultJacksonJaxbJsonProvider jacksonProvider;
+ @Inject
+ public JaxrsFeatureFilter(Providers allProviders) {
+ jacksonProvider = (DefaultJacksonJaxbJsonProvider)
+ allProviders.getMessageBodyReader(Object.class, Object.class, null, MediaType.APPLICATION_JSON_TYPE);
+ try {
+ jacksonProvider.readFrom(Object.class, Object.class, null, MediaType.APPLICATION_JSON_TYPE, null,
+ new ByteArrayInputStream("{}".getBytes()));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ Response.Status status = jacksonProvider.isEnabled(JaxRSFeature.READ_FULL_STREAM)
+ ? Response.Status.FORBIDDEN
+ : Response.Status.OK;
+ requestContext.abortWith(Response.status(status).build());
+ }
+ }
+
+ public static class JaxrsFetureContextResolver implements ContextResolver<ObjectMapper> {
+
+ @Override
+ public ObjectMapper getContext(Class<?> type) {
+ JaxRSFeatureObjectMapper objectMapper = new JaxRSFeatureObjectMapper();
+ objectMapper.disable(JaxRSFeature.READ_FULL_STREAM);
+ return objectMapper;
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index 3f62b1d..f1ab454 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2106,7 +2106,7 @@
<resources.mvn.plugin.version>3.3.1</resources.mvn.plugin.version>
<shade.mvn.plugin.version>3.6.0</shade.mvn.plugin.version>
<source.mvn.plugin.version>3.3.1</source.mvn.plugin.version>
- <surefire.mvn.plugin.version>3.3.1</surefire.mvn.plugin.version>
+ <surefire.mvn.plugin.version>3.5.2</surefire.mvn.plugin.version>
<war.mvn.plugin.version>3.4.0</war.mvn.plugin.version>
<wiremock.mvn.plugin.version>2.11.0</wiremock.mvn.plugin.version>
<xml.mvn.plugin.version>1.1.0</xml.mvn.plugin.version>
@@ -2135,7 +2135,7 @@
<findbugs.glassfish.version>1.7</findbugs.glassfish.version>
<freemarker.version>2.3.33</freemarker.version>
<gae.version>2.0.29</gae.version>
- <groovy.version>4.0.23</groovy.version>
+ <groovy.version>5.0.0-alpha-11</groovy.version>
<gson.version>2.11.0</gson.version>
<!--versions, extracted here due to maven-enforcer-plugin -->
@@ -2169,7 +2169,7 @@
<jmh.version>1.37</jmh.version>
<jmockit.version>1.49</jmockit.version>
<junit4.version>4.13.2</junit4.version>
- <junit5.version>5.11.0</junit5.version>
+ <junit5.version>5.11.4</junit5.version>
<junit-platform-suite.version>1.11.0</junit-platform-suite.version>
<junit-platform-suite.legacy.version>1.10.0</junit-platform-suite.legacy.version>
<kryo.version>4.0.3</kryo.version>
diff --git a/test-framework/maven/container-runner-maven-plugin/pom.xml b/test-framework/maven/container-runner-maven-plugin/pom.xml
index b706320..6acb575 100644
--- a/test-framework/maven/container-runner-maven-plugin/pom.xml
+++ b/test-framework/maven/container-runner-maven-plugin/pom.xml
@@ -36,7 +36,6 @@
</description>
<properties>
-<!-- <groovy.version>3.0.21</groovy.version>-->
<groovy-eclipse-compiler.version>3.7.0</groovy-eclipse-compiler.version>
<groovy-eclipse-batch.version>3.0.8-01</groovy-eclipse-batch.version>
<maven.version>3.9.2</maven.version>
diff --git a/tests/e2e-inject/non-inject/pom.xml b/tests/e2e-inject/non-inject/pom.xml
new file mode 100644
index 0000000..76a53e2
--- /dev/null
+++ b/tests/e2e-inject/non-inject/pom.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>e2e-inject</artifactId>
+ <groupId>org.glassfish.jersey.tests</groupId>
+ <version>3.1.99-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>e2e-inject-noninject</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.incubator</groupId>
+ <artifactId>jersey-injectless-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+
+</project>
diff --git a/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/DisposableSuplierTest.java b/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/DisposableSuplierTest.java
new file mode 100644
index 0000000..0d36f34
--- /dev/null
+++ b/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/DisposableSuplierTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.inject.noninject;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.DisposableSupplier;
+import org.glassfish.jersey.process.internal.RequestScoped;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.core.Response;
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class DisposableSuplierTest {
+ private static final AtomicInteger disposeCounter = new AtomicInteger(0);
+ private static final String HOST = "http://somewhere.anywhere";
+
+ public interface ResponseObject {
+ Response getResponse();
+ }
+
+ private static class TestDisposableSupplier implements DisposableSupplier<ResponseObject> {
+ AtomicInteger counter = new AtomicInteger(300);
+
+ @Override
+ public void dispose(ResponseObject instance) {
+ disposeCounter.incrementAndGet();
+ }
+
+ @Override
+ public ResponseObject get() {
+ return new ResponseObject() {
+
+ @Override
+ public Response getResponse() {
+ return Response.ok().build();
+ }
+ };
+ }
+ }
+
+ private static class DisposableSupplierInjectingFilter implements ClientRequestFilter {
+ private final ResponseObject responseSupplier;
+
+ @Inject
+ private DisposableSupplierInjectingFilter(ResponseObject responseSupplier) {
+ this.responseSupplier = responseSupplier;
+ }
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ requestContext.abortWith(responseSupplier.getResponse());
+ }
+ }
+
+ @Test
+ public void testDisposeCount() {
+ disposeCounter.set(0);
+ int CNT = 4;
+ Client client = ClientBuilder.newClient()
+ .register(new AbstractBinder() {
+ @Override
+ protected void configure() {
+ bindFactory(TestDisposableSupplier.class).to(ResponseObject.class)
+ .proxy(true).proxyForSameScope(false).in(RequestScoped.class);
+ }
+ }).register(DisposableSupplierInjectingFilter.class);
+
+ for (int i = 0; i != CNT; i++) {
+ try (Response response = client.target(HOST).request().get()) {
+ MatcherAssert.assertThat(response.getStatus(), Matchers.is(200));
+ }
+ }
+
+ MatcherAssert.assertThat(disposeCounter.get(), Matchers.is(CNT));
+ }
+}
diff --git a/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/InstanceListSizeTest.java b/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/InstanceListSizeTest.java
new file mode 100644
index 0000000..2acb538
--- /dev/null
+++ b/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/InstanceListSizeTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.inject.noninject;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientRequest;
+import org.glassfish.jersey.client.innate.inject.NonInjectionManager;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.core.Response;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+public class InstanceListSizeTest {
+
+ private static final String TEST_HEADER = "TEST";
+ private static final String HOST = "https://anywhere.any";
+
+ @Test
+ public void leakTest() throws ExecutionException, InterruptedException {
+ int CNT = 100;
+ Client client = ClientBuilder.newClient();
+ client.register(InjectionManagerGrowChecker.class);
+ Response response = client.target(HOST).request().header(TEST_HEADER, "0").get();
+ int status = response.getStatus();
+ response.close();
+ //Create instance in NonInjectionManager$TypedInstances.threadPredestroyables
+
+ for (int i = 0; i <= CNT; i++) {
+ final String header = String.valueOf(i + 1);
+ try (Response r = client.target(HOST).request()
+ .header(TEST_HEADER, header)
+ .async()
+ .post(Entity.text("text")).get()) {
+ int stat = r.getStatus();
+ MatcherAssert.assertThat(
+ "NonInjectionManager#Types#disposableSupplierObjects is increasing", stat, Matchers.is(202));
+ }
+ }
+ //Create 10 instance in NonInjectionManager$TypedInstances.threadPredestroyables
+
+ for (int i = 0; i <= CNT; i++) {
+ final String header = String.valueOf(i + CNT + 2);
+ final Object text = CompletableFuture.supplyAsync(() -> {
+ Response test = client.target(HOST).request()
+ .header("TEST", header)
+ .post(Entity.text("text"));
+ int stat = test.getStatus();
+ test.close();
+ MatcherAssert.assertThat(
+ "NonInjectionManager#Types#disposableSupplierObjects is increasing", stat, Matchers.is(202));
+
+ return null;
+ }).join();
+ }
+ //Create 10 instance in NonInjectionManager$TypedInstances.threadPredestroyables
+
+ response = client.target(HOST).request().header(TEST_HEADER, 2 * CNT + 3).get();
+ status = response.getStatus();
+ MatcherAssert.assertThat(status, Matchers.is(202));
+ response.close();
+ }
+
+ private static class InjectionManagerGrowChecker implements ClientRequestFilter {
+ private boolean first = true;
+ private int disposableSize = 0;
+ private int threadInstancesSize = 0;
+ private HttpHeaders headers;
+ private int headerCnt = 0;
+
+ @Inject
+ public InjectionManagerGrowChecker(HttpHeaders headers) {
+ this.headers = headers;
+ }
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ Response.Status status = Response.Status.ACCEPTED;
+ if (headerCnt++ != Integer.parseInt(headers.getHeaderString("TEST"))) {
+ status = Response.Status.BAD_REQUEST;
+ }
+
+ NonInjectionManager nonInjectionManager = getInjectionManager(requestContext);
+ Object types = getDeclaredField(nonInjectionManager, "types");
+ Object instances = getDeclaredField(nonInjectionManager, "instances");
+ if (first) {
+ first = false;
+ disposableSize = getThreadInstances(types, "disposableSupplierObjects")
+ + getThreadInstances(instances, "disposableSupplierObjects");
+ threadInstancesSize = getThreadInstances(types, "threadInstances")
+ + getThreadInstances(instances, "threadInstances");
+ } else {
+ int newPredestroyableSize = getThreadInstances(types, "disposableSupplierObjects")
+ + getThreadInstances(instances, "disposableSupplierObjects");
+ if (newPredestroyableSize > disposableSize + 1 /* a new service to get disposed */) {
+ status = Response.Status.EXPECTATION_FAILED;
+ }
+ int newThreadInstances = getThreadInstances(types, "threadInstances")
+ + getThreadInstances(instances, "threadInstances");
+ if (newThreadInstances > threadInstancesSize) {
+ status = Response.Status.PRECONDITION_FAILED;
+ }
+ }
+
+ requestContext.abortWith(Response.status(status).build());
+ }
+ }
+
+ private static NonInjectionManager getInjectionManager(ClientRequestContext context) {
+ ClientRequest request = ((ClientRequest) context);
+ try {
+ Method clientConfigMethod = ClientRequest.class.getDeclaredMethod("getClientConfig");
+ clientConfigMethod.setAccessible(true);
+ ClientConfig clientConfig = (ClientConfig) clientConfigMethod.invoke(request);
+
+ Method runtimeMethod = ClientConfig.class.getDeclaredMethod("getRuntime");
+ runtimeMethod.setAccessible(true);
+ Object clientRuntime = runtimeMethod.invoke(clientConfig);
+ Class<?> clientRuntimeClass = clientRuntime.getClass();
+
+ Method injectionManagerMethod = clientRuntimeClass.getDeclaredMethod("getInjectionManager");
+ injectionManagerMethod.setAccessible(true);
+ InjectionManager injectionManager = (InjectionManager) injectionManagerMethod.invoke(clientRuntime);
+ return (NonInjectionManager) injectionManager;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static Object getDeclaredField(NonInjectionManager nonInjectionManager, String name) {
+ try {
+ Field typesField = NonInjectionManager.class.getDeclaredField(name);
+ typesField.setAccessible(true);
+ return typesField.get(nonInjectionManager);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static int getThreadInstances(Object typedInstances, String threadLocalName) {
+ try {
+ Field threadLocalField =
+ typedInstances.getClass().getSuperclass().getDeclaredField(threadLocalName);
+ threadLocalField.setAccessible(true);
+ ThreadLocal<MultivaluedMap<?,?>> threadLocal =
+ (ThreadLocal<MultivaluedMap<?, ?>>) threadLocalField.get(typedInstances);
+ MultivaluedMap<?, ?> map = threadLocal.get();
+ if (map == null) {
+ return 0;
+ } else {
+ int cnt = 0;
+ Set<? extends Map.Entry<?, ? extends List<?>>> set = map.entrySet();
+ for (Map.Entry<?, ? extends List<?>> entry : map.entrySet()) {
+ cnt += entry.getValue().size();
+ }
+ return cnt;
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+}
diff --git a/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/PreDestroyTest.java b/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/PreDestroyTest.java
new file mode 100644
index 0000000..0f43b44
--- /dev/null
+++ b/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/PreDestroyTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.inject.noninject;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.DisposableSupplier;
+import org.glassfish.jersey.process.internal.RequestScoped;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+import jakarta.annotation.PreDestroy;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.core.Response;
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class PreDestroyTest {
+ private static final AtomicInteger disposeCounter = new AtomicInteger(0);
+ private static final String HOST = "http://somewhere.anywhere";
+
+ public interface ResponseObject {
+ Response getResponse();
+ }
+
+ public static class ResponseObjectImpl implements ResponseObject {
+
+ public ResponseObjectImpl() {
+
+ }
+
+ @PreDestroy
+ public void preDestroy() {
+ disposeCounter.incrementAndGet();
+ }
+
+ @Override
+ public Response getResponse() {
+ return Response.ok().build();
+ }
+ }
+
+ private static class PreDestroyInjectingFilter implements ClientRequestFilter {
+ private final ResponseObject responseSupplier;
+
+ @Inject
+ private PreDestroyInjectingFilter(ResponseObject responseSupplier) {
+ this.responseSupplier = responseSupplier;
+ }
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ requestContext.abortWith(responseSupplier.getResponse());
+ }
+ }
+
+ @Test
+ public void testPreDestroyCount() {
+ disposeCounter.set(0);
+ int CNT = 4;
+ Client client = ClientBuilder.newClient()
+ .register(new AbstractBinder() {
+ @Override
+ protected void configure() {
+ bind(ResponseObjectImpl.class).to(ResponseObject.class)
+ .proxy(true).proxyForSameScope(false).in(RequestScoped.class);
+ }
+ }).register(PreDestroyInjectingFilter.class);
+
+ for (int i = 0; i != CNT; i++) {
+ try (Response response = client.target(HOST).request().get()) {
+ MatcherAssert.assertThat(response.getStatus(), Matchers.is(200));
+ }
+ }
+
+ MatcherAssert.assertThat(disposeCounter.get(), Matchers.is(1));
+ }
+}
diff --git a/tests/e2e-inject/pom.xml b/tests/e2e-inject/pom.xml
index f7744f5..2c9d093 100644
--- a/tests/e2e-inject/pom.xml
+++ b/tests/e2e-inject/pom.xml
@@ -36,5 +36,6 @@
<module>cdi2-se</module>
<module>cdi-inject-weld</module>
<module>hk2</module>
+ <module>non-inject</module>
</modules>
</project>
diff --git a/tests/e2e-tls/pom.xml b/tests/e2e-tls/pom.xml
index 188f4f0..2ac849e 100644
--- a/tests/e2e-tls/pom.xml
+++ b/tests/e2e-tls/pom.xml
@@ -170,6 +170,24 @@
</http.patch.addopens>
</properties>
</profile>
+ <profile>
+ <id>JDK_17-</id>
+ <activation>
+ <jdk>[11,17)</jdk>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <!-- The certificate is not working with JDK 11 -->
+ <excludes><exclude>**/ConcurrentHttpsUrlConnectionTest*</exclude></excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
</profiles>
</project>
diff --git a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/ConcurrentHttpsUrlConnectionTest.java b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/ConcurrentHttpsUrlConnectionTest.java
new file mode 100644
index 0000000..444cee6
--- /dev/null
+++ b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/ConcurrentHttpsUrlConnectionTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.tls.connector;
+
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.core.GenericType;
+import jakarta.ws.rs.core.MediaType;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.security.KeyStore;
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManagerFactory;
+
+/**
+ * Jersey client seems to be not thread-safe:
+ * When the first GET request is in progress,
+ * all parallel requests from other Jersey client instances fail
+ * with SSLHandshakeException: PKIX path building failed.
+ * <p>
+ * Once the first GET request is completed,
+ * all subsequent requests work without error.
+ * <p>
+ * BUG 5749
+ */
+public class ConcurrentHttpsUrlConnectionTest {
+ private static int THREAD_NUMBER = 5;
+
+ private static volatile int responseCounter = 0;
+
+ private static SSLContext createContext() throws Exception {
+ URL url = ConcurrentHttpsUrlConnectionTest.class.getResource("keystore.jks");
+ KeyStore keyStore = KeyStore.getInstance("JKS");
+ try (InputStream is = url.openStream()) {
+ keyStore.load(is, "password".toCharArray());
+ }
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(keyStore, "password".toCharArray());
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
+ tmf.init(keyStore);
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+ return context;
+ }
+
+ @Test
+ public void testSSLConnections() throws Exception {
+ if (THREAD_NUMBER == 1) {
+ System.out.println("\nThis is the working case (THREAD_NUMBER==1). Set THREAD_NUMBER > 1 to reproduce the error! \n");
+ }
+
+ final HttpsServer server = new HttpsServer(createContext());
+ Executors.newFixedThreadPool(1).submit(server);
+
+ // set THREAD_NUMBER > 1 to reproduce an issue
+ ExecutorService executorService2clients = Executors.newFixedThreadPool(THREAD_NUMBER);
+
+ final ClientBuilder builder = ClientBuilder.newBuilder().sslContext(createContext())
+ .hostnameVerifier(new HostnameVerifier() {
+ public boolean verify(String arg0, SSLSession arg1) {
+ return true;
+ }
+ });
+
+ AtomicInteger counter = new AtomicInteger(0);
+
+ for (int i = 0; i < THREAD_NUMBER; i++) {
+ executorService2clients.submit(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Client client = builder.build();
+ String ret = client.target("https://127.0.0.1:" + server.getPort() + "/" + new Random().nextInt())
+ .request(MediaType.TEXT_HTML)
+ .get(new GenericType<String>() {
+ });
+ System.out.print(++responseCounter + ". Server returned: " + ret);
+ } catch (Exception e) {
+ //get an exception here, if jersey lib is buggy and THREAD_NUMBER > 1:
+ //jakarta.ws.rs.ProcessingException: jakarta.net.ssl.SSLHandshakeException: PKIX path building failed:
+ e.printStackTrace();
+ } finally {
+ System.out.println(counter.incrementAndGet());
+ }
+ }
+ });
+ }
+
+ while (counter.get() != THREAD_NUMBER) {
+ Thread.sleep(100L);
+ }
+ server.stop();
+ }
+}
diff --git a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/HttpsServer.java b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/HttpsServer.java
new file mode 100644
index 0000000..31214f1
--- /dev/null
+++ b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/HttpsServer.java
@@ -0,0 +1,87 @@
+/*
+ * 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.tls.connector;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
+
+class HttpsServer implements Runnable {
+ private final SSLServerSocket sslServerSocket;
+ private boolean closed = false;
+
+ public HttpsServer(SSLContext context) throws Exception {
+ sslServerSocket = (SSLServerSocket) context.getServerSocketFactory().createServerSocket(0);
+ }
+
+ public int getPort() {
+ return sslServerSocket.getLocalPort();
+ }
+
+ @Override
+ public void run() {
+ System.out.printf("Server started on port %d%n", getPort());
+ while (!closed) {
+ SSLSocket s;
+ try {
+ s = (SSLSocket) sslServerSocket.accept();
+ } catch (IOException e2) {
+ s = null;
+ }
+ final SSLSocket socket = s;
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ if (socket != null) {
+ InputStream is = new BufferedInputStream(socket.getInputStream());
+ byte[] data = new byte[2048];
+ int len = is.read(data);
+ if (len <= 0) {
+ throw new IOException("no data received");
+ }
+ //System.out.printf("Server received: %s\n", new String(data, 0, len));
+ PrintWriter writer = new PrintWriter(socket.getOutputStream());
+ writer.println("HTTP/1.1 200 OK");
+ writer.println("Content-Type: text/html");
+ writer.println();
+ writer.println("Hello from server!");
+ writer.flush();
+ writer.close();
+ socket.close();
+ }
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ }
+ }).start();
+ }
+ }
+
+ void stop() {
+ try {
+ closed = true;
+ sslServerSocket.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tests/e2e-tls/src/test/resources/org/glassfish/jersey/tests/e2e/tls/connector/keystore.jks b/tests/e2e-tls/src/test/resources/org/glassfish/jersey/tests/e2e/tls/connector/keystore.jks
new file mode 100644
index 0000000..130aa71
--- /dev/null
+++ b/tests/e2e-tls/src/test/resources/org/glassfish/jersey/tests/e2e/tls/connector/keystore.jks
Binary files differ
diff --git a/tests/integration/microprofile/rest-client/pom.xml b/tests/integration/microprofile/rest-client/pom.xml
index 09204f9..2b98db3 100644
--- a/tests/integration/microprofile/rest-client/pom.xml
+++ b/tests/integration/microprofile/rest-client/pom.xml
@@ -107,6 +107,15 @@
</dependency>
</dependencies>
</profile>
+ <profile>
+ <id>securityOff</id>
+ <activation>
+ <jdk>[24,)</jdk>
+ </activation>
+ <properties>
+ <surefire.security.argline />
+ </properties>
+ </profile>
</profiles>
<properties>
diff --git a/tests/pom.xml b/tests/pom.xml
index 8611c67..24f7a82 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -47,7 +47,6 @@
<module>e2e-testng</module>
<module>e2e-tls</module>
<module>integration</module>
- <module>jmockit</module>
<module>mem-leaks</module>
<module>osgi</module>
<module>stress</module>
@@ -133,5 +132,14 @@
<module>version-agnostic</module>
</modules>
</profile>
+ <profile>
+ <id>JMOCKIT-MODULE-FOR-PRE-JDK24</id>
+ <activation>
+ <jdk>[11,24)</jdk>
+ </activation>
+ <modules>
+ <module>jmockit</module>
+ </modules>
+ </profile>
</profiles>
</project>