diff --git a/core/pom.xml b/core/pom.xml index 6200adc2b..d82584356 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -40,6 +40,10 @@ org.kohsuke akuma + + net.java.dev.jna + jna + io.github.classgraph classgraph diff --git a/core/src/main/java/org/restheart/graal/PluginsClassloaderInitFeature.java b/core/src/main/java/org/restheart/graal/PluginsClassloaderInitFeature.java new file mode 100644 index 000000000..126ecf8a3 --- /dev/null +++ b/core/src/main/java/org/restheart/graal/PluginsClassloaderInitFeature.java @@ -0,0 +1,50 @@ +/*- + * ========================LICENSE_START================================= + * restheart-core + * %% + * Copyright (C) 2014 - 2024 SoftInstigate + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * =========================LICENSE_END================================== + */ +/** WIP + * Automate reflect configuratio currently done via GenerateGraalvmReflectConfig + * + */ + +package org.restheart.graal; + +import org.graalvm.nativeimage.hosted.Feature; +import org.restheart.plugins.PluginsClassloader; +import org.restheart.plugins.PluginsScanner; + +/** + * Initializes PluginsClassloader with the native image classpath and triggers PluginsScanner + * + */ +public class PluginsClassloaderInitFeature implements Feature { + @Override + public void afterRegistration(AfterRegistrationAccess access) { + // System.out.println("***** afterRegistration " + access.getApplicationClassPath().toString()); + // initialize PluginsClassloader with the native image classpath + PluginsClassloader.init(access.getApplicationClassPath()); + // initialize PluginScanner class + PluginsScanner.initAtBuildTime(); + } + + @Override + public String getDescription() { + return "Initializes PluginsClassloader with the native image classpath and triggers PluginsScanner"; + } +} diff --git a/core/src/main/java/org/restheart/graal/PluginsReflectionRegistrationFeature.java b/core/src/main/java/org/restheart/graal/PluginsReflectionRegistrationFeature.java index acb003ef6..2adeee455 100644 --- a/core/src/main/java/org/restheart/graal/PluginsReflectionRegistrationFeature.java +++ b/core/src/main/java/org/restheart/graal/PluginsReflectionRegistrationFeature.java @@ -44,7 +44,12 @@ public class PluginsReflectionRegistrationFeature implements Feature { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - System.out.println("[PluginsReflectionRegistrationFeature] configuring reflection for:"); + if (PluginsScanner.allPluginsClassNames().isEmpty()) { + System.err.println("[PluginsReflectionRegistrationFeature] Error: no plugins found in classpath. This indicates a build misconfiguration, as at least the plugins in 'restheart-core' should be detected."); + throw new IllegalStateException("Error: No plugins found in classpath."); + } else { + System.out.println("[PluginsReflectionRegistrationFeature] configuring reflection for:"); + } PluginsScanner.allPluginsClassNames().stream() .map(this::clazz) diff --git a/core/src/main/java/org/restheart/plugins/PluginsClassloader.java b/core/src/main/java/org/restheart/plugins/PluginsClassloader.java index ff0e5d0e6..ac5737210 100644 --- a/core/src/main/java/org/restheart/plugins/PluginsClassloader.java +++ b/core/src/main/java/org/restheart/plugins/PluginsClassloader.java @@ -21,8 +21,13 @@ package org.restheart.plugins; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.List; + +import org.restheart.utils.LambdaUtils; /** * Loads a class, including searching within all plugin JAR files. @@ -52,6 +57,26 @@ public static void init(URL[] jars) { } } + public static void init(List paths) { + if (SINGLETON != null) { + throw new IllegalStateException("already initialized"); + } else { + var urls = paths.stream().map(p -> { + try { + return p.toUri().toURL(); + } catch(MalformedURLException murle) { + LambdaUtils.throwsSneakyException(murle); + return null; + } + }).toArray(size -> new URL[size]); + try { + SINGLETON = new PluginsClassloader(urls); + } catch(IOException ioe) { + throw new RuntimeException("error initializing", ioe); + } + } + } + public static boolean isInitialized() { return SINGLETON != null; } diff --git a/core/src/main/java/org/restheart/plugins/PluginsScanner.java b/core/src/main/java/org/restheart/plugins/PluginsScanner.java index c1bd38adf..30b4ec764 100644 --- a/core/src/main/java/org/restheart/plugins/PluginsScanner.java +++ b/core/src/main/java/org/restheart/plugins/PluginsScanner.java @@ -29,7 +29,6 @@ import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -82,40 +81,10 @@ public class PluginsScanner { private static final ArrayList SERVICES = new ArrayList<>(); private static final ArrayList PROVIDERS = new ArrayList<>(); - // ClassGraph.scan() at class initialization time to support native image - // generation with GraalVM - // see https://github.com/SoftInstigate/classgraph-on-graalvm static { - ClassGraph classGraph; - RuntimeClassGraph rtcg = null; - - if (ImageInfo.inImageBuildtimeCode()) { - // initizialize PluginsClassloader with the restheart jar - var jarPath = PluginsScanner.class.getProtectionDomain().getCodeSource().getLocation().getPath(); - try { - var jarFile = new File(jarPath); - var jarURL = jarFile.toURI().toURL(); - URL[] urls = { jarURL }; - - PluginsClassloader.init(urls); - } catch(MalformedURLException mue) { - System.err.println("Error initilizing PluginsClassloader on restheart uber jar " + jarPath + ". Exception: " + mue.getMessage()); - } - - final var cg = new ClassGraph(); - - classGraph = cg - .disableDirScanning() // added for GraalVM - .disableNestedJarScanning() // added for GraalVM - .disableRuntimeInvisibleAnnotations() // added for GraalVM - .overrideClassLoaders(PluginsClassloader.getInstance()) // added for GraalVM. Mandatory, otherwise build fails - .ignoreParentClassLoaders() - .enableAnnotationInfo().enableMethodInfo().enableFieldInfo().ignoreFieldVisibility().initializeLoadedClasses(); - - System.out.println("[PluginsScanner] Scanning plugins at build time with following classpath: " + cg.getClasspathURIs().stream().map(uri -> uri.getPath()).collect(Collectors.joining(File.pathSeparator))); - } else { - rtcg = new RuntimeClassGraph(); - classGraph = rtcg.get(); + if (!ImageInfo.inImageBuildtimeCode()) { + var rtcg = new RuntimeClassGraph(); + var classGraph = rtcg.get(); // apply plugins-scanning-verbose configuration option classGraph = classGraph.verbose(Bootstrapper.getConfiguration().coreModule().pluginsScanningVerbose()); // apply plugins-packages configuration option @@ -125,7 +94,44 @@ public class PluginsScanner { } rtcg.logStartScan(); + + try (var scanResult = classGraph.scan(Runtime.getRuntime().availableProcessors())) { + INITIALIZERS.addAll(collectPlugins(scanResult, INITIALIZER_CLASS_NAME)); + AUTH_MECHANISMS.addAll(collectPlugins(scanResult, AUTHMECHANISM_CLASS_NAME)); + AUTHORIZERS.addAll(collectPlugins(scanResult, AUTHORIZER_CLASS_NAME)); + TOKEN_MANAGERS.addAll(collectPlugins(scanResult, TOKEN_MANAGER_CLASS_NAME)); + AUTHENTICATORS.addAll(collectPlugins(scanResult, AUTHENTICATOR_CLASS_NAME)); + INTERCEPTORS.addAll(collectPlugins(scanResult, INTERCEPTOR_CLASS_NAME)); + SERVICES.addAll(collectPlugins(scanResult, SERVICE_CLASS_NAME)); + PROVIDERS.addAll(collectProviders(scanResult)); + } + + rtcg.logEndScan(); } + } + + // ClassGraph.scan() at class initialization time to support native image + // generation with GraalVM + // see https://github.com/SoftInstigate/classgraph-on-graalvm + public static void initAtBuildTime() { + if (!ImageInfo.inImageBuildtimeCode()) { + throw new IllegalStateException("Called initAtBuildTime() but we are not at build time"); + } + + // requires PluginsClassloader being initialized + // this is done by PluginsClassloaderInitFeature + + final var cg = new ClassGraph(); + + var classGraph = cg + .disableDirScanning() // added for GraalVM + .disableNestedJarScanning() // added for GraalVM + .disableRuntimeInvisibleAnnotations() // added for GraalVM + .overrideClassLoaders(PluginsClassloader.getInstance()) // added for GraalVM. Mandatory, otherwise build fails + .ignoreParentClassLoaders() + .enableAnnotationInfo().enableMethodInfo().enableFieldInfo().ignoreFieldVisibility().initializeLoadedClasses(); + + System.out.println("[PluginsScanner] Scanning plugins at build time with following classpath: " + cg.getClasspathURIs().stream().map(uri -> uri.getPath()).collect(Collectors.joining(File.pathSeparator))); try (var scanResult = classGraph.scan(Runtime.getRuntime().availableProcessors())) { INITIALIZERS.addAll(collectPlugins(scanResult, INITIALIZER_CLASS_NAME)); @@ -137,10 +143,6 @@ public class PluginsScanner { SERVICES.addAll(collectPlugins(scanResult, SERVICE_CLASS_NAME)); PROVIDERS.addAll(collectProviders(scanResult)); } - - if (rtcg != null) { - rtcg.logEndScan(); - } } public static List allPluginsClassNames() { diff --git a/core/src/main/resources/META-INF/native-image/org.restheart/restheart/jni-config.json b/core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/jni-config.json similarity index 100% rename from core/src/main/resources/META-INF/native-image/org.restheart/restheart/jni-config.json rename to core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/jni-config.json diff --git a/core/src/main/resources/META-INF/native-image/org.restheart/restheart/native-image.properties b/core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/native-image.properties similarity index 93% rename from core/src/main/resources/META-INF/native-image/org.restheart/restheart/native-image.properties rename to core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/native-image.properties index 2c3307c0c..848aeb895 100644 --- a/core/src/main/resources/META-INF/native-image/org.restheart/restheart/native-image.properties +++ b/core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/native-image.properties @@ -9,7 +9,7 @@ Args = --initialize-at-build-time=org.restheart.plugins.PluginsScanner,io.github --report-unsupported-elements-at-runtime \ --no-fallback \ --install-exit-handlers \ - --features=org.restheart.graal.PluginsReflectionRegistrationFeature \ + --features=org.restheart.graal.PluginsReflectionRegistrationFeature,org.restheart.graal.PluginsClassloaderInitFeature \ --add-exports=java.net.http/jdk.internal.net.http=org.graalvm.truffle \ --add-modules=org.graalvm.polyglot \ -march=native diff --git a/core/src/main/resources/META-INF/native-image/org.restheart/restheart/predefined-classes-config.json b/core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/predefined-classes-config.json similarity index 100% rename from core/src/main/resources/META-INF/native-image/org.restheart/restheart/predefined-classes-config.json rename to core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/predefined-classes-config.json diff --git a/core/src/main/resources/META-INF/native-image/org.restheart/restheart/proxy-config.json b/core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/proxy-config.json similarity index 100% rename from core/src/main/resources/META-INF/native-image/org.restheart/restheart/proxy-config.json rename to core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/proxy-config.json diff --git a/core/src/main/resources/META-INF/native-image/org.restheart/restheart/reflect-config.json b/core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/reflect-config.json similarity index 100% rename from core/src/main/resources/META-INF/native-image/org.restheart/restheart/reflect-config.json rename to core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/reflect-config.json diff --git a/core/src/main/resources/META-INF/native-image/org.restheart/restheart/resource-config.json b/core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/resource-config.json similarity index 98% rename from core/src/main/resources/META-INF/native-image/org.restheart/restheart/resource-config.json rename to core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/resource-config.json index 8e144f828..b7963536a 100644 --- a/core/src/main/resources/META-INF/native-image/org.restheart/restheart/resource-config.json +++ b/core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/resource-config.json @@ -89,7 +89,7 @@ "pattern":"\\Qlogback.xml\\E" }, { - "pattern":"\\Qlrestheart-default-config.json\\E" + "pattern":"\\Qrestheart-default-config.json\\E" }, { "pattern":"\\Qorg/apache/tika/mime/tika-mimetypes.xml\\E" diff --git a/core/src/main/resources/META-INF/native-image/org.restheart/restheart/serialization-config.json b/core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/serialization-config.json similarity index 100% rename from core/src/main/resources/META-INF/native-image/org.restheart/restheart/serialization-config.json rename to core/src/main/resources/META-INF/native-image/org.restheart/restheart-core/serialization-config.json