diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/ConfigStoreRuntimeHints.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/ConfigStoreRuntimeHints.java new file mode 100644 index 000000000..c005c1ce6 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/ConfigStoreRuntimeHints.java @@ -0,0 +1,37 @@ +/* + * 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 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 ConfigStoreRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader 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(SecretsManagerPropertySources.class), + hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS)); + } +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/CoreAOT.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/CoreAOT.java new file mode 100644 index 000000000..8c4c3b9e3 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/CoreAOT.java @@ -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)); + } + } +} diff --git a/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring/aot.factories b/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 000000000..aaa04efcf --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,3 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +io.awspring.cloud.autoconfigure.config.ConfigStoreRuntimeHints,\ +io.awspring.cloud.autoconfigure.core.CoreAOT diff --git a/spring-cloud-aws-core/src/main/java/io/awspring/cloud/core/AWSCoreRuntimeHints.java b/spring-cloud-aws-core/src/main/java/io/awspring/cloud/core/AWSCoreRuntimeHints.java new file mode 100644 index 000000000..4590ac46e --- /dev/null +++ b/spring-cloud-aws-core/src/main/java/io/awspring/cloud/core/AWSCoreRuntimeHints.java @@ -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"); + } +} diff --git a/spring-cloud-aws-core/src/main/java/io/awspring/cloud/core/SpringCloudClientConfiguration.java b/spring-cloud-aws-core/src/main/java/io/awspring/cloud/core/SpringCloudClientConfiguration.java index 65ca7caeb..a3b9f0363 100644 --- a/spring-cloud-aws-core/src/main/java/io/awspring/cloud/core/SpringCloudClientConfiguration.java +++ b/spring-cloud-aws-core/src/main/java/io/awspring/cloud/core/SpringCloudClientConfiguration.java @@ -66,5 +66,4 @@ public ClientOverrideConfiguration clientOverrideConfiguration() { private String getUserAgent() { return NAME + "/" + version; } - } diff --git a/spring-cloud-aws-core/src/main/resources/META-INF/spring/aot.factories b/spring-cloud-aws-core/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 000000000..bf58875d7 --- /dev/null +++ b/spring-cloud-aws-core/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +io.awspring.cloud.core.AWSCoreRuntimeHints diff --git a/spring-cloud-aws-dependencies/pom.xml b/spring-cloud-aws-dependencies/pom.xml index 33d53219a..8a48586d8 100644 --- a/spring-cloud-aws-dependencies/pom.xml +++ b/spring-cloud-aws-dependencies/pom.xml @@ -24,7 +24,7 @@ 2.31.0 - 2.25.21 + 2.26.3 2.0.4 1.6 4.1.0 diff --git a/spring-cloud-aws-s3-parent/spring-cloud-aws-s3/src/main/resources/META-INF/spring/aot.factories b/spring-cloud-aws-s3-parent/spring-cloud-aws-s3/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 000000000..1f854b5b0 --- /dev/null +++ b/spring-cloud-aws-s3-parent/spring-cloud-aws-s3/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +io.awspring.cloud.s3.S3RuntimeHints diff --git a/spring-cloud-aws-s3/src/main/java/io/awspring/cloud/s3/S3RuntimeHints.java b/spring-cloud-aws-s3/src/main/java/io/awspring/cloud/s3/S3RuntimeHints.java new file mode 100644 index 000000000..b4ef91f8a --- /dev/null +++ b/spring-cloud-aws-s3/src/main/java/io/awspring/cloud/s3/S3RuntimeHints.java @@ -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"); + } +} diff --git a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/SnsRuntimeHints.java b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/SnsRuntimeHints.java new file mode 100644 index 000000000..3cfb5b860 --- /dev/null +++ b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/SnsRuntimeHints.java @@ -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))); + } +} diff --git a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/NotificationMessageMapping.java b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/NotificationMessageMapping.java index 6a151c199..3112c1fa2 100644 --- a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/NotificationMessageMapping.java +++ b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/NotificationMessageMapping.java @@ -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; @@ -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") diff --git a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/NotificationSubscriptionMapping.java b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/NotificationSubscriptionMapping.java index f58df4a10..e5f55d58e 100644 --- a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/NotificationSubscriptionMapping.java +++ b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/NotificationSubscriptionMapping.java @@ -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; @@ -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") diff --git a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/NotificationUnsubscribeConfirmationMapping.java b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/NotificationUnsubscribeConfirmationMapping.java index 4b4e2049c..fe0b4519c 100644 --- a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/NotificationUnsubscribeConfirmationMapping.java +++ b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/NotificationUnsubscribeConfirmationMapping.java @@ -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; @@ -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") diff --git a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/SnsControllerMappingReflectiveProcessor.java b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/SnsControllerMappingReflectiveProcessor.java new file mode 100644 index 000000000..c74794639 --- /dev/null +++ b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/annotation/endpoint/SnsControllerMappingReflectiveProcessor.java @@ -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()); + } +} diff --git a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/handlers/NotificationMessageHandlerMethodArgumentResolver.java b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/handlers/NotificationMessageHandlerMethodArgumentResolver.java index 13b328c74..db64d786d 100644 --- a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/handlers/NotificationMessageHandlerMethodArgumentResolver.java +++ b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/handlers/NotificationMessageHandlerMethodArgumentResolver.java @@ -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; diff --git a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/handlers/NotificationStatusHandlerMethodArgumentResolver.java b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/handlers/NotificationStatusHandlerMethodArgumentResolver.java index 6c827a085..6ed06ebbb 100644 --- a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/handlers/NotificationStatusHandlerMethodArgumentResolver.java +++ b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/handlers/NotificationStatusHandlerMethodArgumentResolver.java @@ -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; diff --git a/spring-cloud-aws-sns/src/main/resources/META-INF/spring/aot.factories b/spring-cloud-aws-sns/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 000000000..0cd0d74b7 --- /dev/null +++ b/spring-cloud-aws-sns/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +io.awspring.cloud.sns.SnsRuntimeHints diff --git a/spring-cloud-aws-sns/src/test/java/io/awspring/cloud/sns/endpoint/SnsControllerMappingReflectiveProcessorTest.java b/spring-cloud-aws-sns/src/test/java/io/awspring/cloud/sns/endpoint/SnsControllerMappingReflectiveProcessorTest.java new file mode 100644 index 000000000..9a84c3630 --- /dev/null +++ b/spring-cloud-aws-sns/src/test/java/io/awspring/cloud/sns/endpoint/SnsControllerMappingReflectiveProcessorTest.java @@ -0,0 +1,164 @@ +/* + * 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.endpoint; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.awspring.cloud.sns.annotation.endpoint.NotificationMessageMapping; +import io.awspring.cloud.sns.annotation.endpoint.NotificationSubscriptionMapping; +import io.awspring.cloud.sns.annotation.endpoint.NotificationUnsubscribeConfirmationMapping; +import io.awspring.cloud.sns.annotation.endpoint.SnsControllerMappingReflectiveProcessor; +import io.awspring.cloud.sns.annotation.handlers.NotificationMessage; +import io.awspring.cloud.sns.annotation.handlers.NotificationSubject; +import io.awspring.cloud.sns.handlers.NotificationStatus; +import java.io.IOException; +import java.lang.reflect.Method; +import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +public class SnsControllerMappingReflectiveProcessorTest { + + private final SnsControllerMappingReflectiveProcessor processor = new SnsControllerMappingReflectiveProcessor(); + + private final ReflectionHints hints = new ReflectionHints(); + + @Test + void registerReflectiveHintsForMethodHandleSubscribe() throws NoSuchMethodException { + Method method = ComplexNotificationTestController.class.getDeclaredMethod("handleSubscriptionMessage", + NotificationStatus.class); + processor.registerReflectionHints(hints, method); + assertThat( + hints.typeHints()) + .satisfiesExactlyInAnyOrder( + typeHint -> assertThat(typeHint.getType()) + .isEqualTo(TypeReference.of(ComplexNotificationTestController.class)), + typeHint -> { + assertThat(typeHint.getType()) + .isEqualTo(TypeReference.of(NotificationStatus.class)); + assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS); + }); + } + + @Test + void registerReflectiveHintsForMethodHandleNotificationMessage() throws NoSuchMethodException { + Method method = ComplexNotificationTestController.class.getDeclaredMethod("handleNotificationMessage", + String.class, Person.class); + processor.registerReflectionHints(hints, method); + assertThat( + hints.typeHints()) + .satisfiesExactlyInAnyOrder( + typeHint -> assertThat(typeHint.getType()) + .isEqualTo(TypeReference.of(ComplexNotificationTestController.class)), + typeHint -> { + assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)); + }, typeHint -> { + assertThat(typeHint.getType()).isEqualTo(TypeReference.of(Person.class)); + assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS); + assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( + hint -> assertThat(hint.getName()).isEqualTo("getFirstName"), + hint -> assertThat(hint.getName()).isEqualTo("setFirstName"), + hint -> assertThat(hint.getName()).isEqualTo("getLastName"), + hint -> assertThat(hint.getName()).isEqualTo("setLastName")); + }); + } + + @Test + void registerReflectiveHintsForMethodHandleUnsubscribe() throws NoSuchMethodException { + Method method = ComplexNotificationTestController.class.getDeclaredMethod("handleUnsubscribeMessage", + NotificationStatus.class); + processor.registerReflectionHints(hints, method); + assertThat( + hints.typeHints()) + .satisfiesExactlyInAnyOrder( + typeHint -> assertThat(typeHint.getType()) + .isEqualTo(TypeReference.of(ComplexNotificationTestController.class)), + typeHint -> { + assertThat(typeHint.getType()) + .isEqualTo(TypeReference.of(NotificationStatus.class)); + assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS); + }); + } + + @Controller + @RequestMapping("/myComplexTopic") + static class ComplexNotificationTestController { + + private String subject; + + private Person message; + + String getSubject() { + return this.subject; + } + + Person getMessage() { + return this.message; + } + + @NotificationSubscriptionMapping + void handleSubscriptionMessage(NotificationStatus status) throws IOException { + // We subscribe to start receive the message + status.confirmSubscription(); + } + + @NotificationMessageMapping + void handleNotificationMessage(@NotificationSubject String subject, @NotificationMessage Person message) { + this.subject = subject; + this.message = message; + } + + @NotificationUnsubscribeConfirmationMapping + void handleUnsubscribeMessage(NotificationStatus status) { + // e.g. the client has been unsubscribed and we want to "re-subscribe" + status.confirmSubscription(); + } + } + + static class Person { + + private String firstName; + + private String lastName; + + public String getFirstName() { + return this.firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return this.lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + } + +} diff --git a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/annotation/SqsListener.java b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/annotation/SqsListener.java index d9ee062b8..993076c2d 100644 --- a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/annotation/SqsListener.java +++ b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/annotation/SqsListener.java @@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; +import org.springframework.messaging.handler.annotation.MessageMapping; /** * Methods with this annotation will be wrapped by a {@link io.awspring.cloud.sqs.listener.MessageListener} or @@ -80,6 +81,7 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented +@MessageMapping public @interface SqsListener { /**