Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ServiceFinder defaults to ServiceLookupIteratorProvider #4999

Open
wants to merge 2 commits into
base: 3.1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ nb-configuration.xml
.settings/*
.project
.classpath
.factorypath

# Maven plugins noise
dependency-reduced-pom.xml
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -106,7 +106,7 @@ public static synchronized OsgiRegistry getInstance() {

private final class OsgiServiceFinder extends ServiceFinder.ServiceIteratorProvider {

final ServiceFinder.ServiceIteratorProvider defaultIterator = new ServiceFinder.DefaultServiceIteratorProvider();
final ServiceFinder.ServiceIteratorProvider defaultIterator = new ServiceFinder.ServiceReflectionIteratorProvider();

@Override
public <T> Iterator<T> createIterator(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -31,10 +31,12 @@
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import org.glassfish.jersey.internal.util.ReflectionHelper;

Expand Down Expand Up @@ -229,7 +231,9 @@ public static <T> ServiceFinder<T> find(final Class<T> service, final ClassLoade
* class loader (or, failing that the bootstrap class loader) is to
* be used
* @param ignoreOnClassNotFound If a provider cannot be loaded by the class loader
* then move on to the next available provider.
* then move on to the next available provider. This value does
* not apply when the {@link ServiceIteratorProvider} is set to
* {@link ServiceLookupIteratorProvider}.
* @throws ServiceConfigurationError If a provider-configuration file violates the specified format
* or names a provider class that cannot be found and instantiated
* @see #find(Class)
Expand Down Expand Up @@ -279,7 +283,10 @@ public static <T> ServiceFinder<T> find(final Class<T> service)
* </pre>
* @param service The service's abstract service class
* @param ignoreOnClassNotFound If a provider cannot be loaded by the class loader
* then move on to the next available provider.
* then move on to the next available provider. This value does
* not apply when the {@link ServiceIteratorProvider} is set to
* {@link ServiceLookupIteratorProvider}.
*
* @throws ServiceConfigurationError If a provider-configuration file violates the specified format
* or names a provider class that cannot be found and instantiated
* @see #find(Class, ClassLoader)
Expand Down Expand Up @@ -312,7 +319,7 @@ public static ServiceFinder<?> find(final String serviceName) throws ServiceConf
* Register the service iterator provider to iterate on provider instances
* or classes.
* <p>
* The default implementation registered, {@link DefaultServiceIteratorProvider},
* The default implementation registered, {@link ServiceLookupIteratorProvider},
* looks up provider classes in META-INF/service files.
* <p>
* This method must be called prior to any attempts to obtain provider
Expand Down Expand Up @@ -790,7 +797,7 @@ private void handleClassNotFoundException() throws ServiceConfigurationError {
* Supports iteration of provider instances or classes.
* <p>
* The default implementation looks up provider classes from META-INF/services
* files, see {@link DefaultServiceIteratorProvider}.
* files, see {@link ServiceLookupIteratorProvider}.
* This implementation may be overridden by invoking
* {@link ServiceFinder#setIteratorProvider(org.glassfish.jersey.internal.ServiceFinder.ServiceIteratorProvider)}.
*/
Expand All @@ -806,7 +813,7 @@ private static ServiceIteratorProvider getInstance() {
synchronized (sipLock) {
result = sip;
if (result == null) { // Second check (with locking)
sip = result = new DefaultServiceIteratorProvider();
sip = result = new ServiceLookupIteratorProvider();
}
}
}
Expand Down Expand Up @@ -834,7 +841,9 @@ private static void setInstance(final ServiceIteratorProvider sip) throws Securi
* classes.
* @param ignoreOnClassNotFound if true ignore an instance if the
* corresponding provider class if cannot be found,
* otherwise throw a {@link ClassNotFoundException}.
* otherwise throw a {@link ClassNotFoundException}. This value does
* not apply when the {@link ServiceIteratorProvider} is set to
* {@link ServiceLookupIteratorProvider}.
* @return the provider instance iterator.
*/
public abstract <T> Iterator<T> createIterator(Class<T> service,
Expand All @@ -851,6 +860,8 @@ public abstract <T> Iterator<T> createIterator(Class<T> service,
* @param ignoreOnClassNotFound if true ignore the provider class if
* cannot be found,
* otherwise throw a {@link ClassNotFoundException}.
* This value does not apply when the {@link ServiceIteratorProvider}
* is set to {@link ServiceLookupIteratorProvider}.
* @return the provider class iterator.
*/
public abstract <T> Iterator<Class<T>> createClassIterator(Class<T> service,
Expand All @@ -860,13 +871,10 @@ public abstract <T> Iterator<Class<T>> createClassIterator(Class<T> service,
}

/**
* The default service iterator provider that looks up provider classes in
* The service iterator provider that looks up provider classes in
* META-INF/services files.
* <p>
* This class may utilized if a {@link ServiceIteratorProvider} needs to
* reuse the default implementation.
*/
public static final class DefaultServiceIteratorProvider extends ServiceIteratorProvider {
public static final class ServiceReflectionIteratorProvider extends ServiceIteratorProvider {

@Override
public <T> Iterator<T> createIterator(final Class<T> service, final String serviceName,
Expand All @@ -880,4 +888,41 @@ public <T> Iterator<Class<T>> createClassIterator(final Class<T> service, final
return new LazyClassIterator<T>(service, serviceName, loader, ignoreOnClassNotFound);
}
}

/**
* The service iterator provider that looks up provider classes in
* META-INF/services files using {@link ServiceLoader}.
*/
public static final class ServiceLookupIteratorProvider extends ServiceIteratorProvider {

@Override
public <T> Iterator<T> createIterator(final Class<T> service, final String serviceName,
final ClassLoader loader, final boolean ignoreOnClassNotFound) {
Class<T> clazz = fixGenericService(service, serviceName, loader, ignoreOnClassNotFound);
return ServiceLoader.load(clazz, loader).iterator();
}

@Override
public <T> Iterator<Class<T>> createClassIterator(final Class<T> service, final String serviceName,
final ClassLoader loader, final boolean ignoreOnClassNotFound) {
Class<T> clazz = fixGenericService(service, serviceName, loader, ignoreOnClassNotFound);
List<Class<T>> classes = ServiceLoader.load(clazz, loader).stream()
.map(provider -> (Class<T>) provider.type())
.collect(Collectors.toList());
return classes.iterator();
}

private <T> Class<T> fixGenericService(final Class<T> service, final String serviceName,
final ClassLoader loader, final boolean ignoreOnClassNotFound) {
Class<T> clazz = service;
if (Object.class == service) {
try {
clazz = (Class<T>) ReflectionHelper.classForNameWithExceptionPEA(serviceName, loader).run();
} catch (Exception e) {
// Ignore it. Later, the service implementation will not be loaded.
}
}
return clazz;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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.server.internal;

public interface ServiceExample {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 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
Expand All @@ -16,29 +16,127 @@

package org.glassfish.jersey.server.internal;

import static org.glassfish.jersey.server.JarUtils.createJarFile;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import jakarta.ws.rs.container.DynamicFeature;
import jakarta.ws.rs.core.Configurable;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.glassfish.jersey.internal.ServiceFinder;
import org.glassfish.jersey.internal.ServiceFinder.ServiceIteratorProvider;
import org.glassfish.jersey.internal.ServiceFinder.ServiceLookupIteratorProvider;
import org.glassfish.jersey.internal.ServiceFinder.ServiceReflectionIteratorProvider;
import org.glassfish.jersey.server.JarUtils;

import org.glassfish.jersey.server.JaxRsFeatureRegistrationTest.DynamicFeatureImpl;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import static org.glassfish.jersey.server.JarUtils.createJarFile;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* @author Michal Gajdos
*/
public class ServiceFinderTest {

@AfterAll
public static void afterClass() {
// Restore the default
ServiceFinder.setIteratorProvider(null);
}

@Test
public void testExistingClass() {
ServiceIteratorProvider[] providers = new ServiceIteratorProvider[] {
new ServiceReflectionIteratorProvider(), new ServiceLookupIteratorProvider()};
for (ServiceIteratorProvider provider : providers) {
ServiceFinder.setIteratorProvider(provider);
ServiceFinder<?> serviceFinder = ServiceFinder.find(DynamicFeature.class);
checks(provider, serviceFinder);
serviceFinder = ServiceFinder.find("jakarta.ws.rs.container.DynamicFeature");
checks(provider, serviceFinder);
}
}

@Test
public void testMissingService() {
ServiceIteratorProvider[] providers = new ServiceIteratorProvider[] {
new ServiceReflectionIteratorProvider(), new ServiceLookupIteratorProvider()};
for (ServiceIteratorProvider provider : providers) {
ServiceFinder.setIteratorProvider(provider);
ServiceFinder<?> serviceFinder = ServiceFinder.find(Configurable.class);
assertFalse(serviceFinder.iterator().hasNext());
serviceFinder = ServiceFinder.find("jakarta.ws.rs.core.Configurable");
assertFalse(serviceFinder.iterator().hasNext());
}
}

@Test
public void testClassNotFound() {
ServiceIteratorProvider[] providers = new ServiceIteratorProvider[] {
new ServiceReflectionIteratorProvider(), new ServiceLookupIteratorProvider()};
for (ServiceIteratorProvider provider : providers) {
ServiceFinder.setIteratorProvider(provider);
ServiceFinder<?> serviceFinder = ServiceFinder.find("doesNotExist");
assertFalse(serviceFinder.iterator().hasNext());
}
}

@Test
public void testServiceReflectionIteratorProviderImplementationNotFound() {
ServiceFinder.setIteratorProvider(new ServiceReflectionIteratorProvider());
ServiceFinder<?> serviceFinder = ServiceFinder.find(ServiceExample.class, true);
assertFalse(serviceFinder.iterator().hasNext());
serviceFinder = ServiceFinder.find(ServiceExample.class, false);
try {
serviceFinder.iterator().hasNext();
fail("It is expected to fail");
} catch (org.glassfish.jersey.internal.ServiceConfigurationError e) {
// Expected
}
}

@Test
public void testServiceLookupIteratorProviderImplementationNotFound() {
ServiceFinder.setIteratorProvider(new ServiceLookupIteratorProvider());
ServiceFinder<?> serviceFinder = ServiceFinder.find(ServiceExample.class, true);
Iterator<?> iterator = serviceFinder.iterator();
try {
iterator.hasNext();
iterator.next();
fail("It is expected to fail");
} catch (java.util.ServiceConfigurationError e) {
// Expected
}
serviceFinder = ServiceFinder.find(ServiceExample.class, false);
iterator = serviceFinder.iterator();
try {
iterator.hasNext();
iterator.next();
fail("It is expected to fail");
} catch (java.util.ServiceConfigurationError e) {
// Expected
}
}

private void checks(ServiceIteratorProvider provider, ServiceFinder<?> serviceFinder) {
Iterator<?> iterator = serviceFinder.iterator();
assertTrue(iterator.hasNext(), "No instance found with " + provider);
Object dynamicFeature = iterator.next();
assertEquals(DynamicFeatureImpl.class, dynamicFeature.getClass());
}

@Test
public void testJarTopLevel() throws Exception {
final Map<String, String> map = new HashMap<>();
Expand All @@ -49,6 +147,7 @@ public void testJarTopLevel() throws Exception {
final String path = ServiceFinderTest.class.getResource("").getPath();
final ClassLoader classLoader = createClassLoader(path.substring(0, path.indexOf("org")), map);

ServiceFinder.setIteratorProvider(new ServiceReflectionIteratorProvider());
final ServiceFinder<?> finder = createServiceFinder(classLoader, "jaxrs-components");

final Set<Class<?>> s = new HashSet<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
unknown
Loading
Loading