Skip to content

Commit

Permalink
Jakarta Activation erroneously assumes that classes can be loaded fro…
Browse files Browse the repository at this point in the history
…m Thread#getContextClassLoader (#145)

Signed-off-by: jmehrens <jason_mehrens@hotmail.com>
  • Loading branch information
jmehrens committed Feb 14, 2024
1 parent f288c7d commit 4b8285b
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 127 deletions.
170 changes: 104 additions & 66 deletions api/src/main/java/jakarta/activation/FactoryFinder.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
Expand All @@ -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
* @throws IllegalStateException 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;
}
}
);
}
}
22 changes: 9 additions & 13 deletions api/src/main/java/jakarta/activation/MailcapCommandMap.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
Expand Down 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
22 changes: 9 additions & 13 deletions api/src/main/java/jakarta/activation/MimetypesFileTypeMap.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
Expand Down 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

0 comments on commit 4b8285b

Please sign in to comment.