diff --git a/docs/src/main/asciidoc/sns.adoc b/docs/src/main/asciidoc/sns.adoc index 21d474b95..8d098f837 100644 --- a/docs/src/main/asciidoc/sns.adoc +++ b/docs/src/main/asciidoc/sns.adoc @@ -85,6 +85,8 @@ public class TopicArnResolverConfiguration { ---- +However, when using the topic ARN in your application, the `SnsTemplate` provides a `topicExists` method to validate the existence of the SNS topic at application startup itself. + ==== SNS Operations Because of Spring Messaging compatibility, `SnsTemplate` exposes many methods that you may not need if you don't need Spring Messaging abstractions. @@ -235,6 +237,7 @@ Following IAM permissions are required by Spring Cloud AWS: | To publish notification you will also need | `sns:ListTopics` | To use Annotation-driven HTTP notification endpoint | `sns:ConfirmSubscription` | For resolving topic name to ARN | `sns:CreateTopic` +| For validating topic existence by ARN | `sns:GetTopicAttributes` |=== Sample IAM policy granting access to SNS: @@ -248,7 +251,8 @@ Sample IAM policy granting access to SNS: "Effect": "Allow", "Action": [ "sns:Publish", - "sns:ConfirmSubscription" + "sns:ConfirmSubscription", + "sns:GetTopicAttributes" ], "Resource": "yourArn" }, diff --git a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/core/SnsOperations.java b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/core/SnsOperations.java index 908bdfebd..fc129e3c9 100644 --- a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/core/SnsOperations.java +++ b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/core/SnsOperations.java @@ -19,6 +19,7 @@ * High level SNS operations. * * @author Maciej Walkowiak + * @author Hardik Singh Behl * @since 3.0 */ public interface SnsOperations { @@ -30,4 +31,12 @@ public interface SnsOperations { * @param notification - the notification */ void sendNotification(String topic, SnsNotification notification); + + /** + * Checks if topic with given ARN exists. + * + * @param topicArn - ARN of the topic + * @return true if topic exists, false otherwise + */ + boolean topicExists(String topicArn); } diff --git a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/core/SnsTemplate.java b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/core/SnsTemplate.java index 071dd565c..f769e7005 100644 --- a/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/core/SnsTemplate.java +++ b/spring-cloud-aws-sns/src/main/java/io/awspring/cloud/sns/core/SnsTemplate.java @@ -34,6 +34,7 @@ import org.springframework.util.Assert; import software.amazon.awssdk.arns.Arn; import software.amazon.awssdk.services.sns.SnsClient; +import software.amazon.awssdk.services.sns.model.NotFoundException; /** * Helper class that simplifies synchronous sending of notifications to SNS. The only mandatory fields are @@ -42,6 +43,7 @@ * @author Alain Sahli * @author Matej Nedic * @author Mariusz Sondecki + * @author Hardik Singh Behl * @since 1.0 */ public class SnsTemplate extends AbstractMessageSendingTemplate @@ -151,6 +153,17 @@ public void addChannelInterceptor(ChannelInterceptor channelInterceptor) { public void sendNotification(String topic, SnsNotification notification) { this.convertAndSend(topic, notification.getPayload(), notification.getHeaders()); } + + @Override + public boolean topicExists(String topicArn) { + Assert.notNull(topicArn, "topicArn must not be null"); + try { + snsClient.getTopicAttributes(request -> request.topicArn(topicArn)); + } catch (NotFoundException exception) { + return false; + } + return true; + } private TopicMessageChannel resolveMessageChannelByTopicName(String topicName) { Arn topicArn = this.topicArnResolver.resolveTopicArn(topicName); diff --git a/spring-cloud-aws-sns/src/test/java/io/awspring/cloud/sns/integration/SnsTemplateIntegrationTest.java b/spring-cloud-aws-sns/src/test/java/io/awspring/cloud/sns/integration/SnsTemplateIntegrationTest.java index e6cc2d63d..dbcd2c8e1 100644 --- a/spring-cloud-aws-sns/src/test/java/io/awspring/cloud/sns/integration/SnsTemplateIntegrationTest.java +++ b/spring-cloud-aws-sns/src/test/java/io/awspring/cloud/sns/integration/SnsTemplateIntegrationTest.java @@ -24,6 +24,8 @@ import io.awspring.cloud.sns.core.SnsTemplate; import io.awspring.cloud.sns.core.TopicNotFoundException; import io.awspring.cloud.sns.core.TopicsListingTopicArnResolver; +import net.bytebuddy.utility.RandomString; + import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.AfterEach; @@ -51,6 +53,7 @@ * Integration tests for {@link SnsTemplate}. * * @author Matej Nedic + * @author Hardik Singh Behl */ @Testcontainers class SnsTemplateIntegrationTest { @@ -203,5 +206,25 @@ private static void createTopics() { } } } + + @Test + void shouldReturnFalseForNonExistingTopic() { + String nonExistentTopicArn = String.format("arn:aws:sns:us-east-1:000000000000:%s", RandomString.make()); + + boolean response = snsTemplate.topicExists(nonExistentTopicArn); + + assertThat(response).isFalse(); + } + + @Test + void shouldReturnTrueForExistingTopic() { + String topicName = RandomString.make(); + snsClient.createTopic(request -> request.name(topicName)); + String topicArn = String.format("arn:aws:sns:us-east-1:000000000000:%s", topicName); + + boolean response = snsTemplate.topicExists(topicArn); + + assertThat(response).isTrue(); + } }