Merge remote-tracking branch 'upstream/2.x' into 'upstream/3.0' Signed-off-by: Maxim Nesen <maxim.nesen@oracle.com>
diff --git a/core-common/src/main/java/org/glassfish/jersey/AbstractFeatureConfigurator.java b/core-common/src/main/java/org/glassfish/jersey/AbstractFeatureConfigurator.java index a73b185..36a3712 100644 --- a/core-common/src/main/java/org/glassfish/jersey/AbstractFeatureConfigurator.java +++ b/core-common/src/main/java/org/glassfish/jersey/AbstractFeatureConfigurator.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025 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 @@ -49,9 +49,11 @@ * @param loader specific classloader (must not be NULL) * @return list of found classes */ + protected List<Class<T>> loadImplementations(Map<String, Object> applicationProperties, ClassLoader loader) { if (PropertiesHelper.isMetaInfServicesEnabled(applicationProperties, getRuntimeType())) { - return Stream.of(ServiceFinder.find(getContract(), loader, true).toClassArray()) + return Stream.of(ServiceFinder.service(getContract()).loader(loader).ignoreNotFound(true) + .runtimeType(getRuntimeType()).find().toClassArray()) .collect(Collectors.toList()); } return Collections.emptyList();
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/AbstractServiceFinderConfigurator.java b/core-common/src/main/java/org/glassfish/jersey/internal/AbstractServiceFinderConfigurator.java index ea7c62c..4a4972b 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/AbstractServiceFinderConfigurator.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/AbstractServiceFinderConfigurator.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025 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 @@ -60,8 +60,8 @@ */ protected List<Class<T>> loadImplementations(Map<String, Object> applicationProperties) { if (PropertiesHelper.isMetaInfServicesEnabled(applicationProperties, runtimeType)) { - return Stream.of(ServiceFinder.find(contract, true).toClassArray()) - .collect(Collectors.toList()); + return Stream.of(ServiceFinder.service(contract).ignoreNotFound(true).runtimeType(runtimeType) + .find().toClassArray()).collect(Collectors.toList()); } return Collections.emptyList(); }
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateDecorator.java b/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateDecorator.java index 5f954ba..3b7bd24 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateDecorator.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateDecorator.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025 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 @@ -114,7 +114,8 @@ static { Set<HeaderDelegateProvider> hps = new HashSet<HeaderDelegateProvider>(); - for (HeaderDelegateProvider provider : ServiceFinder.find(HeaderDelegateProvider.class, true)) { + for (HeaderDelegateProvider provider : ServiceFinder + .service(HeaderDelegateProvider.class).ignoreNotFound(true).find()) { hps.add(provider); } headerDelegateProviders = Collections.unmodifiableSet(hps);
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinder.java b/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinder.java index 46184d8..5aa82a3 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinder.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinder.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -24,6 +24,7 @@ import java.lang.reflect.ReflectPermission; import java.net.URL; import java.net.URLConnection; +import java.nio.charset.StandardCharsets; import java.security.AccessController; import java.security.PrivilegedActionException; import java.util.ArrayList; @@ -38,6 +39,9 @@ import org.glassfish.jersey.internal.util.ReflectionHelper; +import jakarta.ws.rs.ConstrainedTo; +import jakarta.ws.rs.RuntimeType; + /** * A simple service-provider lookup mechanism. A <i>service</i> is a * well-known set of interfaces and (usually abstract) classes. A <i>service @@ -106,7 +110,7 @@ * sun.io.StandardCodec # Standard codecs for the platform * </pre> * <p/> - * To locate an codec for a given encoding name, the internal I/O code would + * To locate a codec for a given encoding name, the internal I/O code would * do something like this: * <p/> * <pre> @@ -133,10 +137,98 @@ private static final Logger LOGGER = Logger.getLogger(ServiceFinder.class.getName()); private static final String PREFIX = "META-INF/services/"; - private final Class<T> serviceClass; - private final String serviceName; - private final ClassLoader classLoader; - private final boolean ignoreOnClassNotFound; + + public static final class Builder<T> { + final Class<T> service; + String serviceName; + private ClassLoader loader; + private Boolean ignoreOnClassNotFound; + RuntimeType runtimeType = null; + + private Builder(Class<T> serviceClass) { + this.service = serviceClass; + } + + private Builder(Builder<T> builder) { + this.service = builder.service; + this.serviceName = builder.serviceName; + this.loader = builder.loader; + this.ignoreOnClassNotFound = builder.ignoreOnClassNotFound; + this.runtimeType = builder.runtimeType; + } + + /** + * Create the service finder capable of locating the services with information specified by the builder. + * @return the service finder instance. + */ + public ServiceFinder<T> find() { + if (serviceName == null) { + serviceName = service.getName(); + } + if (loader == null) { + loader = _getContextClassLoader(); + } + if (ignoreOnClassNotFound == null) { + ignoreOnClassNotFound = false; + } + + return new ServiceFinder<T>(this); + } + + /** + * Set the service name the service finder use to locate the services. + * @param serviceName the service name correspond to a file in + * META-INF/services that contains a list of fully qualified class + * names. + * @return the updated builder. + */ + public Builder<T> serviceName(String serviceName) { + this.serviceName = serviceName; + return this; + } + + /** + * Set the service finder to use the given {@code Classloader}. By default, the context classloader is used. + * @param loader the given classloader for the service finder to use when searching for the service. + * @return the updated builder. + */ + public Builder<T> loader(ClassLoader loader) { + this.loader = loader; + return this; + } + + /** + * Set the service finder to ignore the service if not found. The default is {@code false}. + * @param ignoreOnClassNotFound whether to ignore the service not found or not. + * @return the updated builder. + */ + public Builder<T> ignoreNotFound(boolean ignoreOnClassNotFound) { + this.ignoreOnClassNotFound = ignoreOnClassNotFound; + return this; + } + + /** + * Update the builder with a specified runtime type the searched services are constrained to it. + * @param runtimeType the specified runtime type. + * @return the updated builder. + */ + public Builder<T> runtimeType(RuntimeType runtimeType) { + this.runtimeType = runtimeType; + return this; + } + } + + /** + * Start configuring {@link Builder} with a specific service class to find. + * @param serviceClass the service class to find. + * @return a new instance of service finder builder. + * @param <T> type of the service class the service finder builder is created for. + */ + public static <T> ServiceFinder.Builder<T> service(Class<T> serviceClass) { + return new Builder<>(serviceClass); + } + + private final Builder<T> builder; static { final OsgiRegistry osgiRegistry = ReflectionHelper.getOsgiRegistryInstance(); @@ -201,11 +293,10 @@ * @param <T> the type of the service instance. * @return the service finder */ + @Deprecated public static <T> ServiceFinder<T> find(final Class<T> service, final ClassLoader loader) throws ServiceConfigurationError { - return find(service, - loader, - false); + return service(service).loader(loader).find(); } /** @@ -236,12 +327,11 @@ * @param <T> the type of the service instance. * @return the service finder */ + @Deprecated public static <T> ServiceFinder<T> find(final Class<T> service, final ClassLoader loader, final boolean ignoreOnClassNotFound) throws ServiceConfigurationError { - return new ServiceFinder<T>(service, - loader, - ignoreOnClassNotFound); + return service(service).loader(loader).ignoreNotFound(ignoreOnClassNotFound).find(); } /** @@ -251,7 +341,7 @@ * <p/> * <pre> * ClassLoader cl = Thread.currentThread().getContextClassLoader(); - * return Service.providers(service, cl, false); + * return ServiceFinder.service(service).loader(cl).ignoreNotFound(false).find(); * </pre> * @param service The service's abstract service class * @throws ServiceConfigurationError If a provider-configuration file violates the specified format @@ -262,9 +352,7 @@ */ public static <T> ServiceFinder<T> find(final Class<T> service) throws ServiceConfigurationError { - return find(service, - _getContextClassLoader(), - false); + return ServiceFinder.service(service).find(); } /** @@ -275,7 +363,7 @@ * <pre> * ClassLoader cl = Thread.currentThread().getContextClassLoader(); * boolean ingore = ... - * return Service.providers(service, cl, ignore); + * return ServiceFinder.service(service).loader(cl).ignoreNotFound(ignore).find(); * </pre> * @param service The service's abstract service class * @param ignoreOnClassNotFound If a provider cannot be loaded by the class loader @@ -286,11 +374,10 @@ * @param <T> the type of the service instance. * @return the service finder */ + @Deprecated public static <T> ServiceFinder<T> find(final Class<T> service, final boolean ignoreOnClassNotFound) throws ServiceConfigurationError { - return find(service, - _getContextClassLoader(), - ignoreOnClassNotFound); + return ServiceFinder.service(service).ignoreNotFound(ignoreOnClassNotFound).find(); } /** @@ -305,7 +392,7 @@ * @return the service finder */ public static ServiceFinder<?> find(final String serviceName) throws ServiceConfigurationError { - return new ServiceFinder<Object>(Object.class, serviceName, _getContextClassLoader(), false); + return service(Object.class).serviceName(serviceName).find(); } /** @@ -325,22 +412,8 @@ ServiceIteratorProvider.setInstance(sip); } - private ServiceFinder( - final Class<T> service, - final ClassLoader loader, - final boolean ignoreOnClassNotFound) { - this(service, service.getName(), loader, ignoreOnClassNotFound); - } - - private ServiceFinder( - final Class<T> service, - final String serviceName, - final ClassLoader loader, - final boolean ignoreOnClassNotFound) { - this.serviceClass = service; - this.serviceName = serviceName; - this.classLoader = loader; - this.ignoreOnClassNotFound = ignoreOnClassNotFound; + private ServiceFinder(Builder<T> builder) { + this.builder = new Builder<>(builder); } /** @@ -354,8 +427,7 @@ */ @Override public Iterator<T> iterator() { - return ServiceIteratorProvider.getInstance() - .createIterator(serviceClass, serviceName, classLoader, ignoreOnClassNotFound); + return createIterator(); } /** @@ -373,7 +445,7 @@ for (final T t : this) { result.add(t); } - return result.toArray((T[]) Array.newInstance(serviceClass, result.size())); + return result.toArray((T[]) Array.newInstance(builder.service, result.size())); } /** @@ -388,16 +460,68 @@ @SuppressWarnings("unchecked") public Class<T>[] toClassArray() throws ServiceConfigurationError { final List<Class<T>> result = new ArrayList<Class<T>>(); - - final ServiceIteratorProvider iteratorProvider = ServiceIteratorProvider.getInstance(); - final Iterator<Class<T>> i = iteratorProvider - .createClassIterator(serviceClass, serviceName, classLoader, ignoreOnClassNotFound); + final Iterator<Class<T>> i = createClassIterator(); while (i.hasNext()) { result.add(i.next()); } return result.toArray((Class<T>[]) Array.newInstance(Class.class, result.size())); } + /** + * Return true iff the service class is not constrained to other runtime type. + * @param clazz the service class. + * @param runtimeType the expected constraint runtime type. + * @return {@code true} when the service class is constrained to configurator's runtime type or {@code false} otherwise. + */ + private static boolean isConstrained(Class<?> clazz, RuntimeType runtimeType) { + final ConstrainedTo annotation = clazz.getAnnotation(ConstrainedTo.class); + return annotation == null || annotation.value() == runtimeType; + } + + private Iterator<Class<T>> createClassIterator() { + final Iterator<Class<T>> it = ServiceIteratorProvider.getInstance().createClassIterator( + builder.service, builder.serviceName, builder.loader, builder.ignoreOnClassNotFound); + return builder.runtimeType == null ? it : new ConstrainedIterator<Class<T>>(it, builder.runtimeType); + } + + private Iterator<T> createIterator() { + final Iterator<T> it = ServiceIteratorProvider.getInstance().createIterator( + builder.service, builder.serviceName, builder.loader, builder.ignoreOnClassNotFound); + return builder.runtimeType == null ? it : new ConstrainedIterator<T>(it, builder.runtimeType); + } + + private static final class ConstrainedIterator<IT> implements Iterator<IT> { + private final Iterator<IT> i; + private final RuntimeType runtimeType; + private IT next; + + private ConstrainedIterator(Iterator<IT> i, RuntimeType runtimeType) { + this.i = i; + this.runtimeType = runtimeType; + } + + @Override + public boolean hasNext() { + while (next == null && i.hasNext()) { + next = i.next(); + if (!isConstrained(next.getClass() == Class.class ? (Class<?>) next : next.getClass(), runtimeType)) { + next = null; + } + } + return next != null; + } + + @Override + public IT next() { + if (next == null && !hasNext()) { + throw new NoSuchElementException(); + } + final IT n = next; + next = null; + return n; + } + } + private static void fail(final String serviceName, final String msg, final Throwable cause) throws ServiceConfigurationError { final ServiceConfigurationError sce = new ServiceConfigurationError(serviceName + ": " + msg); @@ -480,7 +604,7 @@ final URLConnection uConn = u.openConnection(); uConn.setUseCaches(false); in = uConn.getInputStream(); - r = new BufferedReader(new InputStreamReader(in, "utf-8")); + r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); int lc = 1; while ((lc = parseLine(serviceName, u, r, lc, names, returned)) >= 0) { // continue @@ -863,7 +987,7 @@ * The default service iterator provider that looks up provider classes in * META-INF/services files. * <p> - * This class may utilized if a {@link ServiceIteratorProvider} needs to + * This class may be utilized if a {@link ServiceIteratorProvider} needs to * reuse the default implementation. */ public static final class DefaultServiceIteratorProvider extends ServiceIteratorProvider {
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinderBinder.java b/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinderBinder.java index 223d32b..670562d 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinderBinder.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinderBinder.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025 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 @@ -58,7 +58,8 @@ @Override protected void configure() { if (PropertiesHelper.isMetaInfServicesEnabled(applicationProperties, runtimeType)) { - for (Class<T> t : ServiceFinder.find(contract, true).toClassArray()) { + for (Class<T> t : ServiceFinder.service(contract).ignoreNotFound(true).runtimeType(runtimeType).find() + .toClassArray()) { bind(t).to(contract); } }
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/inject/Injections.java b/core-common/src/main/java/org/glassfish/jersey/internal/inject/Injections.java index 1750dd8..9444e9c 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/inject/Injections.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/inject/Injections.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025 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 @@ -93,16 +93,12 @@ * @param clazz type of service to look for. * @param <T> type of service to look for. * @param type {@link RuntimeType} the {@link InjectionManagerFactory} must be {@link ConstrainedTo} if annotated. - * @return instance of service with highest priority or {@code null} if service of given type cannot be found. + * @return instance of service with the highest priority or {@code null} if service of given type cannot be found. * @see jakarta.annotation.Priority */ private static <T> Optional<T> lookupService(final Class<T> clazz, RuntimeType type) { List<RankedProvider<T>> providers = new LinkedList<>(); - for (T provider : ServiceFinder.find(clazz)) { - ConstrainedTo constrain = provider.getClass().getAnnotation(ConstrainedTo.class); - if (constrain != null && type != constrain.value()) { - continue; - } + for (T provider : ServiceFinder.service(clazz).runtimeType(type).find()) { providers.add(new RankedProvider<>(provider)); } providers.sort(new RankedComparator<>(RankedComparator.Order.DESCENDING));
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/inject/Providers.java b/core-common/src/main/java/org/glassfish/jersey/internal/inject/Providers.java index f5c467a..e0aa5bf 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/inject/Providers.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/inject/Providers.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025 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 @@ -107,7 +107,8 @@ interfaces.put(Binder.class, ProviderRuntime.BOTH); try { - ServiceFinder<ExternalRegistrables> registerables = ServiceFinder.find(ExternalRegistrables.class, true); + ServiceFinder<ExternalRegistrables> registerables = + ServiceFinder.service(ExternalRegistrables.class).ignoreNotFound(true).find(); registerables.forEach(regs -> regs.registrableContracts() .forEach(pair -> interfaces.put(pair.getContract(), ProviderRuntime.fromRuntimeType(pair.getRuntimeType())))); } catch (Throwable t) {
diff --git a/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java b/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java index 1afa8c1..41d0868 100644 --- a/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java +++ b/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025 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 @@ -596,8 +596,8 @@ // Forced (always invoked). final List<ForcedAutoDiscoverable> forcedAutoDiscroverables = new LinkedList<>(); - for (Class<ForcedAutoDiscoverable> forcedADType : ServiceFinder.find(ForcedAutoDiscoverable.class, true) - .toClassArray()) { + for (Class<ForcedAutoDiscoverable> forcedADType : ServiceFinder.service(ForcedAutoDiscoverable.class) + .ignoreNotFound(true).runtimeType(getRuntimeType()).find().toClassArray()) { forcedAutoDiscroverables.add(injectionManager.createAndInitialize(forcedADType)); } providers.addAll(forcedAutoDiscroverables); @@ -608,15 +608,11 @@ } for (final AutoDiscoverable autoDiscoverable : providers) { - final ConstrainedTo constrainedTo = autoDiscoverable.getClass().getAnnotation(ConstrainedTo.class); - - if (constrainedTo == null || type.equals(constrainedTo.value())) { - try { - autoDiscoverable.configure(this); - } catch (final Exception e) { - LOGGER.log(Level.FINE, - LocalizationMessages.AUTODISCOVERABLE_CONFIGURATION_FAILED(autoDiscoverable.getClass()), e); - } + try { + autoDiscoverable.configure(this); + } catch (final Exception e) { + LOGGER.log(Level.FINE, + LocalizationMessages.AUTODISCOVERABLE_CONFIGURATION_FAILED(autoDiscoverable.getClass()), e); } } }
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ApplicationHandler.java b/core-server/src/main/java/org/glassfish/jersey/server/ApplicationHandler.java index 42d11dd..10e0d81 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/ApplicationHandler.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/ApplicationHandler.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 Payara Foundation and/or its affiliates. * * This program and the accompanying materials are made available under the @@ -366,7 +366,7 @@ if (!disableValidation()) { ComponentModelValidator validator = new ComponentModelValidator( - bootstrapBag.getValueParamProviders(), bootstrapBag.getMessageBodyWorkers()); + bootstrapBag.getValueParamProviders(), bootstrapBag.getMessageBodyWorkers(), runtimeConfig); validator.validate(bootstrapBag.getResourceModel()); }
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ExternalRequestScopeConfigurator.java b/core-server/src/main/java/org/glassfish/jersey/server/ExternalRequestScopeConfigurator.java index d3d67d2..7619fcf 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/ExternalRequestScopeConfigurator.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/ExternalRequestScopeConfigurator.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025 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 @@ -43,7 +43,8 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { ServerBootstrapBag serverBag = (ServerBootstrapBag) bootstrapBag; - Class<ExternalRequestScope>[] extScopes = ServiceFinder.find(ExternalRequestScope.class, true).toClassArray(); + Class<ExternalRequestScope>[] extScopes = + ServiceFinder.service(ExternalRequestScope.class).ignoreNotFound(true).find().toClassArray(); boolean extScopeBound = false; if (extScopes.length == 1) {
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java index 6414204..47c784e 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025 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 @@ -462,6 +462,23 @@ "jersey.config.server.resource.validation.ignoreErrors"; /** + * If {@code true} then validation of application resource models will not log a warning when a get resource method consumes an entity. + * + * This impacts both the validation of root resources during deployment as well as validation of any sub resources + * returned from sub-resource locators. + * <p> + * The default value is {@code false}. + * </p> + * <p> + * The name of the configuration property is <tt>{@value}</tt>. + * </p> + * + * @see #RESOURCE_VALIDATION_DISABLE + */ + public static final String RESOURCE_VALIDATION_IGNORE_GET_CONSUMES_ENTITY_WARNINGS = + "jersey.config.server.resource.validation.ignoreGetConsumesEntityWarnings"; + + /** * If {@code true} then application monitoring will be enabled. * * This will enable the possibility
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/routing/RuntimeLocatorModelBuilder.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/routing/RuntimeLocatorModelBuilder.java index aa5c0f3..23b926f 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/routing/RuntimeLocatorModelBuilder.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/routing/RuntimeLocatorModelBuilder.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025 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 @@ -228,7 +228,7 @@ Errors.process(new Runnable() { @Override public void run() { - final ComponentModelValidator validator = new ComponentModelValidator(valueSuppliers, messageBodyWorkers); + final ComponentModelValidator validator = new ComponentModelValidator(valueSuppliers, messageBodyWorkers, config); validator.validate(component); if (Errors.fatalIssuesFound() && !ignoreValidationErrors) {
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/model/ComponentModelValidator.java b/core-server/src/main/java/org/glassfish/jersey/server/model/ComponentModelValidator.java index b93b172..5cd6878 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/model/ComponentModelValidator.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/model/ComponentModelValidator.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -26,6 +26,8 @@ import org.glassfish.jersey.server.model.internal.ModelErrors; import org.glassfish.jersey.server.spi.internal.ValueParamProvider; +import jakarta.ws.rs.core.Configuration; + /** * A resource model validator that checks the given resource model. * @@ -57,10 +59,16 @@ private final List<ResourceModelIssue> issueList = new LinkedList<>(); public ComponentModelValidator(Collection<ValueParamProvider> valueParamProviders, MessageBodyWorkers msgBodyWorkers) { + this(valueParamProviders, msgBodyWorkers, null); + } + + public ComponentModelValidator(Collection<ValueParamProvider> valueParamProviders, + MessageBodyWorkers msgBodyWorkers, + Configuration configuration) { validators = new ArrayList<>(); validators.add(new ResourceValidator()); validators.add(new RuntimeResourceModelValidator(msgBodyWorkers)); - validators.add(new ResourceMethodValidator(valueParamProviders)); + validators.add(new ResourceMethodValidator(valueParamProviders, configuration)); validators.add(new InvocableValidator()); }
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodValidator.java b/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodValidator.java index ac0c1da..2ab0707 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodValidator.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodValidator.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025 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 @@ -37,10 +37,11 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.sse.SseEventSink; +import jakarta.ws.rs.core.Configuration; import org.glassfish.jersey.internal.Errors; import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ServerProperties; import org.glassfish.jersey.server.internal.LocalizationMessages; import org.glassfish.jersey.server.model.internal.SseTypeResolver; import org.glassfish.jersey.server.spi.internal.ParameterValueHelper; @@ -55,9 +56,15 @@ class ResourceMethodValidator extends AbstractResourceModelVisitor { private final Collection<ValueParamProvider> valueParamProviders; + private final Configuration configuration; ResourceMethodValidator(Collection<ValueParamProvider> valueParamProviders) { + this(valueParamProviders, null); + } + + ResourceMethodValidator(Collection<ValueParamProvider> valueParamProviders, Configuration configuration) { this.valueParamProviders = valueParamProviders; + this.configuration = configuration; } @Override @@ -100,7 +107,7 @@ } // ensure GET does not consume an entity parameter, if not inflector-based - if (invocable.requiresEntity() && !invocable.isInflector()) { + if (invocable.requiresEntity() && !invocable.isInflector() && !shouldIgnoreGetConsumesEntityWarnings(configuration)) { Errors.warning(method, LocalizationMessages.GET_CONSUMES_ENTITY(invocable.getHandlingMethod())); } // ensure GET does not consume any @FormParam annotated parameter @@ -154,6 +161,16 @@ } } + private static Boolean shouldIgnoreGetConsumesEntityWarnings(Configuration configuration) { + if (configuration == null) { + return Boolean.FALSE; + } + return ServerProperties.getValue(configuration.getProperties(), + ServerProperties.RESOURCE_VALIDATION_IGNORE_GET_CONSUMES_ENTITY_WARNINGS, + Boolean.FALSE, + Boolean.class); + } + private void checkUnexpectedAnnotations(ResourceMethod resourceMethod) { Invocable invocable = resourceMethod.getInvocable(); for (Annotation annotation : invocable.getHandlingMethod().getDeclaredAnnotations()) {
diff --git a/inject/hk2/src/main/java/org/glassfish/jersey/inject/hk2/JerseyClassAnalyzer.java b/inject/hk2/src/main/java/org/glassfish/jersey/inject/hk2/JerseyClassAnalyzer.java index bceaeda..d9963b5 100644 --- a/inject/hk2/src/main/java/org/glassfish/jersey/inject/hk2/JerseyClassAnalyzer.java +++ b/inject/hk2/src/main/java/org/glassfish/jersey/inject/hk2/JerseyClassAnalyzer.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025 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 @@ -25,7 +25,6 @@ import java.security.PrivilegedAction; import java.util.List; import java.util.Set; -import java.util.function.Supplier; import jakarta.inject.Inject; import jakarta.inject.Named; @@ -35,9 +34,6 @@ import org.glassfish.jersey.internal.LocalizationMessages; import org.glassfish.jersey.internal.inject.InjectionResolver; import org.glassfish.jersey.internal.util.collection.ImmutableCollectors; -import org.glassfish.jersey.internal.util.collection.LazyValue; -import org.glassfish.jersey.internal.util.collection.Value; -import org.glassfish.jersey.internal.util.collection.Values; import org.glassfish.hk2.api.ClassAnalyzer; import org.glassfish.hk2.api.MultiException; @@ -79,33 +75,31 @@ @Override protected void configure() { - ClassAnalyzer defaultAnalyzer = - serviceLocator.getService(ClassAnalyzer.class, ClassAnalyzer.DEFAULT_IMPLEMENTATION_NAME); - - Supplier<List<InjectionResolver>> resolvers = () -> serviceLocator.getAllServices(InjectionResolver.class); - - bind(new JerseyClassAnalyzer(defaultAnalyzer, resolvers)) + bind(JerseyClassAnalyzer.class) .analyzeWith(ClassAnalyzer.DEFAULT_IMPLEMENTATION_NAME) .named(JerseyClassAnalyzer.NAME) .to(ClassAnalyzer.class); } } + private final Set<Class> resolverAnnotations; private final ClassAnalyzer defaultAnalyzer; - private final LazyValue<Set<Class>> resolverAnnotations; + /** * Injection constructor. * - * @param defaultAnalyzer default HK2 class analyzer. - * @param supplierResolvers configured injection resolvers. + * @param serviceLocator current injection manager. */ - private JerseyClassAnalyzer(ClassAnalyzer defaultAnalyzer, Supplier<List<InjectionResolver>> supplierResolvers) { - this.defaultAnalyzer = defaultAnalyzer; - Value<Set<Class>> resolvers = () -> supplierResolvers.get().stream() + @Inject + public JerseyClassAnalyzer(ServiceLocator serviceLocator) { + defaultAnalyzer = serviceLocator.getService(ClassAnalyzer.class, ClassAnalyzer.DEFAULT_IMPLEMENTATION_NAME); + // Load the resolver annotations once to avoid potential deadlock later + // See https://github.com/eclipse-ee4j/jersey/issues/5996 + List<InjectionResolver> resolvers = serviceLocator.getAllServices(InjectionResolver.class); + this.resolverAnnotations = resolvers.stream() .filter(InjectionResolver::isConstructorParameterIndicator) .map(InjectionResolver::getAnnotation) .collect(ImmutableCollectors.toImmutableSet()); - this.resolverAnnotations = Values.lazy(resolvers); } @SuppressWarnings("unchecked") @@ -186,7 +180,7 @@ final int paramSize = constructor.getParameterTypes().length; - if (paramSize != 0 && resolverAnnotations.get().isEmpty()) { + if (paramSize != 0 && resolverAnnotations.isEmpty()) { return false; } @@ -200,7 +194,7 @@ for (final Annotation[] paramAnnotations : constructor.getParameterAnnotations()) { boolean found = false; for (final Annotation paramAnnotation : paramAnnotations) { - if (resolverAnnotations.get().contains(paramAnnotation.annotationType())) { + if (resolverAnnotations.contains(paramAnnotation.annotationType())) { found = true; break; }
diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/common/AutoDiscoverableTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/common/AutoDiscoverableTest.java index 3dea887..476c255 100644 --- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/common/AutoDiscoverableTest.java +++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/common/AutoDiscoverableTest.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025 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 @@ -17,6 +17,9 @@ package org.glassfish.jersey.tests.e2e.common; import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; import jakarta.ws.rs.ConstrainedTo; import jakarta.ws.rs.POST; @@ -31,13 +34,20 @@ import jakarta.ws.rs.ext.WriterInterceptorContext; import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.internal.AutoDiscoverableConfigurator; +import org.glassfish.jersey.internal.BootstrapBag; +import org.glassfish.jersey.internal.ServiceFinder; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.internal.inject.Injections; import org.glassfish.jersey.internal.spi.AutoDiscoverable; import org.glassfish.jersey.internal.util.PropertiesHelper; +import org.glassfish.jersey.model.internal.CommonConfig; +import org.glassfish.jersey.model.internal.ComponentBag; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; /** * Note: Auto-discoverables from this test "affects" all other tests in suit. @@ -133,6 +143,84 @@ public void testAutoDiscoverableConstrainedTo() throws Exception { final Response response = target().request().post(Entity.text("value")); - assertEquals("value-common-client-common-server", response.readEntity(String.class)); + Assertions.assertEquals("value-common-client-common-server", response.readEntity(String.class)); + } + + @Test + public void testServiceFinderIterator() { + Class<AutoDiscoverable>[] array = + ServiceFinder.service(AutoDiscoverable.class).runtimeType(RuntimeType.SERVER).find().toClassArray(); + int size = array.length; + + Assertions.assertTrue(size > 3); + + ServiceFinder<AutoDiscoverable> finder = + ServiceFinder.service(AutoDiscoverable.class).runtimeType(RuntimeType.SERVER).find(); + AutoDiscoverable next = null; + // check next() + final Iterator<AutoDiscoverable> it = finder.iterator(); + for (int i = 0; i != size; i++) { + AutoDiscoverable n = it.next(); + Assertions.assertNotSame(next, n); + next = n; + } + Assertions.assertThrows(NoSuchElementException.class, it::next); + + // check hasNext(); + final Iterator<AutoDiscoverable> it2 = finder.iterator(); + next = null; + for (int i = 0; i != size; i++) { + for (int j = 0; j != size + 1; j++) { + Assertions.assertTrue(it2.hasNext()); + } + AutoDiscoverable n = it2.next(); + Assertions.assertNotSame(next, n); + next = n; + } + Assertions.assertFalse(it2.hasNext()); + Assertions.assertThrows(NoSuchElementException.class, it2::next); + } + + @Test + public void testAutoDiscoverableConstrainedConfigurator() { + Class<?>[] array = ServiceFinder.find(AutoDiscoverable.class).toClassArray(); + Assertions.assertTrue(contains(ClientAutoDiscoverable.class, array)); + Assertions.assertTrue(contains(CommonAutoDiscoverable.class, array)); + Assertions.assertTrue(contains(ServerAutoDiscoverable.class, array)); + + array = ServiceFinder.service(AutoDiscoverable.class).find().toClassArray(); + Assertions.assertTrue(contains(ClientAutoDiscoverable.class, array)); + Assertions.assertTrue(contains(CommonAutoDiscoverable.class, array)); + Assertions.assertTrue(contains(ServerAutoDiscoverable.class, array)); + + array = ServiceFinder.service(AutoDiscoverable.class).runtimeType(RuntimeType.SERVER).find().toClassArray(); + Assertions.assertFalse(contains(ClientAutoDiscoverable.class, array)); + Assertions.assertTrue(contains(CommonAutoDiscoverable.class, array)); + Assertions.assertTrue(contains(ServerAutoDiscoverable.class, array)); + + array = ServiceFinder.service(AutoDiscoverable.class).runtimeType(RuntimeType.CLIENT).find().toClassArray(); + Assertions.assertTrue(contains(ClientAutoDiscoverable.class, array)); + Assertions.assertTrue(contains(CommonAutoDiscoverable.class, array)); + Assertions.assertFalse(contains(ServerAutoDiscoverable.class, array)); + + AutoDiscoverableConfigurator configurator = new AutoDiscoverableConfigurator(RuntimeType.SERVER); + InjectionManager injectionManager = Injections.createInjectionManager(); + BootstrapBag bb = new BootstrapBag(); + bb.setConfiguration(new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL)); + configurator.init(injectionManager, bb); + array = bb.getAutoDiscoverables().stream().map(ad -> ad.getClass()).collect(Collectors.toList()) + .toArray(new Class[0]); + Assertions.assertFalse(contains(ClientAutoDiscoverable.class, array)); + Assertions.assertTrue(contains(CommonAutoDiscoverable.class, array)); + Assertions.assertTrue(contains(ServerAutoDiscoverable.class, array)); + } + + private static boolean contains(Class<?> clazz, Class<?>... list) { + for (Class<?> listClass : list) { + if (listClass.equals(clazz)) { + return true; + } + } + return false; } }