From 835b0324233269326911f64b41dd78e39cc4bcc3 Mon Sep 17 00:00:00 2001 From: Matthias Kay Date: Thu, 9 Jun 2022 12:31:08 +0200 Subject: [PATCH] feat: add sns producer and consumer annotation (#38) --- README.md | 2 +- .../annotation/VisualizeSnsProducer.java | 26 ++++++++++ .../annotation/VisualizeSnsProducers.java | 18 +++++++ .../annotation/VisualizeSqsConsumer.java | 9 ++-- .../annotation/VisualizeSqsProducer.java | 5 ++ .../VisualizeSqsViaSnsConsumer.java | 21 ++++++++ .../VisualizeSqsViaSnsConsumers.java | 18 +++++++ .../AbstractCommunicationModelVisitor.java | 3 ++ .../analyzer/model/CommunicationModel.java | 18 +++++++ .../analyzer/model/EndpointFactory.java | 12 +++++ .../commvis/analyzer/model/SnsProducer.java | 48 +++++++++++++++++++ .../analyzer/model/SqsViaSnsConsumer.java | 41 ++++++++++++++++ .../analyzer/model/EndpointFactoryTest.java | 31 ++++++++++++ .../analyzer/model/SqsViaSnsConsumerTest.java | 46 ++++++++++++++++++ 14 files changed, 292 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSnsProducer.java create mode 100644 src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSnsProducers.java create mode 100644 src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsViaSnsConsumer.java create mode 100644 src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsViaSnsConsumers.java create mode 100644 src/main/java/com/hlag/tools/commvis/analyzer/model/SnsProducer.java create mode 100644 src/main/java/com/hlag/tools/commvis/analyzer/model/SqsViaSnsConsumer.java create mode 100644 src/test/java/com/hlag/tools/commvis/analyzer/model/SqsViaSnsConsumerTest.java diff --git a/README.md b/README.md index 227d807..bcab511 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This project contains some API classes to allow users to define their own scanne com.hlag.tools.commvis api - 2.5.5 + 2.7.0 provided ``` diff --git a/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSnsProducer.java b/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSnsProducer.java new file mode 100644 index 0000000..4ce2164 --- /dev/null +++ b/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSnsProducer.java @@ -0,0 +1,26 @@ +package com.hlag.tools.commvis.analyzer.annotation; + +import java.lang.annotation.*; + +/** + * Marks a producer for AWS SNS messages. + */ +@Repeatable(VisualizeSnsProducers.class) +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface VisualizeSnsProducer { + /** + * @return name of the SNS topic messages are sent to + */ + String topicName(); + + /** + * @return the id of the project called, usually the Gitlab project id or similar + */ + String projectId(); + + /** + * @return the name of the project called. Just for a better visibility in the code. The value isn't used. + */ + String projectName() default ""; +} diff --git a/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSnsProducers.java b/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSnsProducers.java new file mode 100644 index 0000000..926781a --- /dev/null +++ b/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSnsProducers.java @@ -0,0 +1,18 @@ +package com.hlag.tools.commvis.analyzer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used to group multiple {@link VisualizeSnsProducer} annotations on one element. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface VisualizeSnsProducers { + /** + * @return all grouped {@link VisualizeSnsProducer} annotations + */ + VisualizeSnsProducer[] value(); +} diff --git a/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsConsumer.java b/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsConsumer.java index 731c503..3424f51 100644 --- a/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsConsumer.java +++ b/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsConsumer.java @@ -1,13 +1,12 @@ package com.hlag.tools.commvis.analyzer.annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; /** - * Annotated on methods to indicate that SQS messages are consumed. + * Annotated on methods to indicate that SQS messages are consumed. If the messages are received via a SNS topic + * subscription use the {@link VisualizeSqsViaSnsConsumer} instead. */ +@Repeatable(VisualizeSqsConsumers.class) @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface VisualizeSqsConsumer { diff --git a/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsProducer.java b/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsProducer.java index 3d7eeff..4a2dc09 100644 --- a/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsProducer.java +++ b/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsProducer.java @@ -3,9 +3,14 @@ import com.google.gson.annotations.SerializedName; import com.hlag.tools.commvis.analyzer.model.AbstractCommunicationModelVisitor; +import java.lang.annotation.*; + /** * Marks a producer for AWS SQS messages. */ +@Repeatable(VisualizeSqsProducers.class) +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) public @interface VisualizeSqsProducer { /** * @return name of the SQS queue messages are sent to diff --git a/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsViaSnsConsumer.java b/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsViaSnsConsumer.java new file mode 100644 index 0000000..f6619d9 --- /dev/null +++ b/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsViaSnsConsumer.java @@ -0,0 +1,21 @@ +package com.hlag.tools.commvis.analyzer.annotation; + +import java.lang.annotation.*; + +/** + * Annotated on methods to indicate that SQS messages are consumed from a SBS topic. + */ +@Repeatable(VisualizeSqsViaSnsConsumers.class) +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface VisualizeSqsViaSnsConsumer { + /** + * @return name of the SNS topic messages are received from (via subscription) + */ + String topicName(); + + /** + * @return the name of the project the messages are received from. Just for a better visibility in the code. The value isn't used. + */ + String projectName() default ""; +} diff --git a/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsViaSnsConsumers.java b/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsViaSnsConsumers.java new file mode 100644 index 0000000..3303c9e --- /dev/null +++ b/src/main/java/com/hlag/tools/commvis/analyzer/annotation/VisualizeSqsViaSnsConsumers.java @@ -0,0 +1,18 @@ +package com.hlag.tools.commvis.analyzer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +/** + * Used to group multiple {@link VisualizeSqsViaSnsConsumer} annotations on one element. + */ +public @interface VisualizeSqsViaSnsConsumers { + /** + * @return all grouped {@link VisualizeSqsViaSnsConsumer} annotations + */ + VisualizeSqsViaSnsConsumer[] value(); +} diff --git a/src/main/java/com/hlag/tools/commvis/analyzer/model/AbstractCommunicationModelVisitor.java b/src/main/java/com/hlag/tools/commvis/analyzer/model/AbstractCommunicationModelVisitor.java index e50fcbc..636b9a9 100644 --- a/src/main/java/com/hlag/tools/commvis/analyzer/model/AbstractCommunicationModelVisitor.java +++ b/src/main/java/com/hlag/tools/commvis/analyzer/model/AbstractCommunicationModelVisitor.java @@ -12,5 +12,8 @@ public abstract class AbstractCommunicationModelVisitor { public abstract void visit(JmsReceiver jmsReceiver); public abstract void visit(SqsConsumer sqsConsumer); + public abstract void visit(SqsViaSnsConsumer sqsViaSnsConsumer); public abstract void visit(SqsProducer sqsProducer); + + public abstract void visit(SnsProducer snsProducer); } \ No newline at end of file diff --git a/src/main/java/com/hlag/tools/commvis/analyzer/model/CommunicationModel.java b/src/main/java/com/hlag/tools/commvis/analyzer/model/CommunicationModel.java index 0fd44db..19e907e 100644 --- a/src/main/java/com/hlag/tools/commvis/analyzer/model/CommunicationModel.java +++ b/src/main/java/com/hlag/tools/commvis/analyzer/model/CommunicationModel.java @@ -60,12 +60,24 @@ public class CommunicationModel { @SerializedName(value = "sqs_consumers") private Collection sqsConsumers = new HashSet<>(); + /** + * All SQS via SNS consumers. + */ + @SerializedName(value = "sqs_via_sns_consumers") + private Collection sqsViaSnsConsumers = new HashSet<>(); + /** * All SQS producers. */ @SerializedName(value = "sqs_producers") private Collection sqsProducers = new HashSet<>(); + /** + * All SNS producers. + */ + @SerializedName(value = "sns_producers") + private Collection snsProducers = new HashSet<>(); + private CommunicationModel() { // for GSON deserialize projectId = NOT_SET; @@ -84,6 +96,10 @@ public void addSenderReceiver(T endpoin sqsConsumers.add((SqsConsumer) endpoint); } else if (endpoint instanceof SqsProducer) { sqsProducers.add((SqsProducer) endpoint); + } else if (endpoint instanceof SnsProducer) { + snsProducers.add((SnsProducer) endpoint); + } else if (endpoint instanceof SqsViaSnsConsumer) { + sqsViaSnsConsumers.add((SqsViaSnsConsumer) endpoint); } else { throw new IllegalStateException(String.format("We have no endpoints of type %s", endpoint.getClass().getCanonicalName())); } @@ -97,5 +113,7 @@ public void visit(AbstractCommunicationModelVisitor visitor) { jmsConsumers.forEach(e -> e.visit(visitor)); sqsConsumers.forEach(e -> e.visit(visitor)); sqsProducers.forEach(e -> e.visit(visitor)); + snsProducers.forEach(e -> e.visit(visitor)); + sqsViaSnsConsumers.forEach(e -> e.visit(visitor)); } } diff --git a/src/main/java/com/hlag/tools/commvis/analyzer/model/EndpointFactory.java b/src/main/java/com/hlag/tools/commvis/analyzer/model/EndpointFactory.java index c97aabb..989acd4 100644 --- a/src/main/java/com/hlag/tools/commvis/analyzer/model/EndpointFactory.java +++ b/src/main/java/com/hlag/tools/commvis/analyzer/model/EndpointFactory.java @@ -1,9 +1,13 @@ package com.hlag.tools.commvis.analyzer.model; +import com.hlag.tools.commvis.analyzer.annotation.VisualizeSnsProducer; +import com.hlag.tools.commvis.analyzer.annotation.VisualizeSqsViaSnsConsumer; import com.hlag.tools.commvis.analyzer.port.IIdentityGenerator; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import java.lang.reflect.Method; + @Component @RequiredArgsConstructor public class EndpointFactory { @@ -25,7 +29,15 @@ public SqsConsumer createSqsReceiver(String className, String methodName, String return new SqsConsumer(className, methodName, queueName, identityGenerator.generateUniqueId()); } + public SqsViaSnsConsumer createSqsViaSnsConsumer(VisualizeSqsViaSnsConsumer annotation, Method method) { + return new SqsViaSnsConsumer(method.getDeclaringClass().getCanonicalName(), method.getName(), annotation.topicName(), identityGenerator.generateUniqueId()); + } + public SqsProducer createSqsProducer(String className, String methodName, String queueName, String destinationProjectId) { return new SqsProducer(className, methodName, queueName, destinationProjectId, identityGenerator.generateUniqueId()); } + + public SnsProducer createSnsProducer(VisualizeSnsProducer annotation, Method method) { + return new SnsProducer(method.getDeclaringClass().getCanonicalName(), method.getName(), annotation.topicName(), annotation.projectId(), identityGenerator.generateUniqueId()); + } } diff --git a/src/main/java/com/hlag/tools/commvis/analyzer/model/SnsProducer.java b/src/main/java/com/hlag/tools/commvis/analyzer/model/SnsProducer.java new file mode 100644 index 0000000..328a5e6 --- /dev/null +++ b/src/main/java/com/hlag/tools/commvis/analyzer/model/SnsProducer.java @@ -0,0 +1,48 @@ +package com.hlag.tools.commvis.analyzer.model; + +import com.google.gson.annotations.SerializedName; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.Value; + +/** + * A producer for SNS messages. + */ +@Value +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) +public class SnsProducer implements ISenderReceiverCommunication, IProducer { + /** + * the class name where the producer was found. + */ + @SerializedName(value="class_name") + String className; + + /** + * the method name were the producer was found. + */ + @SerializedName(value="method_name") + String methodName; + + /** + * the topic the messages are sent to. + */ + @SerializedName(value="topic_name") + String topicName; + + /** + * The project id of the referenced project. + */ + @SerializedName(value="destination_project_id") + String destinationProjectId; + + /** + * internal id of this node + */ + @SerializedName(value="id") + String id; + + @Override + public void visit(AbstractCommunicationModelVisitor visitor) { + visitor.visit(this); + } +} diff --git a/src/main/java/com/hlag/tools/commvis/analyzer/model/SqsViaSnsConsumer.java b/src/main/java/com/hlag/tools/commvis/analyzer/model/SqsViaSnsConsumer.java new file mode 100644 index 0000000..a3af247 --- /dev/null +++ b/src/main/java/com/hlag/tools/commvis/analyzer/model/SqsViaSnsConsumer.java @@ -0,0 +1,41 @@ +package com.hlag.tools.commvis.analyzer.model; + +import com.google.gson.annotations.SerializedName; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.Value; + +/** + * A receiver for SQS messages via SNS topics. + */ +@Value +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) +public class SqsViaSnsConsumer implements ISenderReceiverCommunication, IConsumer { + @SerializedName(value="class_name") + String className; + + @SerializedName(value="method_name") + String methodName; + + @SerializedName(value="topic_name") + String topicName; + + @SerializedName(value="id") + String id; + + @Override + public void visit(AbstractCommunicationModelVisitor visitor) { + visitor.visit(this); + } + + @Override + public boolean isProducedBy(IProducer producer) { + if (producer instanceof SnsProducer) { + SnsProducer snsProducer = (SnsProducer) producer; + + return topicName.equals(snsProducer.getTopicName()); + } + + return false; + } +} diff --git a/src/test/java/com/hlag/tools/commvis/analyzer/model/EndpointFactoryTest.java b/src/test/java/com/hlag/tools/commvis/analyzer/model/EndpointFactoryTest.java index 5de09a3..366857b 100644 --- a/src/test/java/com/hlag/tools/commvis/analyzer/model/EndpointFactoryTest.java +++ b/src/test/java/com/hlag/tools/commvis/analyzer/model/EndpointFactoryTest.java @@ -1,5 +1,7 @@ package com.hlag.tools.commvis.analyzer.model; +import com.hlag.tools.commvis.analyzer.annotation.VisualizeSnsProducer; +import com.hlag.tools.commvis.analyzer.annotation.VisualizeSqsViaSnsConsumer; import com.hlag.tools.commvis.analyzer.port.IIdentityGenerator; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -12,6 +14,14 @@ class EndpointFactoryTest { private static final String FIXED_ID = "MY-UNIQUE-ID"; + private static class TestProducersAndConsumers { + @VisualizeSnsProducer(topicName = "topic", projectId = "4711") + public void produceSnsMessage() {} + + @VisualizeSqsViaSnsConsumer(topicName = "topic1", projectName = "4712") + public void consumeSqsViaSnsMessage() {} + } + @Mock private IIdentityGenerator identityGenerator; @InjectMocks @@ -78,4 +88,25 @@ void shouldSetAllFields_whenCreateSqsProducer() { Assertions.assertThat(actualSqsProducer.getDestinationProjectId()).isEqualTo("destinationProjectId"); Assertions.assertThat(actualSqsProducer.getId()).isEqualTo(FIXED_ID); } + + @Test + void shouldSetAllFields_whenCreateSnsProducer() throws NoSuchMethodException { + SnsProducer actualSnsProducer = factory.createSnsProducer(TestProducersAndConsumers.class.getDeclaredMethod("produceSnsMessage").getAnnotationsByType(VisualizeSnsProducer.class)[0], TestProducersAndConsumers.class.getDeclaredMethod("produceSnsMessage")); + + Assertions.assertThat(actualSnsProducer.getClassName()).isEqualTo("com.hlag.tools.commvis.analyzer.model.EndpointFactoryTest.TestProducersAndConsumers"); + Assertions.assertThat(actualSnsProducer.getMethodName()).isEqualTo("produceSnsMessage"); + Assertions.assertThat(actualSnsProducer.getTopicName()).isEqualTo("topic"); + Assertions.assertThat(actualSnsProducer.getDestinationProjectId()).isEqualTo("4711"); + Assertions.assertThat(actualSnsProducer.getId()).isEqualTo(FIXED_ID); + } + + @Test + void shouldSetAllFields_whenCreateSqsViaSnsConsumer() throws NoSuchMethodException { + SqsViaSnsConsumer actualSqsViaSnsConsumer = factory.createSqsViaSnsConsumer(TestProducersAndConsumers.class.getDeclaredMethod("consumeSqsViaSnsMessage").getAnnotationsByType(VisualizeSqsViaSnsConsumer.class)[0], TestProducersAndConsumers.class.getDeclaredMethod("consumeSqsViaSnsMessage")); + + Assertions.assertThat(actualSqsViaSnsConsumer.getClassName()).isEqualTo("com.hlag.tools.commvis.analyzer.model.EndpointFactoryTest.TestProducersAndConsumers"); + Assertions.assertThat(actualSqsViaSnsConsumer.getMethodName()).isEqualTo("consumeSqsViaSnsMessage"); + Assertions.assertThat(actualSqsViaSnsConsumer.getTopicName()).isEqualTo("topic1"); + Assertions.assertThat(actualSqsViaSnsConsumer.getId()).isEqualTo(FIXED_ID); + } } \ No newline at end of file diff --git a/src/test/java/com/hlag/tools/commvis/analyzer/model/SqsViaSnsConsumerTest.java b/src/test/java/com/hlag/tools/commvis/analyzer/model/SqsViaSnsConsumerTest.java new file mode 100644 index 0000000..d2b9caa --- /dev/null +++ b/src/test/java/com/hlag/tools/commvis/analyzer/model/SqsViaSnsConsumerTest.java @@ -0,0 +1,46 @@ +package com.hlag.tools.commvis.analyzer.model; + +import com.google.gson.annotations.SerializedName; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; + +import static org.assertj.core.api.Assertions.assertThat; + +class SqsViaSnsConsumerTest { + private SqsViaSnsConsumer sqsViaSnsConsumer; + + @BeforeEach + void init() { + sqsViaSnsConsumer = new SqsViaSnsConsumer("class", "method", "topic", "id"); + } + @Test + void shouldHaveSerializedNameAnnotationOnFiled_toDecoupleTheFieldNameFromJson() { + Field[] declaredFields = SqsViaSnsConsumer.class.getDeclaredFields(); + + for (Field f : declaredFields) { + SerializedName actualAnnotation = f.getAnnotation(SerializedName.class); + + assertThat(actualAnnotation).withFailMessage(() -> String.format("Field %s has no @SerializedName annotation.", f.getName())).isNotNull(); + } + } + + @Test + void shouldReturnTrue_whenIsProducedBy_givenProducerIsForSameQueue() { + SnsProducer givenProducer = new SnsProducer("class1", "method1", "topic", "destinationProject", "id1"); + + boolean actualIsProducedBy = sqsViaSnsConsumer.isProducedBy(givenProducer); + + assertThat(actualIsProducedBy).isTrue(); + } + + @Test + void shouldReturnFalse_whenIsProducedBy_givenProducerIsForOtherQueue() { + SnsProducer givenProducer = new SnsProducer("class1", "method1", "topic-other", "destinationProject", "id1"); + + boolean actualIsProducedBy = sqsViaSnsConsumer.isProducedBy(givenProducer); + + assertThat(actualIsProducedBy).isFalse(); + } +} \ No newline at end of file