diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/nativeimage/GoogleJsonClientFeature.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/nativeimage/GoogleJsonClientFeature.java index 18f839547888..ed2f8b4c0eea 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/nativeimage/GoogleJsonClientFeature.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/nativeimage/GoogleJsonClientFeature.java @@ -30,7 +30,10 @@ package com.google.api.gax.nativeimage; +import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassForJni; import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassForReflection; +import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassHierarchyForJni; +import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassHierarchyForReflection; import org.graalvm.nativeimage.hosted.Feature; import org.jspecify.annotations.NullMarked; @@ -51,9 +54,65 @@ final class GoogleJsonClientFeature implements Feature { public void beforeAnalysis(BeforeAnalysisAccess access) { loadApiClient(access); loadHttpClient(access); + loadConscrypt(access); loadMiscClasses(access); } + /** + * Registers Conscrypt SSLContext and Security Provider SPI implementation classes (and their + * nested protocol subclasses) for GraalVM reflection, as well as Conscrypt native JNI classes and + * native C shared library resources when Conscrypt is present on the classpath. + * + *
When Conscrypt is configured as the security provider for HTTP/JSON transports, Java's JCA + * framework reflectively instantiates provider implementation classes (e.g. {@code + * OpenSSLContextImpl$TLSv13}) via String lookup in {@code SSLContext.getInstance("TLS", + * provider)}. In GraalVM Native Image builds, these reflectively looked-up SPI classes are + * stripped by static analysis unless explicitly registered for reflection, leading to {@code + * ClassNotFoundException} / {@code NoSuchAlgorithmException} at runtime. Additionally, native JNI + * methods and JDK classes accessed by Conscrypt's native C library must be registered for GraalVM + * JNI linkage. + */ + private void loadConscrypt(BeforeAnalysisAccess access) { + Class> conscryptClass = access.findClassByName("org.conscrypt.Conscrypt"); + if (conscryptClass != null) { + registerClassHierarchyForReflection(access, "org.conscrypt.OpenSSLContextImpl"); + registerClassHierarchyForReflection(access, "org.conscrypt.OpenSSLProvider"); + registerClassHierarchyForReflection(access, "org.conscrypt.KeyManagerFactoryImpl"); + registerClassHierarchyForReflection(access, "org.conscrypt.TrustManagerFactoryImpl"); + + // Register Conscrypt native JNI bridge classes and hierarchy + registerClassHierarchyForReflection(access, "org.conscrypt.NativeCrypto"); + registerClassHierarchyForReflection(access, "org.conscrypt.NativeCryptoJni"); + registerClassHierarchyForReflection(access, "org.conscrypt.CryptoUpcalls"); + registerClassHierarchyForReflection(access, "org.conscrypt.NativeRef"); + registerClassHierarchyForReflection(access, "org.conscrypt.OpenSSLBIOInputStream"); + + registerClassHierarchyForJni(access, "org.conscrypt.NativeCrypto"); + registerClassHierarchyForJni(access, "org.conscrypt.NativeCryptoJni"); + registerClassHierarchyForJni(access, "org.conscrypt.CryptoUpcalls"); + registerClassHierarchyForJni(access, "org.conscrypt.NativeRef"); + registerClassHierarchyForJni(access, "org.conscrypt.OpenSSLBIOInputStream"); + + // Register array types and JDK classes accessed via JNI by Conscrypt native C code + registerClassForJni(Object[].class); + registerClassForJni(byte[].class); + registerClassForJni(byte[][].class); + registerClassForJni(int[].class); + + registerClassHierarchyForJni(access, "java.util.Calendar"); + registerClassHierarchyForJni(access, "java.io.InputStream"); + registerClassHierarchyForJni(access, "java.io.OutputStream"); + registerClassHierarchyForJni(access, "java.io.FileDescriptor"); + registerClassHierarchyForJni(access, "java.lang.Integer"); + registerClassHierarchyForJni(access, "java.lang.String"); + registerClassHierarchyForJni(access, "java.lang.Object"); + registerClassHierarchyForJni(access, "java.nio.Buffer"); + registerClassHierarchyForJni(access, "java.nio.ByteBuffer"); + registerClassHierarchyForJni(access, "java.security.PrivateKey"); + registerClassHierarchyForJni(access, "java.security.Key"); + } + } + private void loadApiClient(BeforeAnalysisAccess access) { // For com.google.api-client:google-api-client Class> googleApiClientClass = access.findClassByName(GOOGLE_API_CLIENT_CLASS); diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/nativeimage/NativeImageUtils.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/nativeimage/NativeImageUtils.java index 3365daa3aee9..70c277e3e681 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/nativeimage/NativeImageUtils.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/nativeimage/NativeImageUtils.java @@ -36,6 +36,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.graalvm.nativeimage.hosted.Feature.FeatureAccess; +import org.graalvm.nativeimage.hosted.RuntimeJNIAccess; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.jspecify.annotations.NullMarked; @@ -85,19 +86,62 @@ public static void registerConstructorsForReflection(FeatureAccess access, Strin } } + /** Registers an entire class for reflection use. */ + public static void registerClassForReflection(Class> clazz) { + RuntimeReflection.register(clazz); + RuntimeReflection.register(clazz.getDeclaredConstructors()); + RuntimeReflection.register(clazz.getDeclaredFields()); + RuntimeReflection.register(clazz.getDeclaredMethods()); + } + /** Registers an entire class for reflection use. */ public static void registerClassForReflection(FeatureAccess access, String name) { Class> clazz = access.findClassByName(name); if (clazz != null) { - RuntimeReflection.register(clazz); - RuntimeReflection.register(clazz.getDeclaredConstructors()); - RuntimeReflection.register(clazz.getDeclaredFields()); - RuntimeReflection.register(clazz.getDeclaredMethods()); + registerClassForReflection(clazz); } else { LOGGER.log(Level.WARNING, CLASS_REFLECTION_ERROR_MESSAGE, name); } } + /** Registers an entire class for JNI use. */ + public static void registerClassForJni(Class> clazz) { + RuntimeJNIAccess.register(clazz); + RuntimeJNIAccess.register(clazz.getDeclaredConstructors()); + RuntimeJNIAccess.register(clazz.getDeclaredFields()); + RuntimeJNIAccess.register(clazz.getDeclaredMethods()); + } + + /** Registers an entire class for JNI use. */ + public static void registerClassForJni(FeatureAccess access, String name) { + Class> clazz = access.findClassByName(name); + if (clazz != null) { + registerClassForJni(clazz); + } else { + LOGGER.log(Level.WARNING, CLASS_REFLECTION_ERROR_MESSAGE, name); + } + } + + /** + * Registers the transitive class hierarchy of the provided {@code className} for JNI use. + * + *
The transitive class hierarchy contains the class itself and its transitive set of + * *non-private* nested subclasses. + */ + public static void registerClassHierarchyForJni(FeatureAccess access, String className) { + Class> clazz = access.findClassByName(className); + if (clazz != null) { + registerClassForJni(access, className); + for (Class> nestedClass : clazz.getDeclaredClasses()) { + if (!Modifier.isPrivate(nestedClass.getModifiers())) { + registerClassHierarchyForJni(access, nestedClass.getName()); + } + } + } else { + LOGGER.log(Level.WARNING, CLASS_REFLECTION_ERROR_MESSAGE, className); + } + } + /** * Registers the transitive class hierarchy of the provided {@code className} for reflection. * diff --git a/sdk-platform-java/gax-java/gax/src/main/resources/META-INF/native-image/com.google.api/gax/native-image.properties b/sdk-platform-java/gax-java/gax/src/main/resources/META-INF/native-image/com.google.api/gax/native-image.properties index 5d11a38791f8..a31e2ab9b761 100644 --- a/sdk-platform-java/gax-java/gax/src/main/resources/META-INF/native-image/com.google.api/gax/native-image.properties +++ b/sdk-platform-java/gax-java/gax/src/main/resources/META-INF/native-image/com.google.api/gax/native-image.properties @@ -1,6 +1,6 @@ Args = --enable-url-protocols=https,http \ ---initialize-at-build-time=org.conscrypt,\ - org.junit.platform.engine.TestTag,\ +--initialize-at-run-time=org.conscrypt \ +--initialize-at-build-time=org.junit.platform.engine.TestTag,\ com.google.api.gax.core.GaxProperties,\ com.google.common.base.Platform,\ com.google.common.base.Platform$JdkPatternCompiler,\ diff --git a/sdk-platform-java/gax-java/gax/src/main/resources/META-INF/native-image/com.google.api/gax/resource-config.json b/sdk-platform-java/gax-java/gax/src/main/resources/META-INF/native-image/com.google.api/gax/resource-config.json index bc40a95d55ef..604cb8f06919 100644 --- a/sdk-platform-java/gax-java/gax/src/main/resources/META-INF/native-image/com.google.api/gax/resource-config.json +++ b/sdk-platform-java/gax-java/gax/src/main/resources/META-INF/native-image/com.google.api/gax/resource-config.json @@ -4,6 +4,7 @@ {"pattern":"\\Qdependencies.properties\\E"}, {"pattern":"\\Qcom/google/api/client/googleapis/google-api-client.properties\\E"}, {"pattern":"\\Qcom/google/api/client/googleapis/google.p12\\E"}, - {"pattern":"\\Qcom/google/api/client/http/google-http-client.properties\\E"}], + {"pattern":"\\Qcom/google/api/client/http/google-http-client.properties\\E"}, + {"pattern":".*conscrypt_openjdk_jni.*"}], "bundles":[] }