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

Jakarta Activation erroneously assumes that classes can be loaded from Thread#getContextClassLoader #145

Merged
merged 6 commits into from
Feb 14, 2024
166 changes: 102 additions & 64 deletions api/src/main/java/jakarta/activation/FactoryFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -26,47 +27,41 @@ class FactoryFinder {
new ServiceLoaderUtil.ExceptionHandler<RuntimeException>() {
@Override
public RuntimeException createException(Throwable throwable, String message) {
return new RuntimeException(message, throwable);
return new IllegalStateException(message, throwable);
}
};

/**
* Finds the implementation {@code Class} object for the given
* factory type. If it fails and {@code tryFallback} is {@code true}
* finds the {@code Class} object for the given default class name.
* The arguments supplied must be used in order
* Note the default class name may be needed even if fallback
* is not to be attempted in order to check if requested type is fallback.
* factory type.
* <P>
* This method is package private so that this code can be shared.
*
* @param factoryClass factory abstract class or interface to be found
* @param defaultClassName the implementation class name, which is
* to be used only if nothing else
* is found; {@code null} to indicate
* that there is no default class name
* @param tryFallback whether to try the default class as a
* fallback
* @return the {@code Class} object of the specified message factory;
* may not be {@code null}
* @throws RuntimeException if there is no factory found
*/
static <T> T find(Class<T> factoryClass,
String defaultClassName,
boolean tryFallback) throws RuntimeException {
static <T> T find(Class<T> factoryClass) throws RuntimeException {
for (ClassLoader l : getClassLoaders(
Thread.class,
FactoryFinder.class,
System.class)) {
T f = find(factoryClass, l);
if (f != null) {
return f;
}
}

ClassLoader tccl = ServiceLoaderUtil.contextClassLoader(EXCEPTION_HANDLER);
String factoryId = factoryClass.getName();
throw EXCEPTION_HANDLER.createException((Throwable) null,
"Provider for " + factoryClass.getName() + " cannot be found");
}

static <T> T find(Class<T> factoryClass, ClassLoader loader) throws RuntimeException {
// Use the system property first
String className = fromSystemProperty(factoryId);
String className = fromSystemProperty(factoryClass.getName());
if (className != null) {
T result = newInstance(className, defaultClassName, tccl);
if (result != null) {
return result;
}
// try api loader
result = newInstance(className, defaultClassName, FactoryFinder.class.getClassLoader());
T result = newInstance(className, factoryClass, loader);
if (result != null) {
return result;
}
Expand All @@ -75,40 +70,29 @@ static <T> T find(Class<T> factoryClass,
// standard services: java.util.ServiceLoader
T factory = ServiceLoaderUtil.firstByServiceLoader(
factoryClass,
loader,
logger,
EXCEPTION_HANDLER);
if (factory != null) {
return factory;
}

// handling Glassfish/OSGi (platform specific default)
if (isOsgi()) {
T result = lookupUsingOSGiServiceLoader(factoryId);
if (result != null) {
return result;
}
}

// If not found and fallback should not be tried, throw RuntimeException.
if (!tryFallback) {
throw new RuntimeException(
"Provider for " + factoryId + " cannot be found", null);
T result = lookupUsingHk2ServiceLoader(factoryClass, loader);
if (result != null) {
return result;
}

// We didn't find the class through the usual means so try the default
// (built in) factory if specified.
if (defaultClassName == null) {
throw new RuntimeException(
"Provider for " + factoryId + " cannot be found", null);
}
return newInstance(defaultClassName, defaultClassName, tccl);
return null;
}

private static <T> T newInstance(String className, String defaultClassName, ClassLoader tccl) throws RuntimeException {
private static <T> T newInstance(String className,
Class<? extends T> service, ClassLoader loader)
throws RuntimeException {
return ServiceLoaderUtil.newInstance(
className,
defaultClassName,
tccl,
service,
loader,
EXCEPTION_HANDLER);
}

Expand Down Expand Up @@ -136,31 +120,85 @@ private static void logFound(String value) {
}
}

private static final String OSGI_SERVICE_LOADER_CLASS_NAME = "org.glassfish.hk2.osgiresourcelocator.ServiceLoader";
private static Class<?>[] getHk2ServiceLoaderTargets(Class<?> factoryClass) {
ClassLoader[] loaders = getClassLoaders(Thread.class, factoryClass, System.class);

Class<?>[] classes = new Class<?>[loaders.length];
int w = 0;
for (ClassLoader loader : loaders) {
if (loader != null) {
try {
classes[w++] = Class.forName("org.glassfish.hk2.osgiresourcelocator.ServiceLoader", false, loader);
} catch (Exception | LinkageError ignored) {
} //GlassFish class loaders can throw undocumented exceptions
}
}

private static boolean isOsgi() {
try {
Class.forName(OSGI_SERVICE_LOADER_CLASS_NAME);
return true;
} catch (ClassNotFoundException ignored) {
if (classes.length != w) {
classes = Arrays.copyOf(classes, w);
}
return false;
return classes;
}

@SuppressWarnings({"unchecked"})
private static <T> T lookupUsingOSGiServiceLoader(String factoryId) {
try {
// Use reflection to avoid having any dependency on HK2 ServiceLoader class
Class<?> serviceClass = Class.forName(factoryId);
Class<?>[] args = new Class<?>[]{serviceClass};
Class<?> target = Class.forName(OSGI_SERVICE_LOADER_CLASS_NAME);
Method m = target.getMethod("lookupProviderInstances", Class.class);
Iterator<?> iter = ((Iterable<?>) m.invoke(null, (Object[]) args)).iterator();
return iter.hasNext() ? (T) iter.next() : null;
} catch (Exception ignored) {
// log and continue
return null;
private static <T> T lookupUsingHk2ServiceLoader(Class<T> factoryClass, ClassLoader loader) {
for (Class<?> target : getHk2ServiceLoaderTargets(factoryClass)) {
try {
// Use reflection to avoid having any dependency on HK2 ServiceLoader class
Class<?> serviceClass = Class.forName(factoryClass.getName(), false, loader);
Class<?>[] args = new Class<?>[]{serviceClass};
Method m = target.getMethod("lookupProviderInstances", Class.class);
Iterable<?> iterable = ((Iterable<?>) m.invoke(null, (Object[]) args));
if (iterable != null) {
Iterator<?> iter = iterable.iterator();
if (iter.hasNext()) {
return factoryClass.cast(iter.next()); //Verify classloader.
}
}
} catch (Exception ignored) {
// log and continue
}
}
return null;
}

private static ClassLoader[] getClassLoaders(final Class<?>... classes) {
return AccessController.doPrivileged(
new PrivilegedAction<ClassLoader[]>() {
@Override
public ClassLoader[] run() {
ClassLoader[] loaders = new ClassLoader[classes.length];
int w = 0;
for (Class<?> k : classes) {
ClassLoader cl = null;
if (k == Thread.class) {
try {
cl = Thread.currentThread().getContextClassLoader();
} catch (SecurityException ex) {
}
} else if (k == System.class) {
try {
cl = ClassLoader.getSystemClassLoader();
} catch (SecurityException ex) {
}
} else {
try {
cl = k.getClassLoader();
} catch (SecurityException ex) {
}
}

if (cl != null) {
loaders[w++] = cl;
}
}

if (loaders.length != w) {
loaders = Arrays.copyOf(loaders, w);
}
return loaders;
}
}
);
}
}
20 changes: 8 additions & 12 deletions api/src/main/java/jakarta/activation/MailcapCommandMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ private MailcapRegistry loadResource(String name) {
} catch (IOException | SecurityException e) {
if (LogSupport.isLoggable())
LogSupport.log("MailcapCommandMap: can't load " + name, e);
} catch (NoSuchElementException | ServiceConfigurationError e) {
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
if (LogSupport.isLoggable()) {
LogSupport.log("Cannot find or load an implementation for MailcapRegistryProvider. " +
"MailcapRegistry: can't load " + name, e);
Expand Down Expand Up @@ -261,7 +261,7 @@ private void loadAllResources(List<MailcapRegistry> v, String name) {
if (LogSupport.isLoggable())
LogSupport.log("MailcapCommandMap: can't load " +
url, ioex);
} catch (NoSuchElementException | ServiceConfigurationError e) {
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
if (LogSupport.isLoggable()) {
LogSupport.log("Cannot find or load an implementation for MailcapRegistryProvider. " +
"MailcapRegistry: can't load " + name, e);
Expand Down Expand Up @@ -296,7 +296,7 @@ private MailcapRegistry loadFile(String name) {
if (LogSupport.isLoggable()) {
LogSupport.log("MailcapRegistry: can't load from file - " + name, e);
}
} catch (NoSuchElementException | ServiceConfigurationError e) {
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
if (LogSupport.isLoggable()) {
LogSupport.log("Cannot find or load an implementation for MailcapRegistryProvider. " +
"MailcapRegistry: can't load " + name, e);
Expand All @@ -317,7 +317,7 @@ public MailcapCommandMap(String fileName) throws IOException {
if (DB[PROG] == null) {
try {
DB[PROG] = getImplementation().getByFileName(fileName);
} catch (NoSuchElementException | ServiceConfigurationError e) {
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
String message = "Cannot find or load an implementation for MailcapRegistryProvider. " +
"MailcapRegistry: can't load " + fileName;
if (LogSupport.isLoggable()) {
Expand Down Expand Up @@ -346,7 +346,7 @@ public MailcapCommandMap(InputStream is) {
DB[PROG] = getImplementation().getByInputStream(is);
} catch (IOException ex) {
// XXX - should throw it
} catch (NoSuchElementException | ServiceConfigurationError e) {
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
if (LogSupport.isLoggable()) {
LogSupport.log("Cannot find or load an implementation for MailcapRegistryProvider." +
"MailcapRegistry: can't load InputStream", e);
Expand Down Expand Up @@ -547,7 +547,7 @@ public synchronized void addMailcap(String mail_cap) {
DB[PROG] = getImplementation().getInMemory();
}
DB[PROG].appendToMailcap(mail_cap);
} catch (NoSuchElementException | ServiceConfigurationError e) {
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
if (LogSupport.isLoggable()) {
LogSupport.log("Cannot find or load an implementation for MailcapRegistryProvider. " +
"MailcapRegistry: can't load", e);
Expand Down Expand Up @@ -706,15 +706,11 @@ private MailcapRegistryProvider getImplementation() {
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged(new PrivilegedAction<MailcapRegistryProvider>() {
public MailcapRegistryProvider run() {
return FactoryFinder.find(MailcapRegistryProvider.class,
null,
false);
return FactoryFinder.find(MailcapRegistryProvider.class);
}
});
} else {
return FactoryFinder.find(MailcapRegistryProvider.class,
null,
false);
return FactoryFinder.find(MailcapRegistryProvider.class);
}
}

Expand Down
20 changes: 8 additions & 12 deletions api/src/main/java/jakarta/activation/MimetypesFileTypeMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ private MimeTypeRegistry loadResource(String name) {
} catch (IOException | SecurityException e) {
if (LogSupport.isLoggable())
LogSupport.log("MimetypesFileTypeMap: can't load " + name, e);
} catch (NoSuchElementException | ServiceConfigurationError e) {
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
if (LogSupport.isLoggable()) {
LogSupport.log("Cannot find or load an implementation for MimeTypeRegistryProvider." +
"MimeTypeRegistry: can't load " + name, e);
Expand Down Expand Up @@ -228,7 +228,7 @@ private void loadAllResources(Vector<MimeTypeRegistry> v, String name) {
if (LogSupport.isLoggable())
LogSupport.log("MimetypesFileTypeMap: can't load " +
url, ioex);
} catch (NoSuchElementException | ServiceConfigurationError e) {
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
if (LogSupport.isLoggable()) {
LogSupport.log("Cannot find or load an implementation for MimeTypeRegistryProvider." +
"MimeTypeRegistry: can't load " + url, e);
Expand Down Expand Up @@ -270,7 +270,7 @@ private MimeTypeRegistry loadFile(String name) {
if (LogSupport.isLoggable()) {
LogSupport.log("MimeTypeRegistry: can't load from file - " + name, e);
}
} catch (NoSuchElementException | ServiceConfigurationError e) {
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
if (LogSupport.isLoggable()) {
LogSupport.log("Cannot find or load an implementation for MimeTypeRegistryProvider." +
"MimeTypeRegistry: can't load " + name, e);
Expand All @@ -290,7 +290,7 @@ public MimetypesFileTypeMap(String mimeTypeFileName) throws IOException {
this();
try {
DB[PROG] = getImplementation().getByFileName(mimeTypeFileName);
} catch (NoSuchElementException | ServiceConfigurationError e) {
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
String errorMessage = "Cannot find or load an implementation for MimeTypeRegistryProvider." +
"MimeTypeRegistry: can't load " + mimeTypeFileName;
if (LogSupport.isLoggable()) {
Expand All @@ -312,7 +312,7 @@ public MimetypesFileTypeMap(InputStream is) {
DB[PROG] = getImplementation().getByInputStream(is);
} catch (IOException ex) {
// XXX - really should throw it
} catch (NoSuchElementException | ServiceConfigurationError e) {
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
if (LogSupport.isLoggable()) {
LogSupport.log("Cannot find or load an implementation for MimeTypeRegistryProvider." +
"MimeTypeRegistry: can't load InputStream", e);
Expand All @@ -332,7 +332,7 @@ public synchronized void addMimeTypes(String mime_types) {
DB[PROG] = getImplementation().getInMemory();
}
DB[PROG].appendToRegistry(mime_types);
} catch (NoSuchElementException | ServiceConfigurationError e) {
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
if (LogSupport.isLoggable()) {
LogSupport.log("Cannot find or load an implementation for MimeTypeRegistryProvider." +
"MimeTypeRegistry: can't add " + mime_types, e);
Expand Down Expand Up @@ -398,15 +398,11 @@ private MimeTypeRegistryProvider getImplementation() {
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged(new PrivilegedAction<MimeTypeRegistryProvider>() {
public MimeTypeRegistryProvider run() {
return FactoryFinder.find(MimeTypeRegistryProvider.class,
null,
false);
return FactoryFinder.find(MimeTypeRegistryProvider.class);
}
});
} else {
return FactoryFinder.find(MimeTypeRegistryProvider.class,
null,
false);
return FactoryFinder.find(MimeTypeRegistryProvider.class);
}
}

Expand Down
Loading
Loading