From e2cd36d90596fc0b6db5891333a02a962d874482 Mon Sep 17 00:00:00 2001 From: jmehrens Date: Wed, 14 Feb 2024 09:04:26 -0600 Subject: [PATCH] Jakarta Activation erroneously assumes that classes can be loaded from Thread#getContextClassLoader (#145) Signed-off-by: jmehrens (cherry picked from commit 4b8285b0f77473f13f18bb46c76d3f82311f5a3c) --- .../jakarta/activation/FactoryFinder.java | 175 +++++++++++------- .../jakarta/activation/MailcapCommandMap.java | 22 +-- .../activation/MimetypesFileTypeMap.java | 29 +-- .../jakarta/activation/ServiceLoaderUtil.java | 53 ++---- 4 files changed, 140 insertions(+), 139 deletions(-) diff --git a/api/src/main/java/jakarta/activation/FactoryFinder.java b/api/src/main/java/jakarta/activation/FactoryFinder.java index 5c6ce47..e1a4441 100644 --- a/api/src/main/java/jakarta/activation/FactoryFinder.java +++ b/api/src/main/java/jakarta/activation/FactoryFinder.java @@ -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 @@ -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; @@ -26,48 +27,41 @@ class FactoryFinder { new ServiceLoaderUtil.ExceptionHandler() { @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. *

