diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java index c7ae772232615..960050e364269 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java @@ -55,10 +55,15 @@ public class QuarkusClassLoader extends ClassLoader implements Closeable { registerAsParallelCapable(); } + private ClassLoader facade; + private static RuntimeException nonQuarkusClassLoaderError() { return new IllegalStateException("The current classloader is not an instance of " + QuarkusClassLoader.class.getName() + " but " - + Thread.currentThread().getContextClassLoader().getClass().getName()); + + Thread.currentThread() + .getContextClassLoader() + .getClass() + .getName()); } /** @@ -72,7 +77,8 @@ private static RuntimeException nonQuarkusClassLoaderError() { * @param visitor runtime resource visitor */ public static void visitRuntimeResources(String resourceName, Consumer visitor) { - if (Thread.currentThread().getContextClassLoader() instanceof QuarkusClassLoader classLoader) { + if (Thread.currentThread() + .getContextClassLoader() instanceof QuarkusClassLoader classLoader) { for (var element : classLoader.getElementsWithResource(resourceName)) { if (element.isRuntime()) { element.apply(tree -> { @@ -87,7 +93,8 @@ public static void visitRuntimeResources(String resourceName, Consumer getElements(String resourceName, boolean onlyFromCurrentClassLoader) { - if (Thread.currentThread().getContextClassLoader() instanceof QuarkusClassLoader classLoader) { + if (Thread.currentThread() + .getContextClassLoader() instanceof QuarkusClassLoader classLoader) { return classLoader.getElementsWithResource(resourceName, onlyFromCurrentClassLoader); } throw nonQuarkusClassLoaderError(); @@ -107,7 +114,8 @@ public static boolean isClassPresentAtRuntime(String className) { * Indicates if a given class is considered an application class. */ public static boolean isApplicationClass(String className) { - if (Thread.currentThread().getContextClassLoader() instanceof QuarkusClassLoader classLoader) { + if (Thread.currentThread() + .getContextClassLoader() instanceof QuarkusClassLoader classLoader) { String resourceName = fromClassNameToResourceName(className); ClassPathResourceIndex classPathResourceIndex = classLoader.getClassPathResourceIndex(); @@ -126,7 +134,8 @@ public static boolean isApplicationClass(String className) { public static boolean isResourcePresentAtRuntime(String resourcePath) { List classPathElements = QuarkusClassLoader.getElements(resourcePath, false); for (int i = 0; i < classPathElements.size(); i++) { - if (classPathElements.get(i).isRuntime()) { + if (classPathElements.get(i) + .isRuntime()) { return true; } } @@ -172,7 +181,8 @@ public static boolean isResourcePresentAtRuntime(String resourcePath) { static { ClassLoader cl = null; try { - cl = (ClassLoader) ClassLoader.class.getDeclaredMethod("getPlatformClassLoader").invoke(null); + cl = (ClassLoader) ClassLoader.class.getDeclaredMethod("getPlatformClassLoader") + .invoke(null); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { } @@ -186,6 +196,9 @@ private QuarkusClassLoader(Builder builder) { // Not passing the name to the parent constructor on purpose: // stacktraces become very ugly if we do that. super(builder.parent); + + this.facade = Thread.currentThread().getContextClassLoader(); + this.name = builder.name; this.status = STATUS_OPEN; this.normalPriorityElements = builder.normalPriorityElements; @@ -249,7 +262,8 @@ public Enumeration getResources(String unsanitisedName, boolean parentAlrea ensureOpen(unsanitisedName); for (int i = 0; i < classLoaderEventListeners.size(); i++) { - classLoaderEventListeners.get(i).enumeratingResourceURLs(unsanitisedName, this.name); + classLoaderEventListeners.get(i) + .enumeratingResourceURLs(unsanitisedName, this.name); } ClassPathResourceIndex classPathResourceIndex = getClassPathResourceIndex(); String name = sanitizeName(unsanitisedName); @@ -284,7 +298,8 @@ public Enumeration getResources(String unsanitisedName, boolean parentAlrea if (!classPathElements.isEmpty()) { boolean endsWithTrailingSlash = unsanitisedName.endsWith("/"); for (int i = 0; i < classPathElements.size(); i++) { - List resList = classPathElements.get(i).getResources(name); + List resList = classPathElements.get(i) + .getResources(name); //if the requested name ends with a trailing / we make sure //that the resource is a directory, and return a URL that ends with a / //this matches the behaviour of URLClassLoader @@ -293,7 +308,8 @@ public Enumeration getResources(String unsanitisedName, boolean parentAlrea if (endsWithTrailingSlash) { if (res.isDirectory()) { try { - resources.add(new URL(res.getUrl().toString() + "/")); + resources.add(new URL(res.getUrl() + .toString() + "/")); } catch (MalformedURLException e) { throw new RuntimeException(e); } @@ -403,7 +419,8 @@ public URL getResource(String unsanitisedName) { private static URL getClassPathElementResourceUrl(List classPathElements, String name, boolean endsWithTrailingSlash) { for (int i = 0; i < classPathElements.size(); i++) { - ClassPathResource res = classPathElements.get(i).getResource(name); + ClassPathResource res = classPathElements.get(i) + .getResource(name); if (res != null) { //if the requested name ends with a trailing / we make sure //that the resource is a directory, and return a URL that ends with a / @@ -411,7 +428,8 @@ private static URL getClassPathElementResourceUrl(List classPa if (endsWithTrailingSlash) { if (res.isDirectory()) { try { - return new URL(res.getUrl().toString() + "/"); + return new URL(res.getUrl() + .toString() + "/"); } catch (MalformedURLException e) { throw new RuntimeException(e); } @@ -430,7 +448,8 @@ public InputStream getResourceAsStream(String unsanitisedName) { ensureOpen(unsanitisedName); for (int i = 0; i < classLoaderEventListeners.size(); i++) { - classLoaderEventListeners.get(i).openResourceStream(unsanitisedName, this.name); + classLoaderEventListeners.get(i) + .openResourceStream(unsanitisedName, this.name); } String name = sanitizeName(unsanitisedName); ClassPathResourceIndex classPathResourceIndex = getClassPathResourceIndex(); @@ -468,7 +487,8 @@ private static InputStream getClassPathElementResourceInputStream(List loadClass(String name, boolean resolve) throws ClassNotFoundE } finally { if (interrupted) { //restore interrupt state - Thread.currentThread().interrupt(); + Thread.currentThread() + .interrupt(); } } } @@ -766,7 +787,8 @@ public void close() { try (InputStream is = getClass().getResourceAsStream("DriverRemover.class")) { byte[] data = is.readAllBytes(); Runnable r = (Runnable) defineClass(DriverRemover.class.getName(), data, 0, data.length) - .getConstructor(ClassLoader.class).newInstance(this); + .getConstructor(ClassLoader.class) + .newInstance(this); r.run(); } catch (Exception e) { log.debug("Failed to clean up DB drivers"); @@ -834,6 +856,10 @@ public void setStartupAction(StartupAction startupAction) { this.startupAction = startupAction; } + public ClassLoader getFacadeClassloader() { + return facade; + } + public static class Builder { final String name; final ClassLoader parent; diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java index d77f47960f227..068c3f983fb88 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java @@ -48,6 +48,8 @@ public class AbstractJvmQuarkusTestExtension extends AbstractQuarkusTestWithCont protected ClassLoader originalCl; + protected final ClassLoader facade; + // Used to preserve state from the previous run, so we know if we should restart an application protected static RunningQuarkusApplication runningQuarkusApplication; @@ -57,6 +59,11 @@ public class AbstractJvmQuarkusTestExtension extends AbstractQuarkusTestWithCont protected static final Deque> currentTestClassStack = new ArrayDeque<>(); protected static Class currentJUnitTestClass; + protected AbstractJvmQuarkusTestExtension() { + // TODO where is right place to get this? + facade = Thread.currentThread().getContextClassLoader(); + } + // TODO only used by QuarkusMainTest, fix that class and delete this protected PrepareResult createAugmentor(ExtensionContext context, Class profile, Collection shutdownTasks) throws Exception { @@ -261,21 +268,44 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con System.out.println("OK smallrye " + SmallRyeConfig.class.getClassLoader()); // TODO this should not be necessary, because we want the config provider to be for our class - // ClassLoader original = Thread.currentThread().getContextClassLoader(); - // Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); + ClassLoader original = Thread.currentThread().getContextClassLoader(); + + QuarkusClassLoader mecl = (QuarkusClassLoader) this.getClass().getClassLoader(); + ClassLoader facade = mecl.getFacadeClassloader(); + + // Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); // SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class); // TestConfig testConfig = config.getConfigMapping(TestConfig.class); // At this point, the TCCL is the FacadeClassLoader; trying to do getConfig with that TCCL fails with java.util.ServiceConfigurationError: io.smallrye.config.SmallRyeConfigFactory: io.quarkus.runtime.configuration.QuarkusConfigFactory not a subtype // If we set the TCCL to be this.getClass().getClassLoader(), get config succeeds, but config.getConfigMapping(TestConfig.class) fails, because the mapping was registered when the TCCL was the FacadeClassLoader - TestConfig testConfig = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class).getConfigMapping(TestConfig.class); + // In nested tests and multimodule tests, the TCCL may not be the facade classloader + + // ClassLoader facade = FacadeClassLoader.instance(Exception.class.getClassLoader()); + // Thread.currentThread().setContextClassLoader(Error.class.getClassLoader()); + Thread.currentThread().setContextClassLoader(facade); + System.out.println("HOLLY Config using facade TCCL " + Thread.currentThread().getContextClassLoader()); + + TestConfig testConfig = null; + System.out.println("HOLLY CONFIG " + " and the class of the mapping is " + + TestConfig.class.getClassLoader()); + try { + testConfig = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class).getConfigMapping(TestConfig.class); + } catch (RuntimeException | Error e) { + System.out.println("HOLLY Config DOOOOOOM " + e); + System.out.println("HOLLY Config TCCL is " + Thread.currentThread().getContextClassLoader() + + " and the class of the mapping is " + + TestConfig.class.getClassLoader()); + System.out.println("HOLLY Config my class is " + this.getClass().getClassLoader()); + throw e; + } Optional> tags = testConfig.profile().tags(); if (tags.isEmpty() || tags.get().isEmpty()) { return ConditionEvaluationResult.enabled("No Quarkus Test Profile tags"); } - // Thread.currentThread().setContextClassLoader(original); + Thread.currentThread().setContextClassLoader(original); Class testProfile = getQuarkusTestProfile(context); if (testProfile == null) {