Skip to content

Commit

Permalink
Merge branch 'master' into iostreams
Browse files Browse the repository at this point in the history
  • Loading branch information
lhazlewood committed Sep 18, 2023
2 parents 3654934 + 7805e08 commit 5432dcd
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 22 deletions.
68 changes: 46 additions & 22 deletions impl/src/main/java/io/jsonwebtoken/impl/lang/Services.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.util.Collections;
import java.util.List;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static io.jsonwebtoken.lang.Collections.arrayToList;

Expand All @@ -30,6 +32,8 @@
*/
public final class Services {

private static ConcurrentMap<Class<?>, ServiceLoader<?>> SERVICE_CACHE = new ConcurrentHashMap<>();

private static final List<ClassLoaderAccessor> CLASS_LOADER_ACCESSORS = arrayToList(new ClassLoaderAccessor[] {
new ClassLoaderAccessor() {
@Override
Expand Down Expand Up @@ -64,25 +68,19 @@ private Services() {}
public static <T> List<T> loadAll(Class<T> spi) {
Assert.notNull(spi, "Parameter 'spi' must not be null.");

for (ClassLoaderAccessor classLoaderAccessor : CLASS_LOADER_ACCESSORS) {
List<T> implementations = loadAll(spi, classLoaderAccessor.getClassLoader());
if (!implementations.isEmpty()) {
return Collections.unmodifiableList(implementations);
ServiceLoader<T> serviceLoader = serviceLoader(spi);
if (serviceLoader != null) {

List<T> implementations = new ArrayList<>();
for (T implementation : serviceLoader) {
implementations.add(implementation);
}
return implementations;
}

throw new UnavailableImplementationException(spi);
}

private static <T> List<T> loadAll(Class<T> spi, ClassLoader classLoader) {
ServiceLoader<T> serviceLoader = ServiceLoader.load(spi, classLoader);
List<T> implementations = new ArrayList<>();
for (T implementation : serviceLoader) {
implementations.add(implementation);
}
return implementations;
}

/**
* Loads the first available implementation the given SPI class from the classpath. Uses the {@link ServiceLoader}
* to find implementations. When multiple implementations are available it will return the first one that it
Expand All @@ -96,23 +94,49 @@ private static <T> List<T> loadAll(Class<T> spi, ClassLoader classLoader) {
public static <T> T loadFirst(Class<T> spi) {
Assert.notNull(spi, "Parameter 'spi' must not be null.");

for (ClassLoaderAccessor classLoaderAccessor : CLASS_LOADER_ACCESSORS) {
T result = loadFirst(spi, classLoaderAccessor.getClassLoader());
if (result != null) {
return result;
}
ServiceLoader<T> serviceLoader = serviceLoader(spi);
if (serviceLoader != null) {
return serviceLoader.iterator().next();
}

throw new UnavailableImplementationException(spi);
}

private static <T> T loadFirst(Class<T> spi, ClassLoader classLoader) {
ServiceLoader<T> serviceLoader = ServiceLoader.load(spi, classLoader);
if (serviceLoader.iterator().hasNext()) {
return serviceLoader.iterator().next();
/**
* Returns a ServiceLoader for <code>spi</code> class, checking multiple classloaders. The ServiceLoader
* will be cached if it contains at least one implementation of the <code>spi</code> class.<BR>
*
* <b>NOTE:</b> Only the first Serviceloader will be cached.
* @param spi The interface or abstract class representing the service loader.
* @return A service loader, or null if no implementations are found
* @param <T> The type of the SPI.
*/
private static <T> ServiceLoader<T> serviceLoader(Class<T> spi) {
// TODO: JDK8, replace this get/putIfAbsent logic with ConcurrentMap.computeIfAbsent
ServiceLoader<T> serviceLoader = (ServiceLoader<T>) SERVICE_CACHE.get(spi);
if (serviceLoader != null) {
return serviceLoader;
}

for (ClassLoaderAccessor classLoaderAccessor : CLASS_LOADER_ACCESSORS) {
serviceLoader = ServiceLoader.load(spi, classLoaderAccessor.getClassLoader());
if (serviceLoader.iterator().hasNext()) {
SERVICE_CACHE.putIfAbsent(spi, serviceLoader);
return serviceLoader;
}
}

return null;
}

/**
* Clears internal cache of ServiceLoaders. This is useful when testing, or for applications that dynamically
* change classloaders.
*/
public static void reload() {
SERVICE_CACHE.clear();
}

private interface ClassLoaderAccessor {
ClassLoader getClassLoader();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package io.jsonwebtoken.impl.lang

import io.jsonwebtoken.StubService
import io.jsonwebtoken.impl.DefaultStubService
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.api.easymock.PowerMock
Expand Down Expand Up @@ -73,6 +74,11 @@ class ServicesTest {
assertEquals(ClassLoader.getSystemClassLoader(), accessorList.get(2).getClassLoader())
}

@After
void resetCache() {
Services.reload();
}

static class NoServicesClassLoader extends ClassLoader {
private NoServicesClassLoader(ClassLoader parent) {
super(parent)
Expand Down

0 comments on commit 5432dcd

Please sign in to comment.