* This method is package private so that this code can be shared. * + * @param factoryClass factory abstract class or interface to be found * @return the {@code Class} object of the specified message factory; - * may not be {@code null} - * - * @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 - * @exception RuntimeException if there is no factory found + * may not be {@code null} + * @throws IllegalStateException if there is no factory found */ - static T find(Class factoryClass, - String defaultClassName, - boolean tryFallback) throws RuntimeException { + static T find(Class 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 find(Class 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; } @@ -76,6 +70,7 @@ static T find(Class factoryClass, // standard services: java.util.ServiceLoader T factory = ServiceLoaderUtil.firstByServiceLoader( factoryClass, + loader, logger, EXCEPTION_HANDLER); if (factory != null) { @@ -83,33 +78,21 @@ static T find(Class factoryClass, } // handling Glassfish/OSGi (platform specific default) - if (isOsgi()) { - T result = lookupUsingOSGiServiceLoader(factoryId); - if (result != null) { - return result; - } + T result = lookupUsingHk2ServiceLoader(factoryClass, loader); + 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); - } - - // 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 newInstance(String className, String defaultClassName, ClassLoader tccl) throws RuntimeException { + private static T newInstance(String className, + Class service, ClassLoader loader) + throws RuntimeException { return ServiceLoaderUtil.newInstance( className, - defaultClassName, - tccl, + service, + loader, EXCEPTION_HANDLER); } @@ -138,31 +121,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 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 lookupUsingHk2ServiceLoader(Class 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() { + @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; + } + } + ); + } } diff --git a/api/src/main/java/jakarta/activation/MailcapCommandMap.java b/api/src/main/java/jakarta/activation/MailcapCommandMap.java index f3a186d..9974333 100644 --- a/api/src/main/java/jakarta/activation/MailcapCommandMap.java +++ b/api/src/main/java/jakarta/activation/MailcapCommandMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022 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 @@ -200,7 +200,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); @@ -251,7 +251,7 @@ private void loadAllResources(List 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); @@ -286,7 +286,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); @@ -307,7 +307,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()) { @@ -336,7 +336,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); @@ -537,7 +537,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); @@ -696,15 +696,11 @@ private MailcapRegistryProvider getImplementation() { if (System.getSecurityManager() != null) { return AccessController.doPrivileged(new PrivilegedAction() { 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); } } diff --git a/api/src/main/java/jakarta/activation/MimetypesFileTypeMap.java b/api/src/main/java/jakarta/activation/MimetypesFileTypeMap.java index 716a865..82bcc5c 100644 --- a/api/src/main/java/jakarta/activation/MimetypesFileTypeMap.java +++ b/api/src/main/java/jakarta/activation/MimetypesFileTypeMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022 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 @@ -161,10 +161,7 @@ private MimeTypeRegistry loadResource(String name) { } catch (IOException e) { if (LogSupport.isLoggable()) LogSupport.log("MimetypesFileTypeMap: can't load " + name, e); - } catch (SecurityException sex) { - if (LogSupport.isLoggable()) - LogSupport.log("MimetypesFileTypeMap: can't load " + name, sex); - } 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); @@ -226,11 +223,7 @@ private void loadAllResources(Vector v, String name) { if (LogSupport.isLoggable()) LogSupport.log("MimetypesFileTypeMap: can't load " + url, ioex); - } catch (SecurityException sex) { - if (LogSupport.isLoggable()) - LogSupport.log("MimetypesFileTypeMap: can't load " + - url, sex); - } 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); @@ -272,7 +265,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); @@ -292,7 +285,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()) { @@ -314,7 +307,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); @@ -334,7 +327,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); @@ -388,15 +381,11 @@ private MimeTypeRegistryProvider getImplementation() { if (System.getSecurityManager() != null) { return AccessController.doPrivileged(new PrivilegedAction() { 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); } } diff --git a/api/src/main/java/jakarta/activation/ServiceLoaderUtil.java b/api/src/main/java/jakarta/activation/ServiceLoaderUtil.java index 83ecf63..dfeabdf 100644 --- a/api/src/main/java/jakarta/activation/ServiceLoaderUtil.java +++ b/api/src/main/java/jakarta/activation/ServiceLoaderUtil.java @@ -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 @@ -15,20 +15,22 @@ import java.util.logging.Logger; /** - * Shared ServiceLoader/FactoryFinder Utils shared among JAF, MAIL, SAAJ, JAXB and JAXWS - * Class duplicated to all those projects. + * Shared ServiceLoader/FactoryFinder Utils. JAF and MAIL use the same loading + * logic of thread context class loader, calling class loader, and finally the + * system class loader. * * @author Miroslav.Kos@oracle.com */ class ServiceLoaderUtil { static P firstByServiceLoader(Class

spiClass, + ClassLoader loader, Logger logger, ExceptionHandler handler) throws T { logger.log(Level.FINE, "Using java.util.ServiceLoader to find {0}", spiClass.getName()); // service discovery try { - ServiceLoader

serviceLoader = ServiceLoader.load(spiClass); + ServiceLoader

serviceLoader = ServiceLoader.load(spiClass, loader); for (P impl : serviceLoader) { logger.log(Level.FINE, "ServiceProvider loading Facility used; returning object [{0}]", impl.getClass().getName()); @@ -54,22 +56,19 @@ static void checkPackageAccess(String className) { @SuppressWarnings({"unchecked"}) static

Class

nullSafeLoadClass(String className, ClassLoader classLoader) throws ClassNotFoundException { - if (classLoader == null) { - return (Class

) Class.forName(className); - } else { - return (Class

) classLoader.loadClass(className); + if (classLoader == null) { //Match behavior of ServiceLoader + classLoader = ClassLoader.getSystemClassLoader(); } + return (Class

) Class.forName(className, false, classLoader); } - // Returns instance of required class. It checks package access (security) - // unless it is defaultClassname. It means if you are trying to instantiate - // default implementation (fallback), pass the class name to both first and second parameter. + // Returns instance of required class. It checks package access (security). static P newInstance(String className, - String defaultImplClassName, ClassLoader classLoader, - final ExceptionHandler handler) throws T { + Class

service, ClassLoader classLoader, + final ExceptionHandler handler) throws T { try { - Class

cls = safeLoadClass(className, defaultImplClassName, classLoader); - return cls.getConstructor().newInstance(); + Class

cls = safeLoadClass(className, classLoader); + return service.cast(cls.getConstructor().newInstance()); } catch (ClassNotFoundException x) { throw handler.createException(x, "Provider " + className + " not found"); } catch (Exception x) { @@ -79,34 +78,14 @@ static P newInstance(String className, @SuppressWarnings({"unchecked"}) static

Class

safeLoadClass(String className, - String defaultImplClassName, - ClassLoader classLoader) throws ClassNotFoundException { - - try { - checkPackageAccess(className); - } catch (SecurityException se) { - // anyone can access the platform default factory class without permission - if (defaultImplClassName != null && defaultImplClassName.equals(className)) { - return (Class

) Class.forName(className); - } - // not platform default implementation ... - throw se; - } + ClassLoader classLoader) throws ClassNotFoundException { + checkPackageAccess(className); return nullSafeLoadClass(className, classLoader); } - static ClassLoader contextClassLoader(ExceptionHandler exceptionHandler) throws T { - try { - return Thread.currentThread().getContextClassLoader(); - } catch (Exception x) { - throw exceptionHandler.createException(x, x.toString()); - } - } - static abstract class ExceptionHandler { public abstract T createException(Throwable throwable, String message); } - }