merge of the actual 2.x into the 3.0
diff --git a/bundles/examples/pom.xml b/bundles/examples/pom.xml index e39812c..48d44ac 100644 --- a/bundles/examples/pom.xml +++ b/bundles/examples/pom.xml
@@ -198,13 +198,6 @@ </dependency> <dependency> <groupId>org.glassfish.jersey.examples</groupId> - <artifactId>groovy</artifactId> - <version>${project.version}</version> - <classifier>project-src</classifier> - <type>zip</type> - </dependency> - <dependency> - <groupId>org.glassfish.jersey.examples</groupId> <artifactId>helloworld</artifactId> <version>${project.version}</version> <classifier>project-src</classifier> @@ -736,4 +729,22 @@ </plugins> </build> + <profiles> + <profile> + <id>groovy_jdk_11</id> + <activation> + <jdk>[11,)</jdk> + </activation> + <dependencies> + <dependency> + <groupId>org.glassfish.jersey.examples</groupId> + <artifactId>groovy</artifactId> + <version>${project.version}</version> + <classifier>project-src</classifier> + <type>zip</type> + </dependency> + </dependencies> + </profile> + </profiles> + </project>
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 cb96a92..cf8ae51 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; @@ -30,6 +31,7 @@ import org.glassfish.jersey.internal.inject.SupplierClassBinding; import org.glassfish.jersey.internal.inject.SupplierInstanceBinding; import org.glassfish.jersey.internal.util.collection.LazyValue; +import org.glassfish.jersey.internal.util.collection.Ref; import org.glassfish.jersey.internal.util.collection.Value; import org.glassfish.jersey.internal.util.collection.Values; import org.glassfish.jersey.process.internal.RequestScope; @@ -52,10 +54,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.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -74,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(); @@ -88,10 +90,11 @@ * @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 ThreadLocal<MultivaluedMap<DisposableSupplier, Object>> disposableSupplierObjects = + ThreadLocal.withInitial(() -> new MultivaluedHashMap<>()); private <T> List<InstanceContext<?>> _getSingletons(TYPE clazz) { List<InstanceContext<?>> si; @@ -102,7 +105,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) { synchronized (singletonInstances) { // check existing singleton with a qualifier already created by another thread io a meantime List<InstanceContext<?>> values = singletonInstances.get(clazz); @@ -115,19 +118,19 @@ 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; } } @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); } } } @@ -143,21 +146,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); } } } @@ -175,28 +179,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(); } } @@ -207,9 +261,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 { @@ -219,20 +283,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; @@ -411,12 +476,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); @@ -431,12 +491,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); @@ -535,7 +590,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 { @@ -567,20 +624,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); } } @@ -592,8 +656,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() { @@ -601,10 +665,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; } @@ -678,7 +742,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<>(); @@ -759,12 +823,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; } @@ -828,20 +888,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; } } @@ -901,28 +958,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; } } @@ -935,19 +991,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; } @@ -968,7 +1020,7 @@ return NonInjectionManager.this.getInstance(actualTypeArgument); } } - }); + }, instances); @Override public Object get() { @@ -991,13 +1043,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; } @@ -1006,6 +1061,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 @@ -1024,6 +1086,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 0b5b318..0a1c086 100644 --- a/core-common/pom.xml +++ b/core-common/pom.xml
@@ -846,6 +846,9 @@ <profile> <id>securityOff</id> + <activation> + <jdk>[24,)</jdk> + </activation> <properties> <surefire.security.argline /> </properties> @@ -854,12 +857,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 263ab48..171512f 100644 --- a/core-server/pom.xml +++ b/core-server/pom.xml
@@ -264,6 +264,9 @@ </profile> <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 3e75852..a12fae2 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 fc66fec..ea5cb87 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>[1.8,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/examples/pom.xml b/examples/pom.xml index 8d25b08..609875f 100644 --- a/examples/pom.xml +++ b/examples/pom.xml
@@ -70,7 +70,6 @@ <!--<module>feed-combiner-java8-webapp</module>--> <module>freemarker-webapp</module> <!--<module>flight-mgmt-webapp</module>--> - <module>groovy</module> <module>helloworld</module> <module>helloworld-benchmark</module> <module>helloworld-cdi2-se</module> @@ -278,9 +277,17 @@ </resource> </resources> </build> - <profiles> <profile> + <id>GROOVY-EXAMPLE</id> + <activation> + <jdk>[11,)</jdk> + </activation> + <modules> + <module>groovy</module> + </modules> + </profile> + <profile> <id>jdk17</id> <activation> <jdk>[17,)</jdk>
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 d6bafee..afbfda4 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>[1.8, 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/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 5bf50ee..bc1397e 100644 --- a/pom.xml +++ b/pom.xml
@@ -2167,7 +2167,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> @@ -2196,7 +2196,8 @@ <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> + <groovy.jdk8.version>4.0.24</groovy.jdk8.version> <gson.version>2.11.0</gson.version> <!--versions, extracted here due to maven-enforcer-plugin --> @@ -2236,7 +2237,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> <junit5.jdk8.version>5.10.3</junit5.jdk8.version> <junit-platform-suite.version>1.11.0</junit-platform-suite.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 f2f9cc0..21bbfd3 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> @@ -305,5 +304,44 @@ </pluginManagement> </build> </profile> + <profile> + <id>jdk_8</id> + <activation> + <jdk>1.8</jdk> + </activation> + <properties> + <groovy.version>${groovy.jdk8.version}</groovy.version> + </properties> + <dependencies> + <dependency> + <groupId>org.apache.groovy</groupId> + <artifactId>groovy-all</artifactId> + <type>pom</type> + <version>${groovy.version}</version> + <exclusions> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + </exclusion> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </exclusion> + <exclusion> + <groupId>org.junit.jupiter</groupId> + <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> + </exclusions> + </dependency> + </dependencies> + </profile> </profiles> </project>
diff --git a/tests/e2e-inject/non-inject/pom.xml b/tests/e2e-inject/non-inject/pom.xml new file mode 100644 index 0000000..f1f1082 --- /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.0.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 3568f84..35f74ec 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 3f2bf1a..8e15cb6 100644 --- a/tests/e2e-tls/pom.xml +++ b/tests/e2e-tls/pom.xml
@@ -164,6 +164,24 @@ </http.patch.addopens> </properties> </profile> + <profile> + <id>JDK_17-</id> + <activation> + <jdk>[1.8,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 c09534a..b323d26 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 99dc2d5..b25b08d 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> @@ -117,5 +116,14 @@ <module>version-agnostic</module> </modules> </profile> + <profile> + <id>JMOCKIT-MODULE-FOR-PRE-JDK24</id> + <activation> + <jdk>[1.8,24)</jdk> + </activation> + <modules> + <module>jmockit</module> + </modules> + </profile> </profiles> </project>