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 Mail erroneously assumes that classes can be loaded from Thread#getContextClassLoader #701

Merged
merged 14 commits into from
Jan 5, 2024
51 changes: 23 additions & 28 deletions api/src/main/java/jakarta/mail/Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ private Session(Properties props, Authenticator authenticator) {
this.authenticator = authenticator;
this.streamProvider = StreamProvider.provider();

if (Boolean.valueOf(props.getProperty("mail.debug")).booleanValue())
if (Boolean.parseBoolean(props.getProperty("mail.debug")))
debug = true;

initLogger();
Expand Down Expand Up @@ -983,21 +983,20 @@ public void load(InputStream is) throws IOException {
}

// next, add all the non-default services
ServiceLoader<Provider> sl = ServiceLoader.load(Provider.class);
ServiceLoader<Provider> sl = ServiceLoader.load(Provider.class, cl.getClassLoader());
for (Provider p : sl) {
if (!containsDefaultProvider(p))
addProvider(p);
}

// + handle Glassfish/OSGi (platform specific default)
if (isHk2Available()) {
Iterator<Provider> iter = lookupUsingHk2ServiceLoader(Provider.class.getName());
while (iter.hasNext()) {
Provider p = iter.next();
if (!containsDefaultProvider(p))
addProvider(p);
}
Iterator<Provider> iter = lookupUsingHk2ServiceLoader(Provider.class, cl.getClassLoader());
while (iter.hasNext()) {
Provider p = iter.next();
if (!containsDefaultProvider(p))
addProvider(p);
}


// load the META-INF/javamail.providers file supplied by an application
loadAllResources("META-INF/javamail.providers", cl, loader);
Expand All @@ -1006,27 +1005,26 @@ public void load(InputStream is) throws IOException {
loadResource("/META-INF/javamail.default.providers", cl, loader, false);

// finally, add all the default services
sl = ServiceLoader.load(Provider.class);
sl = ServiceLoader.load(Provider.class, cl.getClassLoader());
for (Provider p : sl) {
if (containsDefaultProvider(p))
addProvider(p);
}

// + handle Glassfish/OSGi (platform specific default)
if (isHk2Available()) {
Iterator<Provider> iter = lookupUsingHk2ServiceLoader(Provider.class.getName());
while (iter.hasNext()) {
Provider p = iter.next();
if (containsDefaultProvider(p)) {
addProvider(p);
}
iter = lookupUsingHk2ServiceLoader(Provider.class, cl.getClassLoader());
while (iter.hasNext()) {
Provider p = iter.next();
if (containsDefaultProvider(p)) {
addProvider(p);
}
}


/*
* If we haven't loaded any providers, fake it.
*/
if (providers.size() == 0) {
if (providers.isEmpty()) {
logger.config("failed to load any providers, using defaults");
// failed to load any providers, initialize with our defaults
addProvider(new Provider(Provider.Type.STORE,
Expand Down Expand Up @@ -1388,22 +1386,19 @@ EventQueue getEventQueue() {

private static final String OSGI_SERVICE_LOADER_CLASS_NAME = "org.glassfish.hk2.osgiresourcelocator.ServiceLoader";

private static boolean isHk2Available() {
@SuppressWarnings({"unchecked"})
private <T> Iterator<T> lookupUsingHk2ServiceLoader(Class<T> factoryId, ClassLoader loader) {
Class<?> target;
try {
Class.forName(OSGI_SERVICE_LOADER_CLASS_NAME);
return true;
target = Class.forName(OSGI_SERVICE_LOADER_CLASS_NAME, false, loader);
} catch (ClassNotFoundException ignored) {
return Collections.emptyIterator();
}
return false;
}

@SuppressWarnings({"unchecked"})
private <T> Iterator<T> lookupUsingHk2ServiceLoader(String factoryId) {

try {
// Use reflection to avoid having any dependency on HK2 ServiceLoader class
Class<?> serviceClass = Class.forName(factoryId);
Class<?> serviceClass = Class.forName(factoryId.getName(), false, loader);
Class<?>[] args = new Class<?>[]{serviceClass};
Class<?> target = Class.forName(OSGI_SERVICE_LOADER_CLASS_NAME);
Method m = target.getMethod("lookupProviderInstances", Class.class);
Iterable<T> result = ((Iterable<T>) m.invoke(null, (Object[]) args));
return result != null ? result.iterator() : Collections.emptyIterator();
Expand Down
71 changes: 45 additions & 26 deletions api/src/main/java/jakarta/mail/util/FactoryFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,46 +33,66 @@ class FactoryFinder {
* @throws RuntimeException if there is an error
*/
static <T> T find(Class<T> factoryClass) throws RuntimeException {
T result;
result = find(factoryClass, Thread.currentThread().getContextClassLoader());
if (result != null) {
return result;
}

result = find(factoryClass, factoryClass.getClassLoader());
if (result != null) {
return result;
}

result = find(factoryClass, ClassLoader.getSystemClassLoader());
if (result != null) {
return result;
}

throw new IllegalStateException("No provider of " + factoryClass.getName() + " was found");
}

private static <T> T find(Class<T> factoryClass, ClassLoader loader) throws RuntimeException {
String factoryId = factoryClass.getName();

// Use the system property first
String className = fromSystemProperty(factoryId);
if (className != null) {
T result = newInstance(className);
T result = newInstance(className, factoryClass, loader);
if (result != null) {
return result;
}
}

// standard services: java.util.ServiceLoader
T factory = factoryFromServiceLoader(factoryClass);
T factory = factoryFromServiceLoader(factoryClass, loader);
if (factory != null) {
return factory;
}

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

T result = lookupUsingHk2ServiceLoader(factoryId, factoryClass, loader);
if (result != null) {
return result;
}
throw new IllegalStateException("Not provider of " + factoryClass.getName() + " was found");

return null;
}

@SuppressWarnings({"unchecked"})
private static <T> T newInstance(String className) throws RuntimeException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
private static <T> T newInstance(String className, Class<T> factoryClass, ClassLoader classLoader) throws RuntimeException {
checkPackageAccess(className);
Class<T> clazz = null;
try {
Class<T> clazz;
if (classLoader == null) {
clazz = (Class<T>) Class.forName(className);
} else {
clazz = (Class<T>) classLoader.loadClass(className);
clazz = (Class<T>) Class.forName(className, false, classLoader);
}
return clazz.getConstructor().newInstance();
return clazz.asSubclass(factoryClass).getConstructor().newInstance();
} catch (ClassCastException wrongLoader) {
return null;
} catch (ReflectiveOperationException e) {
throw new IllegalArgumentException("Cannot instance " + className, e);
}
Expand All @@ -85,44 +105,43 @@ private static String fromSystemProperty(String factoryId) {

private static final String OSGI_SERVICE_LOADER_CLASS_NAME = "org.glassfish.hk2.osgiresourcelocator.ServiceLoader";

private static boolean isHk2Available() {
@SuppressWarnings({"unchecked"})
private static <T> T lookupUsingHk2ServiceLoader(String factoryId, Class<T> factoryClass, ClassLoader loader) {
Class<?> target;
try {
Class.forName(OSGI_SERVICE_LOADER_CLASS_NAME);
return true;
target = Class.forName(OSGI_SERVICE_LOADER_CLASS_NAME, false, loader);
} catch (ClassNotFoundException ignored) {
return null;
}
return false;
}

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

private static <T> T factoryFromServiceLoader(Class<T> factory) {
private static <T> T factoryFromServiceLoader(Class<T> factory, ClassLoader loader) {
try {
ServiceLoader<T> sl = ServiceLoader.load(factory);
ServiceLoader<T> sl = ServiceLoader.load(factory, loader);
Iterator<T> iter = sl.iterator();
if (iter.hasNext()) {
return iter.next();
return factory.cast(iter.next());
} else {
return null;
}
} catch (ClassCastException wrongLoader) {
return null;
} catch (Throwable t) {
// For example, ServiceConfigurationError can be thrown if the factory class is not declared with 'uses' in module-info
throw new IllegalStateException("Cannot load " + factory + " as ServiceLoader", t);
Expand Down
Loading