From c545fc2eb1e794ed60fbde73818ef3002fe8f910 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Tue, 9 Feb 2021 10:34:27 +1100 Subject: [PATCH] Fix ClassLoader getParent() hack This replaces the hack where QuarkusClassLoader.getParent() returns null, and fixes it with some smaller more limited hacks around the problematic API's. Fixes #14898 --- ...tinyReactiveStreamsOperatorsProcessor.java | 9 +++ .../ReactiveStreamsOperatorsRecorder.java | 58 +++++++++++++++++++ ...lRyeReactiveStreamsOperatorsProcessor.java | 9 +++ .../ReactiveStreamsOperatorsRecorder.java | 58 +++++++++++++++++++ .../deployment/SmallRyeOpenApiProcessor.java | 6 ++ .../openapi/runtime/OpenApiRecorder.java | 49 +++++++++++++++- .../classloading/QuarkusClassLoader.java | 43 +++++++++----- .../io/quarkus/it/mockbean/SuffixService.java | 2 +- 8 files changed, 218 insertions(+), 16 deletions(-) create mode 100644 extensions/reactive-streams-operators/mutiny-reactive-streams-operators/runtime/src/main/java/io/quarkus/mutiny/reactive/operators/runtime/ReactiveStreamsOperatorsRecorder.java create mode 100644 extensions/reactive-streams-operators/smallrye-reactive-streams-operators/runtime/src/main/java/io/quarkus/smallrye/reactivestreamsoperators/runtime/ReactiveStreamsOperatorsRecorder.java diff --git a/extensions/reactive-streams-operators/mutiny-reactive-streams-operators/deployment/src/main/java/io/quarkus/mutiny/reactive/operators/deployment/MutinyReactiveStreamsOperatorsProcessor.java b/extensions/reactive-streams-operators/mutiny-reactive-streams-operators/deployment/src/main/java/io/quarkus/mutiny/reactive/operators/deployment/MutinyReactiveStreamsOperatorsProcessor.java index c8d30dcda7412..ea0e6b962f824 100644 --- a/extensions/reactive-streams-operators/mutiny-reactive-streams-operators/deployment/src/main/java/io/quarkus/mutiny/reactive/operators/deployment/MutinyReactiveStreamsOperatorsProcessor.java +++ b/extensions/reactive-streams-operators/mutiny-reactive-streams-operators/deployment/src/main/java/io/quarkus/mutiny/reactive/operators/deployment/MutinyReactiveStreamsOperatorsProcessor.java @@ -6,7 +6,10 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; +import io.quarkus.mutiny.reactive.operators.runtime.ReactiveStreamsOperatorsRecorder; import io.smallrye.mutiny.streams.Engine; public class MutinyReactiveStreamsOperatorsProcessor { @@ -19,4 +22,10 @@ public void build(BuildProducer serviceProvider) { ReactiveStreamsFactoryImpl.class.getName())); } + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void classLoadingHack(ReactiveStreamsOperatorsRecorder reactiveStreamsOperatorsRecorder) { + reactiveStreamsOperatorsRecorder.classLoaderHack(); + } + } diff --git a/extensions/reactive-streams-operators/mutiny-reactive-streams-operators/runtime/src/main/java/io/quarkus/mutiny/reactive/operators/runtime/ReactiveStreamsOperatorsRecorder.java b/extensions/reactive-streams-operators/mutiny-reactive-streams-operators/runtime/src/main/java/io/quarkus/mutiny/reactive/operators/runtime/ReactiveStreamsOperatorsRecorder.java new file mode 100644 index 0000000000000..a0ab776c1cf1c --- /dev/null +++ b/extensions/reactive-streams-operators/mutiny-reactive-streams-operators/runtime/src/main/java/io/quarkus/mutiny/reactive/operators/runtime/ReactiveStreamsOperatorsRecorder.java @@ -0,0 +1,58 @@ +package io.quarkus.mutiny.reactive.operators.runtime; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +import org.eclipse.microprofile.reactive.streams.operators.core.ReactiveStreamsEngineResolver; +import org.eclipse.microprofile.reactive.streams.operators.spi.ReactiveStreamsFactoryResolver; + +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class ReactiveStreamsOperatorsRecorder { + + /** + * ClassLoader hack to work around reactive streams API issue + * see https://github.com/eclipse/microprofile-reactive-streams-operators/pull/130 + * + * This must be deleted when Reactive Streams Operators 1.1 is merged + */ + public void classLoaderHack() { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(new ClassLoader(null) { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return cl.loadClass(name); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + return cl.loadClass(name); + } + + @Override + public URL getResource(String name) { + return cl.getResource(name); + } + + @Override + public Enumeration getResources(String name) throws IOException { + return cl.getResources(name); + } + + @Override + public InputStream getResourceAsStream(String name) { + return cl.getResourceAsStream(name); + } + }); + ReactiveStreamsFactoryResolver.instance(); + ReactiveStreamsEngineResolver.instance(); + } finally { + Thread.currentThread().setContextClassLoader(cl); + } + + } +} diff --git a/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/deployment/src/main/java/io/quarkus/smallrye/reactivestreamoperators/deployment/SmallRyeReactiveStreamsOperatorsProcessor.java b/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/deployment/src/main/java/io/quarkus/smallrye/reactivestreamoperators/deployment/SmallRyeReactiveStreamsOperatorsProcessor.java index c5ee5b2b5ef3e..7e532e0091380 100644 --- a/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/deployment/src/main/java/io/quarkus/smallrye/reactivestreamoperators/deployment/SmallRyeReactiveStreamsOperatorsProcessor.java +++ b/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/deployment/src/main/java/io/quarkus/smallrye/reactivestreamoperators/deployment/SmallRyeReactiveStreamsOperatorsProcessor.java @@ -7,8 +7,11 @@ import io.quarkus.deployment.Feature; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; +import io.quarkus.smallrye.reactivestreamsoperators.runtime.ReactiveStreamsOperatorsRecorder; import io.smallrye.reactive.streams.Engine; public class SmallRyeReactiveStreamsOperatorsProcessor { @@ -22,4 +25,10 @@ public void build(BuildProducer serviceProvider, ReactiveStreamsFactoryImpl.class.getName())); } + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void classLoadingHack(ReactiveStreamsOperatorsRecorder reactiveStreamsOperatorsRecorder) { + reactiveStreamsOperatorsRecorder.classLoaderHack(); + } + } diff --git a/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/runtime/src/main/java/io/quarkus/smallrye/reactivestreamsoperators/runtime/ReactiveStreamsOperatorsRecorder.java b/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/runtime/src/main/java/io/quarkus/smallrye/reactivestreamsoperators/runtime/ReactiveStreamsOperatorsRecorder.java new file mode 100644 index 0000000000000..66f5adbe71997 --- /dev/null +++ b/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/runtime/src/main/java/io/quarkus/smallrye/reactivestreamsoperators/runtime/ReactiveStreamsOperatorsRecorder.java @@ -0,0 +1,58 @@ +package io.quarkus.smallrye.reactivestreamsoperators.runtime; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +import org.eclipse.microprofile.reactive.streams.operators.core.ReactiveStreamsEngineResolver; +import org.eclipse.microprofile.reactive.streams.operators.spi.ReactiveStreamsFactoryResolver; + +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class ReactiveStreamsOperatorsRecorder { + + /** + * ClassLoader hack to work around reactive streams API issue + * see https://github.com/eclipse/microprofile-reactive-streams-operators/pull/130 + * + * This must be deleted when Reactive Streams Operators 1.1 is merged + */ + public void classLoaderHack() { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(new ClassLoader(null) { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return cl.loadClass(name); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + return cl.loadClass(name); + } + + @Override + public URL getResource(String name) { + return cl.getResource(name); + } + + @Override + public Enumeration getResources(String name) throws IOException { + return cl.getResources(name); + } + + @Override + public InputStream getResourceAsStream(String name) { + return cl.getResourceAsStream(name); + } + }); + ReactiveStreamsFactoryResolver.instance(); + ReactiveStreamsEngineResolver.instance(); + } finally { + Thread.currentThread().setContextClassLoader(cl); + } + + } +} diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java index 02962090e64ea..d566cb96b4073 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java @@ -180,6 +180,12 @@ RouteBuildItem handler(LaunchModeBuildItem launch, .build(); } + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void classLoaderHack(OpenApiRecorder recorder) { + recorder.classLoaderHack(); + } + @BuildStep void additionalBean(BuildProducer additionalBeanProducer) { additionalBeanProducer.produce(AdditionalBeanBuildItem.builder() diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java index 9e7463ec7761b..28919339dbb9d 100644 --- a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java @@ -1,5 +1,12 @@ package io.quarkus.smallrye.openapi.runtime; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +import org.eclipse.microprofile.openapi.spi.OASFactoryResolver; + import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; import io.vertx.core.Handler; @@ -9,7 +16,6 @@ public class OpenApiRecorder { public Handler handler(OpenApiRuntimeConfig runtimeConfig) { - if (runtimeConfig.enable) { return new OpenApiHandler(); } else { @@ -27,4 +33,45 @@ public void run() { }); } + /** + * ClassLoader hack to work around reactive streams API issue + * see https://github.com/eclipse/microprofile-open-api/pull/470 + *

