From f3b381a65ff80fe6ffea51d0c48cdb383c67ef3c Mon Sep 17 00:00:00 2001 From: Robert Scholte Date: Wed, 7 Aug 2024 01:12:19 +0200 Subject: [PATCH] Allow resolving Micronaut beans definitions in a modular application (#10982) Uses the `jrt` URI protocol to resolve Micronaut bean definitions from modules if the protocol available. Since this protocol is not available on native image the PR catches the exception and ignores the provider being missing. This fixes #10842 --------- Co-authored-by: Graeme Rocher --- .../java/io/micronaut/core/io/IOUtils.java | 4 ++ .../scan/DefaultClassPathResourceLoader.java | 20 ++++++-- .../core/io/service/ServiceLoaderFeature.java | 2 +- .../core/io/service/ServiceScanner.java | 36 +++++++++++++++ .../io/service/SoftServiceLoaderTest.java | 43 ++++++++++++++++++ core/src/test/resources/test.jar | Bin 0 -> 1520 bytes 6 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 core/src/test/java/io/micronaut/core/io/service/SoftServiceLoaderTest.java create mode 100644 core/src/test/resources/test.jar diff --git a/core/src/main/java/io/micronaut/core/io/IOUtils.java b/core/src/main/java/io/micronaut/core/io/IOUtils.java index 6541907b464..0a7c13420d5 100644 --- a/core/src/main/java/io/micronaut/core/io/IOUtils.java +++ b/core/src/main/java/io/micronaut/core/io/IOUtils.java @@ -41,6 +41,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.function.Consumer; /** @@ -165,6 +166,9 @@ static Path resolvePath(@NonNull URI uri, return loadNestedJarUriFunction.apply(toClose, jarUri).resolve(path); } else if ("file".equals(scheme)) { return Paths.get(uri).resolve(path); + } else if ("jrt".equals(scheme)) { + FileSystem fs = FileSystems.newFileSystem(URI.create("jrt:/"), Map.of()); + return fs.getPath(uri.getPath()); } else { // graal resource: case return Paths.get(uri); diff --git a/core/src/main/java/io/micronaut/core/io/scan/DefaultClassPathResourceLoader.java b/core/src/main/java/io/micronaut/core/io/scan/DefaultClassPathResourceLoader.java index 2c242e500a8..bfc3ff679c5 100644 --- a/core/src/main/java/io/micronaut/core/io/scan/DefaultClassPathResourceLoader.java +++ b/core/src/main/java/io/micronaut/core/io/scan/DefaultClassPathResourceLoader.java @@ -60,7 +60,7 @@ public class DefaultClassPathResourceLoader implements ClassPathResourceLoader { private final String basePath; private final URL baseURL; private final Map isDirectoryCache = new ConcurrentLinkedHashMap.Builder() - .maximumWeightedCapacity(50).build(); + .maximumWeightedCapacity(50).build(); private final boolean missingPath; private final boolean checkBase; @@ -100,7 +100,7 @@ public DefaultClassPathResourceLoader(ClassLoader classLoader, String basePath, * @param classLoader The class loader for loading resources * @param basePath The path to look for resources under * @param checkBase If set to {@code true} an extended check for the base path is performed otherwise paths with relative URLs like {@code ../} are prohibited. - * @param logEnabled flag to enable or disable logger + * @param logEnabled flag to enable or disable logger */ public DefaultClassPathResourceLoader(ClassLoader classLoader, String basePath, boolean checkBase, boolean logEnabled) { @@ -290,9 +290,8 @@ public ResourceLoader forBase(String basePath) { /** * Need this method to ability disable Slf4J initizalization. * - * @param basePath The path to load resources + * @param basePath The path to load resources * @param logEnabled flag to enable or disable logger - * * @return The resource loader */ public ResourceLoader forBase(String basePath, boolean logEnabled) { @@ -348,6 +347,19 @@ private boolean isDirectory(String path) { } } } + } else if ("jrt".equals(uri.getScheme())) { + FileSystem fileSystem = null; + try { + fileSystem = FileSystems.getFileSystem(URI.create("jrt:/")); + } catch (FileSystemNotFoundException e) { + //no-op + } + if (fileSystem == null || !fileSystem.isOpen()) { + fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap(), classLoader); + } + + pathObject = fileSystem.getPath(path); + return pathObject == null || Files.isDirectory(pathObject); } else if (uri.getScheme().equals("file")) { pathObject = Paths.get(uri); return pathObject == null || Files.isDirectory(pathObject); diff --git a/core/src/main/java/io/micronaut/core/io/service/ServiceLoaderFeature.java b/core/src/main/java/io/micronaut/core/io/service/ServiceLoaderFeature.java index 637561c33f4..8f6a2ff98b2 100644 --- a/core/src/main/java/io/micronaut/core/io/service/ServiceLoaderFeature.java +++ b/core/src/main/java/io/micronaut/core/io/service/ServiceLoaderFeature.java @@ -179,7 +179,7 @@ protected void registerRuntimeReflection(Field... fields) { } /** - * Initialize a class at build time + * Initialize a class at build time. * @param buildInitClass The class */ protected void initializeAtBuildTime(@Nullable Class buildInitClass) { diff --git a/core/src/main/java/io/micronaut/core/io/service/ServiceScanner.java b/core/src/main/java/io/micronaut/core/io/service/ServiceScanner.java index 4bca150cd90..93d5962800a 100644 --- a/core/src/main/java/io/micronaut/core/io/service/ServiceScanner.java +++ b/core/src/main/java/io/micronaut/core/io/service/ServiceScanner.java @@ -18,6 +18,9 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.io.IOUtils; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.ProviderNotFoundException; +import java.util.stream.Stream; import org.graalvm.nativeimage.ImageSingletons; import java.io.BufferedReader; @@ -27,6 +30,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -187,6 +192,37 @@ private void findMicronautMetaServiceConfigs(BiConsumer consumer) t uniqueURIs.add(uri); } + if (uniqueURIs.isEmpty()) { + FileSystem fs = null; + try { + fs = FileSystems.getFileSystem(URI.create("jrt:/")); + } catch (FileSystemNotFoundException | ProviderNotFoundException e) { + //no-op + } + if (fs == null || !fs.isOpen()) { + try { + fs = FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap(), classLoader); + } catch (IOException | ProviderNotFoundException e) { + // not available, probably running in Native Image. + } + } + if (fs != null) { + Path modulesPath = fs.getPath("modules"); + try (Stream stream = Files.list(modulesPath)) { + stream + .filter(p -> !p.getFileName().toString().startsWith("jdk.")) // filter out JDK internal modules + .filter(p -> !p.getFileName().toString().startsWith("java.")) // filter out JDK public modules + .map(p -> p.resolve(path)) + .filter(Files::exists) + .map(modulesPath::resolve) + .map(Path::toUri) + .forEach(uniqueURIs::add); + } + + // uri will be jrt:/modules//META-INF/micronaut/, so we can walk through its files as if it was a directory + } + } + for (URI uri : uniqueURIs) { String scheme = uri.getScheme(); if ("file".equals(scheme)) { diff --git a/core/src/test/java/io/micronaut/core/io/service/SoftServiceLoaderTest.java b/core/src/test/java/io/micronaut/core/io/service/SoftServiceLoaderTest.java new file mode 100644 index 00000000000..ede7f26ed99 --- /dev/null +++ b/core/src/test/java/io/micronaut/core/io/service/SoftServiceLoaderTest.java @@ -0,0 +1,43 @@ +package io.micronaut.core.io.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.module.Configuration; +import java.lang.module.ModuleFinder; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import io.micronaut.core.io.service.SoftServiceLoader.ServiceCollector; + +public class SoftServiceLoaderTest { + + @Test + void findServicesUsingJrtScheme() { + String modulename = "io.micronaut.core.test"; + ModuleFinder finder = ModuleFinder.of(Path.of("build/resources/test/test.jar")); + ModuleLayer parent = ModuleLayer.boot(); + Configuration cf = parent.configuration().resolve(finder, ModuleFinder.of(), Set.of(modulename)); + ClassLoader scl = ClassLoader.getSystemClassLoader(); + ModuleLayer layer = parent.defineModulesWithOneLoader(cf, scl); + + ClassLoader oldLoader = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(layer.findLoader(modulename)); + + ServiceCollector collector = SoftServiceLoader.newCollector("io.micronaut.inject.BeanDefinitionReference", null, layer.findLoader(modulename), Function.identity()); + List services = new ArrayList<>(); + collector.collect(services::add); + + assertEquals(1, services.size()); + assertEquals("io.micronaut.logging.$PropertiesLoggingLevelsConfigurer$Definition", services.get(0)); + } + finally { + Thread.currentThread().setContextClassLoader(oldLoader); + } + } +} diff --git a/core/src/test/resources/test.jar b/core/src/test/resources/test.jar new file mode 100644 index 0000000000000000000000000000000000000000..2e4fe64adb55225fd57f945bdfacc45c430d5be4 GIT binary patch literal 1520 zcmb_cO-sW-5S>~-3l+g0JQnOlq*3u?FC}U#rA;X+B6yG(r(vbtEt|x9e}+fT`cpiL zi2p%6daSdFrY**#q7(Kb$AoO^YZ|pQv;atdw>NRjmoiJtTn3}X=u^H zjQ{HS?Co^t;C!`8cdAkPaG{|$Yt_opv1U{ale0EDZt(=A!vrtox3tY{M)zFNHC<*E z4RPspu-G)aSjsb@b(tjvH$7Lgghb7y+-yeSMiX2f#SL>msYa(Lx}m0F%6IGwG`XYg zQeWe)6iyE3DgVJp7e`JxIfx|~=(jWtQHCas z+d%m`2(`f@O(QlKE3tpV5?vmLsa*aQgMNtw{}1LE%>>WK@l&1;Le=D90)7c6AP%hm kg+=3_zfS+qq+%q1MOWcB=b=@fnN_Uj=vqtzJorEM4@a1L$p8QV literal 0 HcmV?d00001