Skip to content

Commit

Permalink
Add GraalVM Native Image support (#856)
Browse files Browse the repository at this point in the history
Fixes #468, #673, #1021, #1040

---------

Co-authored-by: Maciej Walkowiak <walkowiak.maciej@yahoo.com>
  • Loading branch information
MatejNedic and maciejwalkowiak authored Dec 10, 2024
1 parent 98a7863 commit a08ebc1
Show file tree
Hide file tree
Showing 19 changed files with 430 additions and 3 deletions.
9 changes: 9 additions & 0 deletions docs/src/main/asciidoc/core.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,12 @@ There can be multiple customizer beans present in single application context and

Client-specific customizations can be applied through client-specific customizer interfaces (for example `S3ClientCustomizer` for S3). See integrations documentation for details.


=== GraalVM Native Image

Since version 3.3.0 the framework provides **experimental** support for GraalVM Native Image build.

Known issues are:

- in DynamoDB integration, `StaticTableSchema` must be used instead of `DynamicTableSchema` (see https://github.com/aws/aws-sdk-java-v2/issues/2445)
- in S3 integration, when working with CRT client, following guide must be followed: https://github.com/awslabs/aws-crt-java?tab=readme-ov-file#graalvm-support
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.awspring.cloud.autoconfigure.config;

import io.awspring.cloud.autoconfigure.config.parameterstore.ParameterStorePropertySources;
import io.awspring.cloud.autoconfigure.config.secretsmanager.SecretsManagerPropertySources;
import io.awspring.cloud.parameterstore.ParameterStorePropertySource;
import io.awspring.cloud.secretsmanager.SecretsManagerPropertySource;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.util.ClassUtils;

public class ConfigStoreRuntimeHints implements RuntimeHintsRegistrar {

@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
if (ClassUtils.isPresent("io.awspring.cloud.parameterstore.ParameterStorePropertySource", classLoader)) {
hints.reflection().registerType(TypeReference.of(ParameterStorePropertySources.class),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
hints.reflection().registerType(TypeReference.of(ParameterStorePropertySource.class),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
}

if (ClassUtils.isPresent("io.awspring.cloud.secretsmanager.SecretsManagerPropertySource", classLoader)) {
hints.reflection().registerType(TypeReference.of(SecretsManagerPropertySources.class),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));

hints.reflection().registerType(TypeReference.of(SecretsManagerPropertySource.class),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.awspring.cloud.autoconfigure.core;

import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.util.ClassUtils;

public class CoreAOT implements RuntimeHintsRegistrar {

private static final String STS_WEB_IDENTITY_TOKEN_FILE_CREDENTIALS_PROVIDER = "software.amazon.awssdk.services.sts.auth.StsWebIdentityTokenFileCredentialsProvider";

@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
if (ClassUtils.isPresent(STS_WEB_IDENTITY_TOKEN_FILE_CREDENTIALS_PROVIDER, classLoader)) {
hints.reflection().registerType(TypeReference.of(STS_WEB_IDENTITY_TOKEN_FILE_CREDENTIALS_PROVIDER),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
io.awspring.cloud.autoconfigure.config.ConfigStoreRuntimeHints,\
io.awspring.cloud.autoconfigure.core.CoreAOT
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.awspring.cloud.core;

import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;

public class AWSCoreRuntimeHints implements RuntimeHintsRegistrar {

@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.resources().registerPattern("io/awspring/cloud/core/SpringCloudClientConfiguration.properties");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,4 @@ public ClientOverrideConfiguration clientOverrideConfiguration() {
private String getUserAgent() {
return NAME + "/" + version;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
io.awspring.cloud.core.AWSCoreRuntimeHints
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
io.awspring.cloud.s3.S3RuntimeHints
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.awspring.cloud.s3;

import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;

public class S3RuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.resources().registerPattern("io/awspring/cloud/s3/S3ObjectContentTypeResolver.properties");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.awspring.cloud.sns;

import io.awspring.cloud.sns.handlers.NotificationMessageHandlerMethodArgumentResolver;
import io.awspring.cloud.sns.handlers.NotificationStatus;
import io.awspring.cloud.sns.handlers.NotificationStatusHandlerMethodArgumentResolver;
import io.awspring.cloud.sns.handlers.NotificationSubjectHandlerMethodArgumentResolver;
import java.util.stream.Stream;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;

public class SnsRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
Stream.of(NotificationStatusHandlerMethodArgumentResolver.class,
NotificationStatusHandlerMethodArgumentResolver.AmazonSnsNotificationStatus.class,
NotificationMessageHandlerMethodArgumentResolver.class,
NotificationMessageHandlerMethodArgumentResolver.ByteArrayHttpInputMessage.class,
NotificationSubjectHandlerMethodArgumentResolver.class, NotificationStatus.class)
.forEach(type -> hints.reflection().registerType(TypeReference.of(type),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.awspring.cloud.sns.annotation.handlers.NotificationSubject;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -44,6 +45,7 @@
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(headers = "x-amz-sns-message-type=Notification", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.NO_CONTENT)
@Reflective(processors = SnsControllerMappingReflectiveProcessor.class)
public @interface NotificationMessageMapping {

@AliasFor(annotation = RequestMapping.class, attribute = "path")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -42,6 +43,7 @@
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(headers = "x-amz-sns-message-type=SubscriptionConfirmation", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.NO_CONTENT)
@Reflective(processors = SnsControllerMappingReflectiveProcessor.class)
public @interface NotificationSubscriptionMapping {

@AliasFor(annotation = RequestMapping.class, attribute = "path")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -43,6 +44,7 @@
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(headers = "x-amz-sns-message-type=UnsubscribeConfirmation", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.NO_CONTENT)
@Reflective(processors = SnsControllerMappingReflectiveProcessor.class)
public @interface NotificationUnsubscribeConfirmationMapping {

@AliasFor(annotation = RequestMapping.class, attribute = "path")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.awspring.cloud.sns.annotation.endpoint;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.annotation.ReflectiveProcessor;
import org.springframework.core.MethodParameter;

/**
* Heavily inspired by Spring Frameworks ControllerMappingReflectiveProcessor.
*
* @author Matej Nedic
* @author Stephane Nicoll
* @author Sebastien Deleuze
* @since 3.0.2
*/
public class SnsControllerMappingReflectiveProcessor implements ReflectiveProcessor {
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();

@Override
public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) {
if (element instanceof Class<?> type) {
registerTypeHints(hints, type);
}
else if (element instanceof Method method) {
registerMethodHints(hints, method);
}
}

protected void registerTypeHints(ReflectionHints hints, Class<?> type) {
hints.registerType(type);
}

protected void registerMethodHints(ReflectionHints hints, Method method) {
hints.registerMethod(method, ExecutableMode.INVOKE);
for (Parameter parameter : method.getParameters()) {
registerParameterTypeHints(hints, MethodParameter.forParameter(parameter));
}
registerReturnTypeHints(hints, MethodParameter.forExecutable(method, -1));
}

protected void registerParameterTypeHints(ReflectionHints hints, MethodParameter methodParameter) {
this.bindingRegistrar.registerReflectionHints(hints, methodParameter.getGenericParameterType());
}

protected void registerReturnTypeHints(ReflectionHints hints, MethodParameter returnTypeParameter) {
this.bindingRegistrar.registerReflectionHints(hints, getEntityType(returnTypeParameter));
}

private Type getEntityType(MethodParameter parameter) {
MethodParameter nestedParameter = parameter.nested();
return (nestedParameter.getNestedParameterType() == nestedParameter.getParameterType() ? null
: nestedParameter.getNestedParameterType());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ protected Object doResolveArgumentFromNotificationMessage(JsonNode content, Http
"Error converting notification message with payload:" + messageContent, request);
}

private static final class ByteArrayHttpInputMessage implements HttpInputMessage {
public static final class ByteArrayHttpInputMessage implements HttpInputMessage {

private final String content;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ protected Object doResolveArgumentFromNotificationMessage(JsonNode content, Http
content.get("Token").asText());
}

private static final class AmazonSnsNotificationStatus implements NotificationStatus {
public static final class AmazonSnsNotificationStatus implements NotificationStatus {

private final SnsClient snsClient;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
io.awspring.cloud.sns.SnsRuntimeHints
Loading

0 comments on commit a08ebc1

Please sign in to comment.