+ * This must be deleted when it is fixed upstream + */ + public void classLoaderHack() { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(new ClassLoader(null) { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return cl.loadClass(name); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + return cl.loadClass(name); + } + + @Override + public URL getResource(String name) { + return cl.getResource(name); + } + + @Override + public Enumeration getResources(String name) throws IOException { + return cl.getResources(name); + } + + @Override + public InputStream getResourceAsStream(String name) { + return cl.getResourceAsStream(name); + } + }); + OASFactoryResolver.instance(); + } finally { + Thread.currentThread().setContextClassLoader(cl); + } + + } } 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 8a914decce32e..9fcfed8c85a76 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 @@ -42,6 +42,7 @@ public class QuarkusClassLoader extends ClassLoader implements Closeable { private final String name; private final List elements; private final ConcurrentMap protectionDomains = new ConcurrentHashMap<>(); + private final ConcurrentMap definedPackages = new ConcurrentHashMap<>(); private final ClassLoader parent; /** * If this is true it will attempt to load from the parent first @@ -87,15 +88,7 @@ public class QuarkusClassLoader extends ClassLoader implements Closeable { private volatile boolean driverLoaded; private QuarkusClassLoader(Builder builder) { - //we need the parent to be null - //as MP has super broken class loading where it attempts to resolve stuff from the parent - //will hopefully be fixed in 1.4 - //e.g. https://github.com/eclipse/microprofile-config/issues/390 - //e.g. https://github.com/eclipse/microprofile-reactive-streams-operators/pull/130 - //to further complicate things we also have https://github.com/quarkusio/quarkus/issues/8985 - //where getParent must work to load JDK services on JDK9+ - //to get around this we pass in the platform ClassLoader, if it exists - super(PLATFORM_CLASS_LOADER); + super(new GetPackageBlockingClassLoader(builder.parent)); this.name = builder.name; this.elements = builder.elements; this.bannedElements = builder.bannedElements; @@ -440,23 +433,25 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE private void definePackage(String name, ClassPathElement classPathElement) { final String pkgName = getPackageNameFromClassName(name); - if ((pkgName != null) && getPackage(pkgName) == null) { + //we can't use getPackage here + //if can return a package from the parent + if ((pkgName != null) && definedPackages.get(pkgName) == null) { synchronized (getClassLoadingLock(pkgName)) { - if (getPackage(pkgName) == null) { + if (definedPackages.get(pkgName) == null) { Manifest mf = classPathElement.getManifest(); if (mf != null) { Attributes ma = mf.getMainAttributes(); - definePackage(pkgName, ma.getValue(Attributes.Name.SPECIFICATION_TITLE), + definedPackages.put(pkgName, definePackage(pkgName, ma.getValue(Attributes.Name.SPECIFICATION_TITLE), ma.getValue(Attributes.Name.SPECIFICATION_VERSION), ma.getValue(Attributes.Name.SPECIFICATION_VENDOR), ma.getValue(Attributes.Name.IMPLEMENTATION_TITLE), ma.getValue(Attributes.Name.IMPLEMENTATION_VERSION), - ma.getValue(Attributes.Name.IMPLEMENTATION_VENDOR), null); + ma.getValue(Attributes.Name.IMPLEMENTATION_VENDOR), null)); return; } // this could certainly be improved to use the actual manifest - definePackage(pkgName, null, null, null, null, null, null, null); + definedPackages.put(pkgName, definePackage(pkgName, null, null, null, null, null, null, null)); } } } @@ -706,4 +701,24 @@ static final class ClassLoaderState { this.parentFirstResources = parentFirstResources; } } + + /** + * Horrible JDK8 hack + * + * getPackage() on JDK8 will always delegate to the parent, so QuarkusClassLoader will never define a package, + * which can cause issues if you then try and read package annotations as they will be from the wrong ClassLoader. + * + * We add this ClassLoader into the mix to block the getPackage() delegation. + */ + private static class GetPackageBlockingClassLoader extends ClassLoader { + + protected GetPackageBlockingClassLoader(ClassLoader parent) { + super(parent); + } + + @Override + protected Package getPackage(String name) { + return null; + } + } } diff --git a/integration-tests/injectmock/src/main/java/io/quarkus/it/mockbean/SuffixService.java b/integration-tests/injectmock/src/main/java/io/quarkus/it/mockbean/SuffixService.java index bb0728476173c..9abe827814401 100644 --- a/integration-tests/injectmock/src/main/java/io/quarkus/it/mockbean/SuffixService.java +++ b/integration-tests/injectmock/src/main/java/io/quarkus/it/mockbean/SuffixService.java @@ -5,7 +5,7 @@ @ApplicationScoped public class SuffixService { - public String getSuffix() { + String getSuffix() { return ""; } }