diff --git a/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsConsumer.java b/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsConsumer.java index 8c97987..4f85363 100644 --- a/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsConsumer.java +++ b/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsConsumer.java @@ -48,7 +48,6 @@ import com.amazon.sns.messaging.lib.model.ResponseFailEntry; import com.amazon.sns.messaging.lib.model.ResponseSuccessEntry; import com.amazon.sns.messaging.lib.model.TopicProperty; -import com.fasterxml.jackson.databind.ObjectMapper; // @formatter:off /** @@ -106,7 +105,7 @@ abstract class AbstractAmazonSnsConsumer implements Runnable, Amazon * * @param amazonSnsClient the Amazon SNS client * @param topicProperty the topic configuration - * @param objectMapper the Jackson ObjectMapper for payload serialization + * @param jsonMapper the JsonMapper for payload serialization * @param pendingRequests the shared map of pending requests keyed by request ID * @param topicRequests the shared blocking queue for topic requests * @param executorService the executor service for async publishing @@ -115,7 +114,7 @@ abstract class AbstractAmazonSnsConsumer implements Runnable, Amazon protected AbstractAmazonSnsConsumer( final C amazonSnsClient, final TopicProperty topicProperty, - final ObjectMapper objectMapper, + final JsonMapper jsonMapper, final ConcurrentMap> pendingRequests, final BlockingQueue> topicRequests, final ExecutorService executorService, @@ -123,11 +122,11 @@ protected AbstractAmazonSnsConsumer( this.topicProperty = Objects.requireNonNull(topicProperty, "topicProperty cannot be null"); this.amazonSnsClient = Objects.requireNonNull(amazonSnsClient, "amazonSnsClient cannot be null"); - requestEntryInternalFactory = new RequestEntryInternalFactory(Objects.requireNonNull(objectMapper, "objectMapper cannot be null")); + this.executorService = Objects.requireNonNull(executorService, "executorService cannot be null"); + this.requestEntryInternalFactory = RequestEntryInternalFactory.build(jsonMapper); this.pendingRequests = pendingRequests; this.topicRequests = topicRequests; this.publishDecorator = publishDecorator; - this.executorService = Objects.requireNonNull(executorService, "executorService cannot be null"); scheduledExecutorService.scheduleAtFixedRate(this, 0, topicProperty.getLinger(), TimeUnit.MILLISECONDS); } diff --git a/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsTemplate.java b/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsTemplate.java index 5f4c6f1..bc9f5b1 100644 --- a/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsTemplate.java +++ b/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsTemplate.java @@ -26,6 +26,8 @@ import java.util.function.Function; import java.util.function.UnaryOperator; +import org.apache.fory.json.ForyJson; + import com.amazon.sns.messaging.lib.concurrent.AmazonSnsThreadPoolExecutor; import com.amazon.sns.messaging.lib.concurrent.RingBufferBlockingQueue; import com.amazon.sns.messaging.lib.metrics.BlockingQueueMetricsDecorator; @@ -152,9 +154,9 @@ public static final class Builder> topicRequests; /** - * The Jackson ObjectMapper for serializing payloads. + * The JsonMapper for serializing payloads. */ - private ObjectMapper objectMapper = new ObjectMapper(); + private JsonMapper jsonMapper = JsonMapperFactory.create(new ObjectMapper()); /** * Decorator function applied to the publish batch request before sending. @@ -213,7 +215,18 @@ public Builder topicRequests(final BlockingQueue> * @return this builder */ public Builder objectMapper(final ObjectMapper objectMapper) { - this.objectMapper = Objects.requireNonNull(objectMapper, "objectMapper"); + jsonMapper = JsonMapperFactory.create(objectMapper); + return this; + } + + /** + * Sets the Fory for serializing payloads. + * + * @param objectMapper the Fory + * @return this builder + */ + public Builder foryJson(final ForyJson foryJson) { + jsonMapper = JsonMapperFactory.create(foryJson); return this; } diff --git a/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/JsonMapper.java b/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/JsonMapper.java new file mode 100644 index 0000000..d303d1b --- /dev/null +++ b/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/JsonMapper.java @@ -0,0 +1,83 @@ +/* + * Copyright 2022 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 com.amazon.sns.messaging.lib.core; + +import com.amazon.sns.messaging.lib.exception.PoisonRequestEntryException; + +/** + * Abstraction over a JSON serialization library used to convert arbitrary Java + * objects to and from their JSON representation. + * + *

+ * Implementations are expected to translate any underlying serialization or + * deserialization failure into a {@link PoisonRequestEntryException}, allowing + * callers to handle malformed or unprocessable payloads uniformly regardless of + * which JSON library backs a given implementation. + * + * @see JsonMapperFactory + */ +interface JsonMapper { + + /** + * Serializes the given value to its JSON string representation. + * + * @param value the object to serialize; implementations determine whether + * {@code null} is permitted + * @return the JSON representation of {@code value} + * @throws PoisonRequestEntryException if {@code value} cannot be serialized to + * JSON + */ + String toJson(final Object value) throws PoisonRequestEntryException; + + /** + * Serializes the given value to its JSON representation, encoded as bytes. + * + * @param value the object to serialize; implementations determine whether + * {@code null} is permitted + * @return the JSON representation of {@code value}, encoded as bytes + * @throws PoisonRequestEntryException if {@code value} cannot be serialized to + * JSON + */ + byte[] toJsonBytes(final Object value) throws PoisonRequestEntryException; + + /** + * Deserializes the given JSON string into an instance of the specified type. + * + * @param the target type + * @param json the JSON string to deserialize + * @param clazz the class of the target type + * @return an instance of {@code T} populated from {@code json} + * @throws PoisonRequestEntryException if {@code json} is malformed or cannot be + * deserialized into an instance of + * {@code clazz} + */ + T fromJson(final String json, Class clazz) throws PoisonRequestEntryException; + + /** + * Deserializes the given JSON bytes into an instance of the specified type. + * + * @param the target type + * @param bytes the JSON content to deserialize, encoded as bytes + * @param clazz the class of the target type + * @return an instance of {@code T} populated from {@code bytes} + * @throws PoisonRequestEntryException if {@code bytes} is malformed or cannot + * be deserialized into an instance of + * {@code clazz} + */ + T fromJson(final byte[] bytes, Class clazz) throws PoisonRequestEntryException; + +} \ No newline at end of file diff --git a/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/JsonMapperFactory.java b/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/JsonMapperFactory.java new file mode 100644 index 0000000..abc3f1e --- /dev/null +++ b/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/JsonMapperFactory.java @@ -0,0 +1,231 @@ +/* + * Copyright 2022 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 com.amazon.sns.messaging.lib.core; + +import java.util.Objects; + +import org.apache.fory.json.ForyJson; + +import com.amazon.sns.messaging.lib.exception.PoisonRequestEntryException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; + +/** + * Factory for creating {@link JsonMapper} instances backed by a specific JSON + * serialization library. + * + *

+ * Each {@code create} overload wraps a caller-supplied JSON library delegate + * ({@link ObjectMapper} or {@link ForyJson}) in a {@link JsonMapper} + * implementation that translates any failure raised by the delegate into a + * {@link PoisonRequestEntryException}, giving callers a single, + * library-agnostic exception type to handle regardless of which JSON library + * backs the returned mapper. + * + *

+ * This class is not instantiable; use the static {@code create} methods to + * obtain a {@link JsonMapper}. + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +final class JsonMapperFactory { + + /** + * Creates a {@link JsonMapper} backed by the given Jackson + * {@link ObjectMapper}. + * + * @param objectMapper the Jackson mapper to delegate serialization and + * deserialization to; must not be {@code null} + * @return a {@link JsonMapper} that delegates to {@code objectMapper} + * @throws NullPointerException if {@code objectMapper} is {@code null} + */ + static JsonMapper create(final ObjectMapper objectMapper) { + return new JsonMapperJackson(Objects.requireNonNull(objectMapper, "objectMapper cannot be null")); + } + + /** + * Creates a {@link JsonMapper} backed by the given Apache Fory + * {@link ForyJson}. + * + * @param foryJson the Fory JSON mapper to delegate serialization and + * deserialization to; must not be {@code null} + * @return a {@link JsonMapper} that delegates to {@code foryJson} + * @throws NullPointerException if {@code foryJson} is {@code null} + */ + static JsonMapper create(final ForyJson foryJson) { + return new JsonMapperFory(Objects.requireNonNull(foryJson, "foryJson cannot be null")); + } + + /** + * {@link JsonMapper} implementation backed by a Jackson {@link ObjectMapper}. + * + *

+ * Every operation delegates to the wrapped {@code ObjectMapper} and translates + * any exception raised during serialization or deserialization into a + * {@link PoisonRequestEntryException}, preserving the original exception's + * message and cause. + */ + @RequiredArgsConstructor(access = AccessLevel.PACKAGE) + static class JsonMapperJackson implements JsonMapper { + + private final ObjectMapper objectMapper; + + /** + * {@inheritDoc} + * + * @throws PoisonRequestEntryException if the underlying {@link ObjectMapper} + * fails to serialize {@code value} + */ + @Override + public String toJson(final Object value) throws PoisonRequestEntryException { + try { + return objectMapper.writeValueAsString(value); + } catch (final Exception ex) { + throw PoisonRequestEntryException.fromJsonProcessing(ex.getMessage(), ex); + } + } + + /** + * {@inheritDoc} + * + * @throws PoisonRequestEntryException if the underlying {@link ObjectMapper} + * fails to serialize {@code value} + */ + @Override + public byte[] toJsonBytes(final Object value) throws PoisonRequestEntryException { + try { + return objectMapper.writeValueAsBytes(value); + } catch (final Exception ex) { + throw PoisonRequestEntryException.fromJsonProcessing(ex.getMessage(), ex); + } + } + + /** + * {@inheritDoc} + * + * @throws PoisonRequestEntryException if the underlying {@link ObjectMapper} + * fails to deserialize {@code json} into an + * instance of {@code clazz} + */ + @Override + public T fromJson(final String json, final Class clazz) throws PoisonRequestEntryException { + try { + return objectMapper.readValue(json, clazz); + } catch (final Exception ex) { + throw PoisonRequestEntryException.fromJsonProcessing(ex.getMessage(), ex); + } + } + + /** + * {@inheritDoc} + * + * @throws PoisonRequestEntryException if the underlying {@link ObjectMapper} + * fails to deserialize {@code bytes} into + * an instance of {@code clazz} + */ + @Override + public T fromJson(final byte[] bytes, final Class clazz) throws PoisonRequestEntryException { + try { + return objectMapper.readValue(bytes, clazz); + } catch (final Exception ex) { + throw PoisonRequestEntryException.fromJsonProcessing(ex.getMessage(), ex); + } + } + + } + + /** + * {@link JsonMapper} implementation backed by an Apache Fory {@link ForyJson}. + * + *

+ * Every operation delegates to the wrapped {@code ForyJson} and translates any + * exception raised during serialization or deserialization into a + * {@link PoisonRequestEntryException}, preserving the original exception's + * message and cause. + */ + @RequiredArgsConstructor(access = AccessLevel.PACKAGE) + static class JsonMapperFory implements JsonMapper { + + private final ForyJson foryJson; + + /** + * {@inheritDoc} + * + * @throws PoisonRequestEntryException if the underlying {@link ForyJson} fails + * to serialize {@code value} + */ + @Override + public String toJson(final Object value) throws PoisonRequestEntryException { + try { + return foryJson.toJson(value); + } catch (final Exception ex) { + throw PoisonRequestEntryException.fromJsonProcessing(ex.getMessage(), ex); + } + } + + /** + * {@inheritDoc} + * + * @throws PoisonRequestEntryException if the underlying {@link ForyJson} fails + * to serialize {@code value} + */ + @Override + public byte[] toJsonBytes(final Object value) throws PoisonRequestEntryException { + try { + return foryJson.toJsonBytes(value); + } catch (final Exception ex) { + throw PoisonRequestEntryException.fromJsonProcessing(ex.getMessage(), ex); + } + } + + /** + * {@inheritDoc} + * + * @throws PoisonRequestEntryException if the underlying {@link ForyJson} fails + * to deserialize {@code json} into an + * instance of {@code clazz} + */ + @Override + public T fromJson(final String json, final Class clazz) throws PoisonRequestEntryException { + try { + return foryJson.fromJson(json, clazz); + } catch (final Exception ex) { + throw PoisonRequestEntryException.fromJsonProcessing(ex.getMessage(), ex); + } + } + + /** + * {@inheritDoc} + * + * @throws PoisonRequestEntryException if the underlying {@link ForyJson} fails + * to deserialize {@code bytes} into an + * instance of {@code clazz} + */ + @Override + public T fromJson(final byte[] bytes, final Class clazz) throws PoisonRequestEntryException { + try { + return foryJson.fromJson(bytes, clazz); + } catch (final Exception ex) { + throw PoisonRequestEntryException.fromJsonProcessing(ex.getMessage(), ex); + } + } + + } + +} \ No newline at end of file diff --git a/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/RequestEntryInternalFactory.java b/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/RequestEntryInternalFactory.java index a590d6e..11eedcb 100644 --- a/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/RequestEntryInternalFactory.java +++ b/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/core/RequestEntryInternalFactory.java @@ -20,12 +20,11 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import com.amazon.sns.messaging.lib.exception.PoisonRequestEntryException; import com.amazon.sns.messaging.lib.model.RequestEntry; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.AccessLevel; import lombok.Builder; @@ -38,12 +37,19 @@ /** * Factory for creating internal request entry representations and computing payload sizes. */ -@RequiredArgsConstructor final class RequestEntryInternalFactory { - private final ObjectMapper objectMapper; + private final JsonMapper jsonMapper; - /** + private RequestEntryInternalFactory(final JsonMapper jsonMapper) { + this.jsonMapper = Objects.requireNonNull(jsonMapper, "jsonMapper cannot be null"); + } + + public static RequestEntryInternalFactory build(final JsonMapper jsonMapper) { + return new RequestEntryInternalFactory(jsonMapper); + } + + /**IllegalArgumentException * Creates an internal request entry from a request entry and its serialized payload. * * @param requestEntry the source request entry @@ -82,13 +88,9 @@ public RequestEntryInternal create(final RequestEntry requestEntry) throws Po * @throws PoisonRequestEntryException if the payload fails JSON serialization */ public byte[] convertPayload(final RequestEntry requestEntry) throws PoisonRequestEntryException { - try { - return requestEntry.getValue() instanceof String - ? String.class.cast(requestEntry.getValue()).getBytes(StandardCharsets.UTF_8) - : objectMapper.writeValueAsBytes(requestEntry.getValue()); - } catch (final JsonProcessingException ex) { - throw PoisonRequestEntryException.fromJsonProcessing(ex.getMessage(), ex); - } + return requestEntry.getValue() instanceof String + ? String.class.cast(requestEntry.getValue()).getBytes(StandardCharsets.UTF_8) + : jsonMapper.toJsonBytes(requestEntry.getValue()); } /** @@ -117,8 +119,8 @@ public Integer messageAttributesSize(final RequestEntry requestEntry) { */ @Getter @ToString - @RequiredArgsConstructor @Builder(setterPrefix = "with") + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) static class RequestEntryInternal { /** The creation timestamp in nanoseconds. */ diff --git a/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/model/RequestEntry.java b/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/model/RequestEntry.java index 5d15adb..2eabdda 100644 --- a/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/model/RequestEntry.java +++ b/amazon-sns-java-messaging-lib-template/src/main/java/com/amazon/sns/messaging/lib/model/RequestEntry.java @@ -20,10 +20,11 @@ import java.util.Map; import java.util.UUID; -import lombok.AllArgsConstructor; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; import lombok.ToString; /** @@ -35,27 +36,28 @@ */ @Getter @ToString -@NoArgsConstructor -@AllArgsConstructor -@Builder(setterPrefix = "with") +@Builder(toBuilder = true, setterPrefix = "with") +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class RequestEntry { /** * The creation timestamp in nanoseconds (from {@link System#nanoTime}). */ + @NonNull @Builder.Default - private final long createTime = System.nanoTime(); + private final Long createTime = System.nanoTime(); /** * A unique identifier for this request. */ + @NonNull @Builder.Default private final String id = UUID.randomUUID().toString(); /** * The message payload value. */ - private T value; + private final T value; /** * Optional message attributes / headers. @@ -66,16 +68,16 @@ public class RequestEntry { /** * An optional subject line for the message. */ - private String subject; + private final String subject; /** * The message group ID for FIFO topics. */ - private String groupId; + private final String groupId; /** * The message deduplication ID for FIFO topics. */ - private String deduplicationId; + private final String deduplicationId; } diff --git a/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsConsumerTest.java b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsConsumerTest.java index f445e54..c62890d 100644 --- a/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsConsumerTest.java +++ b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsConsumerTest.java @@ -96,7 +96,7 @@ class AbstractAmazonSnsConsumerTest { @Mock(strictness = Strictness.LENIENT) private ListenableFuture listenableFutureImpl; - private ObjectMapper objectMapper; + private JsonMapper jsonMapper; private ConcurrentMap> pendingRequests; @@ -106,7 +106,7 @@ class AbstractAmazonSnsConsumerTest { @BeforeEach void setUp() { - objectMapper = new ObjectMapper(); + jsonMapper = JsonMapperFactory.create(new ObjectMapper()); pendingRequests = new ConcurrentHashMap<>(); topicRequests = new RingBufferBlockingQueue<>(); publishDecorator = UnaryOperator.identity(); @@ -135,7 +135,7 @@ void testConstructorInitializesTopicRequests() { @Test void testConstructorThrowsNpeWhenTopicPropertyIsNull() { final NullPointerException thrown = assertThrows(NullPointerException.class, () -> - new TestableAmazonSnsConsumer(amazonSnsClient, null, objectMapper, pendingRequests, topicRequests, executorService, publishDecorator) + new TestableAmazonSnsConsumer(amazonSnsClient, null, jsonMapper, pendingRequests, topicRequests, executorService, publishDecorator) ); assertThat(thrown.getMessage(), containsString("topicProperty cannot be null")); @@ -144,7 +144,7 @@ void testConstructorThrowsNpeWhenTopicPropertyIsNull() { @Test void testConstructorThrowsNpeWhenAmazonSnsClientIsNull() { final NullPointerException thrown = assertThrows(NullPointerException.class, () -> - new TestableAmazonSnsConsumer(null, topicProperty, objectMapper, pendingRequests, topicRequests, executorService, publishDecorator) + new TestableAmazonSnsConsumer(null, topicProperty, jsonMapper, pendingRequests, topicRequests, executorService, publishDecorator) ); assertThat(thrown.getMessage(), containsString("amazonSnsClient cannot be null")); @@ -156,13 +156,13 @@ void testConstructorThrowsNpeWhenObjectMapperIsNull() { new TestableAmazonSnsConsumer(amazonSnsClient, topicProperty, null, pendingRequests, topicRequests, executorService, publishDecorator) ); - assertThat(thrown.getMessage(), containsString("objectMapper cannot be null")); + assertThat(thrown.getMessage(), containsString("jsonMapper cannot be null")); } @Test void testConstructorThrowsNpeWhenExecutorServiceIsNull() { final NullPointerException thrown = assertThrows(NullPointerException.class, () -> - new TestableAmazonSnsConsumer(amazonSnsClient, topicProperty, objectMapper, pendingRequests, topicRequests, null, publishDecorator) + new TestableAmazonSnsConsumer(amazonSnsClient, topicProperty, jsonMapper, pendingRequests, topicRequests, null, publishDecorator) ); assertThat(thrown.getMessage(), containsString("executorService cannot be null")); @@ -310,7 +310,7 @@ void testShutdownExecutorService() throws InterruptedException { mockedStatic.when(() -> Executors.newSingleThreadScheduledExecutor(any())).thenReturn(scheduledExecutorService); - try (final TestableAmazonSnsConsumer snsConsumer = new TestableAmazonSnsConsumer(amazonSnsClient, topicProperty, objectMapper, pendingRequests, topicRequests, executorService, publishDecorator)) { + try (final TestableAmazonSnsConsumer snsConsumer = new TestableAmazonSnsConsumer(amazonSnsClient, topicProperty, jsonMapper, pendingRequests, topicRequests, executorService, publishDecorator)) { when(executorService.awaitTermination(anyLong(), any(TimeUnit.class))).thenReturn(false); when(executorService.shutdownNow()).thenReturn(Collections.singletonList(mock())); when(scheduledExecutorService.awaitTermination(anyLong(), any(TimeUnit.class))).thenReturn(true); @@ -336,7 +336,7 @@ void testShutdownScheduledExecutorService() throws InterruptedException { mockedStatic.when(() -> Executors.newSingleThreadScheduledExecutor(any())).thenReturn(scheduledExecutorService); - try (final TestableAmazonSnsConsumer snsConsumer = new TestableAmazonSnsConsumer(amazonSnsClient, topicProperty, objectMapper, pendingRequests, topicRequests, executorService, publishDecorator)) { + try (final TestableAmazonSnsConsumer snsConsumer = new TestableAmazonSnsConsumer(amazonSnsClient, topicProperty, jsonMapper, pendingRequests, topicRequests, executorService, publishDecorator)) { when(scheduledExecutorService.awaitTermination(anyLong(), any(TimeUnit.class))).thenReturn(false); when(scheduledExecutorService.shutdownNow()).thenReturn(Collections.singletonList(mock())); when(executorService.awaitTermination(anyLong(), any(TimeUnit.class))).thenReturn(true); @@ -353,6 +353,30 @@ void testShutdownScheduledExecutorService() throws InterruptedException { } } + @Test + void testShutdownRiseInterruptedException() throws InterruptedException { + try (final MockedStatic mockedStatic = mockStatic(Executors.class)) { + + final ExecutorService executorService = mock(); + final ScheduledExecutorService scheduledExecutorService = mock(); + + mockedStatic.when(() -> Executors.newSingleThreadScheduledExecutor(any())).thenReturn(scheduledExecutorService); + + try (final TestableAmazonSnsConsumer snsConsumer = new TestableAmazonSnsConsumer(amazonSnsClient, topicProperty, jsonMapper, pendingRequests, topicRequests, executorService, publishDecorator)) { + + doAnswer(invocation -> { + throw new InterruptedException("interrupt"); + }).when(executorService).shutdown(); + + final Thread worker = new Thread(snsConsumer::shutdown); + + worker.start(); + + assertThat(Thread.currentThread().isInterrupted(), is(false)); + } + } + } + } @Nested @@ -766,13 +790,13 @@ private String buildPayloadOfBytes(final int targetBytes) { } private void context(final TryConsumer consumer) throws Exception { - try (final TestableAmazonSnsConsumer snsConsumer = new TestableAmazonSnsConsumer(amazonSnsClient, topicProperty, objectMapper, pendingRequests, topicRequests, executorService, publishDecorator)) { + try (final TestableAmazonSnsConsumer snsConsumer = new TestableAmazonSnsConsumer(amazonSnsClient, topicProperty, jsonMapper, pendingRequests, topicRequests, executorService, publishDecorator)) { consumer.accept(snsConsumer); } } private void context(final UnaryOperator trackingDecorator, final TryConsumer consumer) throws Exception { - try (final TestableAmazonSnsConsumer snsConsumer = new TestableAmazonSnsConsumer(amazonSnsClient, topicProperty, objectMapper, pendingRequests, topicRequests, executorService, trackingDecorator)) { + try (final TestableAmazonSnsConsumer snsConsumer = new TestableAmazonSnsConsumer(amazonSnsClient, topicProperty, jsonMapper, pendingRequests, topicRequests, executorService, trackingDecorator)) { consumer.accept(snsConsumer); } } @@ -792,12 +816,12 @@ static class TestableAmazonSnsConsumer extends AbstractAmazonSnsConsumer> pendingRequests, final BlockingQueue> topicRequests, final ExecutorService executorService, final UnaryOperator publishDecorator) { - super(amazonSnsClient, topicProperty, objectMapper, pendingRequests, topicRequests, executorService, publishDecorator); + super(amazonSnsClient, topicProperty, jsonMapper, pendingRequests, topicRequests, executorService, publishDecorator); } @Override diff --git a/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsTemplateTest.java b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsTemplateTest.java index 475d6e9..1485b74 100644 --- a/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsTemplateTest.java +++ b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/AbstractAmazonSnsTemplateTest.java @@ -38,6 +38,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.function.UnaryOperator; +import org.apache.fory.json.ForyJson; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -226,6 +227,14 @@ void testBuilderObjectMapperThrowsWhenNull() { assertThrows(NullPointerException.class, () -> builder.objectMapper(null)); } + @Test + void testBuilderForyJsonThrowsWhenNull() { + final TopicProperty topicProperty = mock(TopicProperty.class); + final AbstractAmazonSnsTemplate.Builder builder = new AbstractAmazonSnsTemplate.Builder<>(b -> null, new Object(), topicProperty); + + assertThrows(NullPointerException.class, () -> builder.foryJson(null)); + } + @Test void testBuilderPublishDecoratorThrowsWhenNull() { final TopicProperty topicProperty = mock(TopicProperty.class); @@ -272,8 +281,8 @@ void testBuilderDefaultObjectMapperIsNotNull() { final TopicProperty topicProperty = mock(TopicProperty.class); final AbstractAmazonSnsTemplate.Builder builder = new AbstractAmazonSnsTemplate.Builder<>(b -> null, new Object(), topicProperty); - assertThat(builder.getObjectMapper(), is(notNullValue())); - assertThat(builder.getObjectMapper(), is(instanceOf(ObjectMapper.class))); + assertThat(builder.getJsonMapper(), is(notNullValue())); + assertThat(builder.getJsonMapper(), is(instanceOf(JsonMapperFactory.JsonMapperJackson.class))); } @Test @@ -316,6 +325,16 @@ void testBuilderObjectMapperReturnsSelf() { assertThat(result, is(equalTo(builder))); } + @Test + void testBuilderForyJsonReturnsSelf() { + final TopicProperty topicProperty = mock(TopicProperty.class); + final AbstractAmazonSnsTemplate.Builder builder = new AbstractAmazonSnsTemplate.Builder<>(b -> null, new Object(), topicProperty); + + final AbstractAmazonSnsTemplate.Builder result = builder.foryJson(ForyJson.builder().build()); + + assertThat(result, is(equalTo(builder))); + } + @Test void testBuilderPublishDecoratorReturnsSelf() { final TopicProperty topicProperty = mock(TopicProperty.class); @@ -425,12 +444,23 @@ void testBuilderSetsPendingRequests() { @Test void testBuilderSetsObjectMapper() { final TopicProperty topicProperty = mock(TopicProperty.class); - final ObjectMapper customMapper = new ObjectMapper(); final AbstractAmazonSnsTemplate.Builder builder = new AbstractAmazonSnsTemplate.Builder<>(b -> null, new Object(), topicProperty); - builder.objectMapper(customMapper); + builder.objectMapper(new ObjectMapper()); + + assertThat(builder.getJsonMapper(), is(notNullValue())); + assertThat(builder.getJsonMapper(), is(instanceOf(JsonMapperFactory.JsonMapperJackson.class))); + } + + @Test + void testBuilderSetsForyJsonMapper() { + final TopicProperty topicProperty = mock(TopicProperty.class); + + final AbstractAmazonSnsTemplate.Builder builder = new AbstractAmazonSnsTemplate.Builder<>(b -> null, new Object(), topicProperty); + builder.foryJson(ForyJson.builder().build()); - assertThat(builder.getObjectMapper(), is(equalTo(customMapper))); + assertThat(builder.getJsonMapper(), is(notNullValue())); + assertThat(builder.getJsonMapper(), is(instanceOf(JsonMapperFactory.JsonMapperFory.class))); } @Test @@ -443,4 +473,4 @@ void testBuilderSetsMeterRegistry() { assertThat(builder.getMeterRegistry(), is(equalTo(customRegistry))); } -} +} \ No newline at end of file diff --git a/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/JsonMapperFactoryTest.java b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/JsonMapperFactoryTest.java new file mode 100644 index 0000000..9111598 --- /dev/null +++ b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/JsonMapperFactoryTest.java @@ -0,0 +1,264 @@ +package com.amazon.sns.messaging.lib.core; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.fory.json.ForyJson; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import com.amazon.sns.messaging.lib.exception.PoisonRequestEntryException; +import com.fasterxml.jackson.databind.ObjectMapper; + +class JsonMapperFactoryTest { + + private static final String SAMPLE_JSON = "{\"key\":\"value\"}"; + + private static final byte[] SAMPLE_JSON_BYTES = SAMPLE_JSON.getBytes(StandardCharsets.UTF_8); + + private static final Object SAMPLE_VALUE = new Object(); + + @Nested + @DisplayName("static create(...) factory methods") + class CreateFactoryMethods { + + @Test + @DisplayName("create(ObjectMapper) throws NPE with descriptive message when null") + void testCreateJacksonNullObjectMapperThrowsNullPointerException() { + final NullPointerException ex = assertThrows(NullPointerException.class, () -> JsonMapperFactory.create((ObjectMapper) null)); + assertThat(ex.getMessage(), is("objectMapper cannot be null")); + } + + @Test + @DisplayName("create(ObjectMapper) returns a non-null JsonMapper for a valid delegate") + void testCreateJacksonValidObjectMapperReturnsMapper() { + final ObjectMapper objectMapper = mock(ObjectMapper.class); + + final JsonMapper mapper = JsonMapperFactory.create(objectMapper); + + assertThat(mapper, is(notNullValue())); + assertThat(mapper, instanceOf(JsonMapperFactory.JsonMapperJackson.class)); + } + + @Test + @DisplayName("create(ForyJson) throws NPE with descriptive message when null") + void testCreateForyNullForyJsonThrowsNullPointerException() { + final NullPointerException ex = assertThrows(NullPointerException.class, () -> JsonMapperFactory.create((ForyJson) null)); + assertThat(ex.getMessage(), is("foryJson cannot be null")); + } + + @Test + @DisplayName("create(ForyJson) returns a non-null JsonMapper for a valid delegate") + void testCreateForyValidForyJsonReturnsMapper() { + final ForyJson foryJson = mock(ForyJson.class); + + final JsonMapper mapper = JsonMapperFactory.create(foryJson); + + assertThat(mapper, is(notNullValue())); + assertThat(mapper, instanceOf(JsonMapperFactory.JsonMapperFory.class)); + } + } + + @Nested + @DisplayName("JsonMapperJackson") + class JsonMapperJacksonTest { + + private ObjectMapper objectMapper; + private JsonMapper jsonMapper; + + @BeforeEach + void setUp() { + objectMapper = mock(ObjectMapper.class); + jsonMapper = new JsonMapperFactory.JsonMapperJackson(objectMapper); + } + + @Test + void testToJsonDelegatesAndReturnsResult() throws Exception { + when(objectMapper.writeValueAsString(SAMPLE_VALUE)).thenReturn(SAMPLE_JSON); + + final String result = jsonMapper.toJson(SAMPLE_VALUE); + + assertThat(result, is(SAMPLE_JSON)); + verify(objectMapper).writeValueAsString(SAMPLE_VALUE); + } + + @Test + void testToJsonDelegateThrowsWrapsInPoisonRequestEntryException() throws Exception { + final IOException cause = new IOException("boom"); + when(objectMapper.writeValueAsString(any())).thenAnswer(invocation -> { throw cause; }); + + final PoisonRequestEntryException ex = assertThrows(PoisonRequestEntryException.class, () -> jsonMapper.toJson(SAMPLE_VALUE)); + + assertThat(ex.getCause(), sameInstance(cause)); + } + + @Test + void testToJsonBytesDelegatesAndReturnsResult() throws Exception { + when(objectMapper.writeValueAsBytes(SAMPLE_VALUE)).thenReturn(SAMPLE_JSON_BYTES); + + final byte[] result = jsonMapper.toJsonBytes(SAMPLE_VALUE); + + assertThat(result, is(SAMPLE_JSON_BYTES)); + verify(objectMapper).writeValueAsBytes(SAMPLE_VALUE); + } + + @Test + void testToJsonBytesDelegateThrowsWrapsInPoisonRequestEntryException() throws Exception { + final IOException cause = new IOException("boom"); + when(objectMapper.writeValueAsBytes(any())).thenAnswer(invocation -> { throw cause; }); + + final PoisonRequestEntryException ex = assertThrows(PoisonRequestEntryException.class, () -> jsonMapper.toJsonBytes(SAMPLE_VALUE)); + + assertThat(ex.getCause(), sameInstance(cause)); + } + + @Test + void testFromJsonStringDelegatesAndReturnsResult() throws Exception { + when(objectMapper.readValue(eq(SAMPLE_JSON), eq(String.class))).thenReturn("value"); + + final String result = jsonMapper.fromJson(SAMPLE_JSON, String.class); + + assertThat(result, is("value")); + verify(objectMapper).readValue(SAMPLE_JSON, String.class); + } + + @Test + void testFromJsonStringDelegateThrowsWrapsInPoisonRequestEntryException() throws Exception { + final IOException cause = new IOException("malformed"); + when(objectMapper.readValue(any(String.class), eq(String.class))).thenAnswer(invocation -> { throw cause; }); + + final PoisonRequestEntryException ex = assertThrows(PoisonRequestEntryException.class, () -> jsonMapper.fromJson(SAMPLE_JSON, String.class)); + + assertThat(ex.getCause(), sameInstance(cause)); + } + + @Test + void testFromJsonBytesDelegatesAndReturnsResult() throws Exception { + when(objectMapper.readValue(eq(SAMPLE_JSON_BYTES), eq(String.class))).thenReturn("value"); + + final String result = jsonMapper.fromJson(SAMPLE_JSON_BYTES, String.class); + + assertThat(result, is("value")); + verify(objectMapper).readValue(SAMPLE_JSON_BYTES, String.class); + } + + @Test + void testFromJsonBytesDelegateThrowsWrapsInPoisonRequestEntryException() throws Exception { + final IOException cause = new IOException("malformed"); + when(objectMapper.readValue(any(byte[].class), eq(String.class))).thenThrow(cause); + + final PoisonRequestEntryException ex = assertThrows(PoisonRequestEntryException.class, () -> jsonMapper.fromJson(SAMPLE_JSON_BYTES, String.class)); + + assertThat(ex.getCause(), sameInstance(cause)); + } + } + + @Nested + @DisplayName("JsonMapperFory") + class JsonMapperForyTest { + + private ForyJson foryJson; + private JsonMapper jsonMapper; + + @BeforeEach + void setUp() { + foryJson = mock(ForyJson.class); + jsonMapper = new JsonMapperFactory.JsonMapperFory(foryJson); + } + + @Test + void testToJsonDelegatesAndReturnsResult() throws Exception { + when(foryJson.toJson(SAMPLE_VALUE)).thenReturn(SAMPLE_JSON); + + final String result = jsonMapper.toJson(SAMPLE_VALUE); + + assertThat(result, is(SAMPLE_JSON)); + verify(foryJson).toJson(SAMPLE_VALUE); + } + + @Test + void testToJsonDelegateThrowsWrapsInPoisonRequestEntryException() { + final RuntimeException cause = new RuntimeException("boom"); + doThrow(cause).when(foryJson).toJson(any()); + + final PoisonRequestEntryException ex = assertThrows(PoisonRequestEntryException.class, () -> jsonMapper.toJson(SAMPLE_VALUE)); + + assertThat(ex.getCause(), sameInstance(cause)); + } + + @Test + void testToJsonBytesDelegatesAndReturnsResult() throws Exception { + when(foryJson.toJsonBytes(SAMPLE_VALUE)).thenReturn(SAMPLE_JSON_BYTES); + + final byte[] result = jsonMapper.toJsonBytes(SAMPLE_VALUE); + + assertThat(result, is(SAMPLE_JSON_BYTES)); + verify(foryJson).toJsonBytes(SAMPLE_VALUE); + } + + @Test + void testToJsonBytesDelegateThrowsWrapsInPoisonRequestEntryException() { + final RuntimeException cause = new RuntimeException("boom"); + doThrow(cause).when(foryJson).toJsonBytes(any()); + + final PoisonRequestEntryException ex = assertThrows(PoisonRequestEntryException.class, () -> jsonMapper.toJsonBytes(SAMPLE_VALUE)); + + assertThat(ex.getCause(), sameInstance(cause)); + } + + @Test + void testFromJsonStringDelegatesAndReturnsResult() throws Exception { + when(foryJson.fromJson(eq(SAMPLE_JSON), eq(String.class))).thenReturn("value"); + + final String result = jsonMapper.fromJson(SAMPLE_JSON, String.class); + + assertThat(result, is("value")); + verify(foryJson).fromJson(SAMPLE_JSON, String.class); + } + + @Test + void testFromJsonStringDelegateThrowsWrapsInPoisonRequestEntryException() { + final RuntimeException cause = new RuntimeException("malformed"); + doThrow(cause).when(foryJson).fromJson(any(String.class), eq(String.class)); + + final PoisonRequestEntryException ex = assertThrows(PoisonRequestEntryException.class, () -> jsonMapper.fromJson(SAMPLE_JSON, String.class)); + + assertThat(ex.getCause(), sameInstance(cause)); + } + + @Test + void testFromJsonBytesDelegatesAndReturnsResult() throws Exception { + when(foryJson.fromJson(eq(SAMPLE_JSON_BYTES), eq(String.class))).thenReturn("value"); + + final String result = jsonMapper.fromJson(SAMPLE_JSON_BYTES, String.class); + + assertThat(result, is("value")); + verify(foryJson).fromJson(SAMPLE_JSON_BYTES, String.class); + } + + @Test + void testFromJsonBytesDelegateThrowsWrapsInPoisonRequestEntryException() { + final RuntimeException cause = new RuntimeException("malformed"); + doThrow(cause).when(foryJson).fromJson(any(byte[].class), eq(String.class)); + + final PoisonRequestEntryException ex = assertThrows(PoisonRequestEntryException.class, () -> jsonMapper.fromJson(SAMPLE_JSON_BYTES, String.class)); + + assertThat(ex.getCause(), sameInstance(cause)); + } + } +} \ No newline at end of file diff --git a/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/JsonMapperPerformanceTest.java b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/JsonMapperPerformanceTest.java new file mode 100644 index 0000000..a43134b --- /dev/null +++ b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/JsonMapperPerformanceTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2022 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 com.amazon.sns.messaging.lib.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.apache.fory.json.ForyJson; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import com.amazon.sns.messaging.lib.helpers.BenchmarkPayloads.LargeMessage; +import com.amazon.sns.messaging.lib.helpers.BenchmarkPayloads.SmallMessage; +import com.amazon.sns.messaging.lib.helpers.TrySupplier; +import com.fasterxml.jackson.databind.ObjectMapper; + +// @formatter:off +@Tag("performance") +class JsonMapperPerformanceTest { + + private static final int WARMUP_ITERATIONS = 20_000; + + private static final int MEASURED_ITERATIONS = 100_000; + + private static final int LARGE_ENTRY_COUNT = 50; + + private static final double MAX_RELATIVE_SLOWDOWN = 5.0; + + private static Stream providePayloads() { + return Stream.of( + Arguments.of("small", (Supplier) SmallMessage::sample, SmallMessage.class), + Arguments.of("large", (Supplier) () -> LargeMessage.sample(LARGE_ENTRY_COUNT), LargeMessage.class) + ); + } + + @Timeout(120) + @MethodSource("providePayloads") + @ParameterizedTest(name = ParameterizedTest.ARGUMENTS_PLACEHOLDER) + void testToJsonAndFromJsonThroughput( + final String payloadLabel, final Supplier payloadSupplier, + final Class payloadType) throws Exception { + System.out.println(String.format("%n=== payload=%s ===", payloadLabel)); + + final JsonMapper jackson = JsonMapperFactory.create(new ObjectMapper()); + final JsonMapper fory = JsonMapperFactory.create(ForyJson.builder().build()); + final Object payload = payloadSupplier.get(); + + final String jacksonJson = jackson.toJson(payload); + final String foryJson = fory.toJson(payload); + final byte[] jacksonBytes = jackson.toJsonBytes(payload); + final byte[] foryBytes = fory.toJsonBytes(payload); + + assertOperation("toJson", payloadLabel, + benchmark(() -> jackson.toJson(payload)), + benchmark(() -> fory.toJson(payload))); + + System.out.println("---"); + + assertOperation("toJsonBytes", payloadLabel, + benchmark(() -> jackson.toJsonBytes(payload)), + benchmark(() -> fory.toJsonBytes(payload))); + + System.out.println("---"); + + assertOperation("fromJson(String)", payloadLabel, + benchmark(() -> jackson.fromJson(jacksonJson, payloadType)), + benchmark(() -> fory.fromJson(foryJson, payloadType))); + + System.out.println("---"); + + assertOperation("fromJson(bytes)", payloadLabel, + benchmark(() -> jackson.fromJson(jacksonBytes, payloadType)), + benchmark(() -> fory.fromJson(foryBytes, payloadType))); + } + + private void assertOperation( + final String operation, + final String payloadLabel, + final double jacksonOpsPerSec, + final double foryOpsPerSec) { + report(operation + " (jackson)", jacksonOpsPerSec); + report(operation + " (fory)", foryOpsPerSec); + + final double faster = Math.max(jacksonOpsPerSec, foryOpsPerSec); + final double slower = Math.min(jacksonOpsPerSec, foryOpsPerSec); + final double relativeSlowdown = faster / slower; + + assertThat(relativeSlowdown) + .as("%s/%s: neither backend should be more than %.1fx slower than the other (jackson=%.0f fory=%.0f ops/sec)", + payloadLabel, operation, MAX_RELATIVE_SLOWDOWN, jacksonOpsPerSec, foryOpsPerSec) + .isLessThan(MAX_RELATIVE_SLOWDOWN); + } + + private static void report(final String label, final double opsPerSec) { + System.out.printf(String.format("%-30s %,15.0f ops/sec%n", label, opsPerSec)); + } + + private double benchmark(final TrySupplier op) throws Exception { + for (int i = 0; i < WARMUP_ITERATIONS; i++) { + op.get(); + } + + final long start = System.nanoTime(); + + for (int i = 0; i < MEASURED_ITERATIONS; i++) { + op.get(); + } + + final long elapsedNanos = System.nanoTime() - start; + + return MEASURED_ITERATIONS / (elapsedNanos / 1_000_000_000.0); + } + +} +// @formatter:on \ No newline at end of file diff --git a/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/RequestEntryInternalFactoryTest.java b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/RequestEntryInternalFactoryTest.java index 29ef165..c2e342b 100644 --- a/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/RequestEntryInternalFactoryTest.java +++ b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/core/RequestEntryInternalFactoryTest.java @@ -34,10 +34,10 @@ import java.util.List; import java.util.Map; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; @@ -52,11 +52,15 @@ @ExtendWith(MockitoExtension.class) class RequestEntryInternalFactoryTest { - @InjectMocks private RequestEntryInternalFactory factory; @Spy - private ObjectMapper objectMapper; + private final JsonMapper jsonMapper = JsonMapperFactory.create(new ObjectMapper()); + + @BeforeEach + void before() { + factory = RequestEntryInternalFactory.build(jsonMapper); + } private RequestEntry buildRequestEntry(final Object payload, final Map headers) { return RequestEntry.builder() @@ -258,7 +262,7 @@ void testCreateWithObjectPayloadSerializesViaJackson() throws Exception { final RequestEntryInternal result = factory.create(entry); final String decoded = result.getMessage(); - final Map parsed = objectMapper.readValue(decoded, Map.class); + final Map parsed = jsonMapper.fromJson(decoded, Map.class); assertThat(parsed.get("key"), equalTo("value")); } @@ -330,7 +334,7 @@ void testConvertPayloadObjectUsesJackson() throws Exception { final byte[] result = factory.convertPayload(entry); - final Map parsed = objectMapper.readValue(result, Map.class); + final Map parsed = jsonMapper.fromJson(result, Map.class); assertThat(parsed.get("a"), equalTo("b")); } @@ -341,7 +345,7 @@ void testConvertPayloadListUsesJackson() throws Exception { final byte[] result = factory.convertPayload(entry); - final List parsed = objectMapper.readValue(result, List.class); + final List parsed = jsonMapper.fromJson(result, List.class); assertThat(parsed.size(), equalTo(3)); } diff --git a/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/helpers/BenchmarkPayloads.java b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/helpers/BenchmarkPayloads.java new file mode 100644 index 0000000..8be0714 --- /dev/null +++ b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/helpers/BenchmarkPayloads.java @@ -0,0 +1,105 @@ +/* + * Copyright 2022 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 com.amazon.sns.messaging.lib.helpers; + +import java.util.LinkedList; +import java.util.List; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class BenchmarkPayloads { + + @Getter + @NoArgsConstructor + @AllArgsConstructor(access = AccessLevel.PRIVATE) + public static class SmallMessage { + + private String id; + + private String body; + + private long timestamp; + + private boolean successful; + + private Attributes attributes; + + public static SmallMessage sample() { + return new SmallMessage("msg-0000000001", "The quick brown fox jumps over the lazy dog.", 1_700_000_000_000L, true, new Attributes("us-east-1", "standard", 3)); + } + + public static SmallMessage sample(final String id) { + return new SmallMessage(id, "The quick brown fox jumps over the lazy dog.", 1_700_000_000_000L, true, new Attributes("us-east-1", "standard", 3)); + } + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class Attributes { + + private String region; + + private String tier; + + private int retryCount; + + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor(access = AccessLevel.PRIVATE) + public static class LargeMessage { + + private String batchId; + + private final List entries = new LinkedList<>(); + + private final List tags = new LinkedList<>(); + + public static LargeMessage sample(final int entryCount) { + final LargeMessage m = new LargeMessage("batch-0000000001"); + + for (int i = 0; i < entryCount; i++) { + m.entries.add(SmallMessage.sample("msg-" + i)); + } + + for (int i = 0; i < 20; i++) { + m.tags.add(new Tag("key-" + i, "value-" + i)); + } + + return m; + } + + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class Tag { + + private String key; + + private String value; + + } + +} \ No newline at end of file diff --git a/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/helpers/TrySupplier.java b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/helpers/TrySupplier.java new file mode 100644 index 0000000..3cb3c94 --- /dev/null +++ b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/helpers/TrySupplier.java @@ -0,0 +1,24 @@ +/* + * Copyright 2022 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 com.amazon.sns.messaging.lib.helpers; + +@FunctionalInterface +public interface TrySupplier { + + T get() throws Exception; + +} \ No newline at end of file diff --git a/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/model/RequestEntryTest.java b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/model/RequestEntryTest.java index 32db175..08b1586 100644 --- a/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/model/RequestEntryTest.java +++ b/amazon-sns-java-messaging-lib-template/src/test/java/com/amazon/sns/messaging/lib/model/RequestEntryTest.java @@ -16,8 +16,13 @@ package com.amazon.sns.messaging.lib.model; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.not; import java.util.HashMap; import java.util.Map; @@ -32,7 +37,7 @@ void testSuccess() { final Map messageHeaders = new HashMap<>(); final RequestEntry requestEntry = RequestEntry.builder() - .withCreateTime(12345) + .withCreateTime(12345L) .withDeduplicationId("deduplicationId") .withGroupId("groupId") .withId("id") @@ -50,5 +55,20 @@ void testSuccess() { assertThat(requestEntry.getValue(), equalTo("value")); } + @Test + void testSuccessMissingFields() { + final RequestEntry requestEntry = RequestEntry.builder() + .withValue("value") + .build(); + + assertThat(requestEntry.getCreateTime(), greaterThan(0L)); + assertThat(requestEntry.getDeduplicationId(), nullValue()); + assertThat(requestEntry.getGroupId(), nullValue()); + assertThat(requestEntry.getId(), not(nullValue())); + assertThat(requestEntry.getMessageHeaders().entrySet(), empty()); + assertThat(requestEntry.getSubject(), nullValue()); + assertThat(requestEntry.getValue(), is("value")); + } + } // @formatter:on diff --git a/amazon-sns-java-messaging-lib-v1/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsConsumerImpl.java b/amazon-sns-java-messaging-lib-v1/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsConsumerImpl.java index f4ac479..304052d 100644 --- a/amazon-sns-java-messaging-lib-v1/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsConsumerImpl.java +++ b/amazon-sns-java-messaging-lib-v1/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsConsumerImpl.java @@ -39,7 +39,6 @@ import com.amazonaws.services.sns.model.PublishBatchRequest; import com.amazonaws.services.sns.model.PublishBatchRequestEntry; import com.amazonaws.services.sns.model.PublishBatchResult; -import com.fasterxml.jackson.databind.ObjectMapper; // @formatter:off /** @@ -61,7 +60,7 @@ class AmazonSnsConsumerImpl extends AbstractAmazonSnsConsumer extends AbstractAmazonSnsConsumer> pendingRequests, final BlockingQueue> topicRequests, final ExecutorService executorService, final UnaryOperator publishDecorator) { - super(amazonSnsClient, topicProperty, objectMapper, pendingRequests, topicRequests, executorService, publishDecorator); + super(amazonSnsClient, topicProperty, jsonMapper, pendingRequests, topicRequests, executorService, publishDecorator); } /** diff --git a/amazon-sns-java-messaging-lib-v1/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplate.java b/amazon-sns-java-messaging-lib-v1/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplate.java index 56866c7..16b3948 100644 --- a/amazon-sns-java-messaging-lib-v1/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplate.java +++ b/amazon-sns-java-messaging-lib-v1/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplate.java @@ -16,17 +16,11 @@ package com.amazon.sns.messaging.lib.core; -import java.util.concurrent.BlockingQueue; -import java.util.function.UnaryOperator; - -import com.amazon.sns.messaging.lib.concurrent.RingBufferBlockingQueue; import com.amazon.sns.messaging.lib.metrics.AmazonSnsConsumerMetricsDecorator; -import com.amazon.sns.messaging.lib.model.RequestEntry; import com.amazon.sns.messaging.lib.model.TopicProperty; import com.amazonaws.services.sns.AmazonSNS; import com.amazonaws.services.sns.model.PublishBatchRequest; import com.amazonaws.services.sns.model.PublishBatchResult; -import com.fasterxml.jackson.databind.ObjectMapper; // @formatter:off /** @@ -47,7 +41,7 @@ private AmazonSnsTemplate(final Builder( builder.getAmazonSnsClient(), builder.getTopicProperty(), - builder.getObjectMapper(), + builder.getJsonMapper(), builder.getPendingRequests(), builder.getTopicRequests(), getExecutorService(builder.getTopicProperty(), builder.getMeterRegistry()), @@ -73,153 +67,5 @@ public static Builder(AmazonSnsTemplate::new, amazonSnsClient, topicProperty); } - /** - * Creates a new v1 SNS template with default settings. - * - * @deprecated since 1.3.0, use {@link #builder(AmazonSNS, TopicProperty)} instead - * - * @param amazonSnsClient the v1 {@link AmazonSNS} client - * @param topicProperty the topic configuration - */ - @Deprecated - public AmazonSnsTemplate( - final AmazonSNS amazonSnsClient, - final TopicProperty topicProperty) { - this(amazonSnsClient, topicProperty, UnaryOperator.identity()); - } - - /** - * Creates a new v1 SNS template with a custom publish decorator. - * - * @deprecated since 1.3.0, use {@link #builder(AmazonSNS, TopicProperty)} and {@link Builder#publishDecorator(UnaryOperator)} instead - * - * @param amazonSnsClient the v1 {@link AmazonSNS} client - * @param topicProperty the topic configuration - * @param publishDecorator a decorator for the publish batch request - */ - @Deprecated - public AmazonSnsTemplate( - final AmazonSNS amazonSnsClient, - final TopicProperty topicProperty, - final UnaryOperator publishDecorator) { - this(amazonSnsClient, topicProperty, new ObjectMapper(), publishDecorator); - } - - /** - * Creates a new v1 SNS template with a custom topic request queue. - * - * @deprecated since 1.3.0, use {@link #builder(AmazonSNS, TopicProperty)} and {@link Builder#topicRequests(BlockingQueue)} instead - * - * @param amazonSnsClient the v1 {@link AmazonSNS} client - * @param topicProperty the topic configuration - * @param topicRequests the blocking queue for topic requests - */ - @Deprecated - public AmazonSnsTemplate( - final AmazonSNS amazonSnsClient, - final TopicProperty topicProperty, - final BlockingQueue> topicRequests) { - this(amazonSnsClient, topicProperty, topicRequests, UnaryOperator.identity()); - } - - /** - * Creates a new v1 SNS template with a custom queue and publish decorator. - * - * @deprecated since 1.3.0, use {@link #builder(AmazonSNS, TopicProperty)} with {@link Builder#topicRequests(BlockingQueue)} and {@link Builder#publishDecorator(UnaryOperator)} instead - * - * @param amazonSnsClient the v1 {@link AmazonSNS} client - * @param topicProperty the topic configuration - * @param topicRequests the blocking queue for topic requests - * @param publishDecorator a decorator for the publish batch request - */ - @Deprecated - public AmazonSnsTemplate( - final AmazonSNS amazonSnsClient, - final TopicProperty topicProperty, - final BlockingQueue> topicRequests, - final UnaryOperator publishDecorator) { - this(amazonSnsClient, topicProperty, topicRequests, new ObjectMapper(), publishDecorator); - } - - /** - * Creates a new v1 SNS template with a custom ObjectMapper. - * - * @deprecated since 1.3.0, use {@link #builder(AmazonSNS, TopicProperty)} and {@link Builder#objectMapper(ObjectMapper)} instead - * - * @param amazonSnsClient the v1 {@link AmazonSNS} client - * @param topicProperty the topic configuration - * @param objectMapper the Jackson ObjectMapper for payload serialization - */ - @Deprecated - public AmazonSnsTemplate( - final AmazonSNS amazonSnsClient, - final TopicProperty topicProperty, - final ObjectMapper objectMapper) { - this(amazonSnsClient, topicProperty, objectMapper, UnaryOperator.identity()); - } - - /** - * Creates a new v1 SNS template with a custom ObjectMapper and publish decorator. - * - * @deprecated since 1.3.0, use {@link #builder(AmazonSNS, TopicProperty)} with {@link Builder#objectMapper(ObjectMapper)} and {@link Builder#publishDecorator(UnaryOperator)} instead - * - * @param amazonSnsClient the v1 {@link AmazonSNS} client - * @param topicProperty the topic configuration - * @param objectMapper the Jackson ObjectMapper for payload serialization - * @param publishDecorator a decorator for the publish batch request - */ - @Deprecated - public AmazonSnsTemplate( - final AmazonSNS amazonSnsClient, - final TopicProperty topicProperty, - final ObjectMapper objectMapper, - final UnaryOperator publishDecorator) { - this(amazonSnsClient, topicProperty, new RingBufferBlockingQueue<>(topicProperty.getMaximumPoolSize() * topicProperty.getMaxBatchSize()), objectMapper, publishDecorator); - } - - /** - * Creates a new v1 SNS template with a custom queue and ObjectMapper. - * - * @deprecated since 1.3.0, use {@link #builder(AmazonSNS, TopicProperty)} with {@link Builder#topicRequests(BlockingQueue)} and {@link Builder#objectMapper(ObjectMapper)} instead - * - * @param amazonSnsClient the v1 {@link AmazonSNS} client - * @param topicProperty the topic configuration - * @param topicRequests the blocking queue for topic requests - * @param objectMapper the Jackson ObjectMapper for payload serialization - */ - @Deprecated - public AmazonSnsTemplate( - final AmazonSNS amazonSnsClient, - final TopicProperty topicProperty, - final BlockingQueue> topicRequests, - final ObjectMapper objectMapper) { - this(amazonSnsClient, topicProperty, topicRequests, objectMapper, UnaryOperator.identity()); - } - - /** - * Creates a new v1 SNS template with full custom configuration. - * - * @deprecated since 1.3.0, use {@link #builder(AmazonSNS, TopicProperty)} with builder setters instead - * - * @param amazonSnsClient the v1 {@link AmazonSNS} client - * @param topicProperty the topic configuration - * @param topicRequests the blocking queue for topic requests - * @param objectMapper the Jackson ObjectMapper for payload serialization - * @param publishDecorator a decorator for the publish batch request - */ - @Deprecated - public AmazonSnsTemplate( - final AmazonSNS amazonSnsClient, - final TopicProperty topicProperty, - final BlockingQueue> topicRequests, - final ObjectMapper objectMapper, - final UnaryOperator publishDecorator) { - this(AmazonSnsTemplate.builder(amazonSnsClient, topicProperty) - .topicRequests(topicRequests) - .objectMapper(objectMapper) - .publishDecorator(publishDecorator) - ); - } - } // @formatter:on diff --git a/amazon-sns-java-messaging-lib-v1/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsProducerAsyncTest.java b/amazon-sns-java-messaging-lib-v1/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsProducerAsyncTest.java index 3d72b37..ff93391 100644 --- a/amazon-sns-java-messaging-lib-v1/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsProducerAsyncTest.java +++ b/amazon-sns-java-messaging-lib-v1/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsProducerAsyncTest.java @@ -226,7 +226,7 @@ void testSuccessBlockingSubmissionPolicy() { .topicArn("arn:aws:sns:us-east-2:000000000000:topic") .build(); - snsTemplate = new AmazonSnsTemplate<>(amazonSNS, topicProperty); + snsTemplate = AmazonSnsTemplate.builder(amazonSNS, topicProperty).build(); when(amazonSNS.publishBatch(any())).thenAnswer(invocation -> { while (true) { diff --git a/amazon-sns-java-messaging-lib-v1/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplateIntegrationTest.java b/amazon-sns-java-messaging-lib-v1/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplateIntegrationTest.java index a17da9f..96db401 100644 --- a/amazon-sns-java-messaging-lib-v1/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplateIntegrationTest.java +++ b/amazon-sns-java-messaging-lib-v1/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplateIntegrationTest.java @@ -36,6 +36,7 @@ import java.util.stream.IntStream; import org.apache.commons.lang3.RandomStringUtils; +import org.apache.fory.json.ForyJson; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -185,6 +186,7 @@ private AmazonSnsTemplate createTemplate( return AmazonSnsTemplate.builder(snsClient, topicProperty) .meterRegistry(new SimpleMeterRegistry()) .topicRequests(new RingBufferBlockingQueue<>(1024)) + .foryJson(ForyJson.builder().build()) .build(); } diff --git a/amazon-sns-java-messaging-lib-v2/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsConsumerImpl.java b/amazon-sns-java-messaging-lib-v2/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsConsumerImpl.java index 39fc92a..f6bec8a 100644 --- a/amazon-sns-java-messaging-lib-v2/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsConsumerImpl.java +++ b/amazon-sns-java-messaging-lib-v2/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsConsumerImpl.java @@ -34,7 +34,6 @@ import com.amazon.sns.messaging.lib.model.ResponseFailEntry; import com.amazon.sns.messaging.lib.model.ResponseSuccessEntry; import com.amazon.sns.messaging.lib.model.TopicProperty; -import com.fasterxml.jackson.databind.ObjectMapper; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.services.sns.SnsClient; @@ -62,7 +61,7 @@ class AmazonSnsConsumerImpl extends AbstractAmazonSnsConsumer extends AbstractAmazonSnsConsumer> pendingRequests, final BlockingQueue> topicRequests, final ExecutorService executorService, final UnaryOperator publishDecorator) { - super(amazonSnsClient, topicProperty, objectMapper, pendingRequests, topicRequests, executorService, publishDecorator); + super(amazonSnsClient, topicProperty, jsonMapper, pendingRequests, topicRequests, executorService, publishDecorator); } /** diff --git a/amazon-sns-java-messaging-lib-v2/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplate.java b/amazon-sns-java-messaging-lib-v2/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplate.java index bbc2153..60b1103 100644 --- a/amazon-sns-java-messaging-lib-v2/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplate.java +++ b/amazon-sns-java-messaging-lib-v2/src/main/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplate.java @@ -16,14 +16,8 @@ package com.amazon.sns.messaging.lib.core; -import java.util.concurrent.BlockingQueue; -import java.util.function.UnaryOperator; - -import com.amazon.sns.messaging.lib.concurrent.RingBufferBlockingQueue; import com.amazon.sns.messaging.lib.metrics.AmazonSnsConsumerMetricsDecorator; -import com.amazon.sns.messaging.lib.model.RequestEntry; import com.amazon.sns.messaging.lib.model.TopicProperty; -import com.fasterxml.jackson.databind.ObjectMapper; import software.amazon.awssdk.services.sns.SnsClient; import software.amazon.awssdk.services.sns.model.PublishBatchRequest; @@ -48,7 +42,7 @@ private AmazonSnsTemplate(final Builder( builder.getAmazonSnsClient(), builder.getTopicProperty(), - builder.getObjectMapper(), + builder.getJsonMapper(), builder.getPendingRequests(), builder.getTopicRequests(), getExecutorService(builder.getTopicProperty(), builder.getMeterRegistry()), @@ -74,153 +68,5 @@ public static Builder(AmazonSnsTemplate::new, amazonSnsClient, topicProperty); } - /** - * Creates a new v2 SNS template with default settings. - * - * @deprecated since 1.3.0, use {@link #builder(SnsClient, TopicProperty)} instead - * - * @param amazonSnsClient the v2 {@link SnsClient} - * @param topicProperty the topic configuration - */ - @Deprecated - public AmazonSnsTemplate( - final SnsClient amazonSnsClient, - final TopicProperty topicProperty) { - this(amazonSnsClient, topicProperty, UnaryOperator.identity()); - } - - /** - * Creates a new v2 SNS template with a custom publish decorator. - * - * @deprecated since 1.3.0, use {@link #builder(SnsClient, TopicProperty)} and {@link Builder#publishDecorator(UnaryOperator)} instead - * - * @param amazonSnsClient the v2 {@link SnsClient} - * @param topicProperty the topic configuration - * @param publishDecorator a decorator for the publish batch request - */ - @Deprecated - public AmazonSnsTemplate( - final SnsClient amazonSnsClient, - final TopicProperty topicProperty, - final UnaryOperator publishDecorator) { - this(amazonSnsClient, topicProperty, new ObjectMapper(), publishDecorator); - } - - /** - * Creates a new v2 SNS template with a custom topic request queue. - * - * @deprecated since 1.3.0, use {@link #builder(SnsClient, TopicProperty)} and {@link Builder#topicRequests(BlockingQueue)} instead - * - * @param amazonSnsClient the v2 {@link SnsClient} - * @param topicProperty the topic configuration - * @param topicRequests the blocking queue for topic requests - */ - @Deprecated - public AmazonSnsTemplate( - final SnsClient amazonSnsClient, - final TopicProperty topicProperty, - final BlockingQueue> topicRequests) { - this(amazonSnsClient, topicProperty, topicRequests, UnaryOperator.identity()); - } - - /** - * Creates a new v2 SNS template with a custom queue and publish decorator. - * - * @deprecated since 1.3.0, use {@link #builder(SnsClient, TopicProperty)} with {@link Builder#topicRequests(BlockingQueue)} and {@link Builder#publishDecorator(UnaryOperator)} instead - * - * @param amazonSnsClient the v2 {@link SnsClient} - * @param topicProperty the topic configuration - * @param topicRequests the blocking queue for topic requests - * @param publishDecorator a decorator for the publish batch request - */ - @Deprecated - public AmazonSnsTemplate( - final SnsClient amazonSnsClient, - final TopicProperty topicProperty, - final BlockingQueue> topicRequests, - final UnaryOperator publishDecorator) { - this(amazonSnsClient, topicProperty, topicRequests, new ObjectMapper(), publishDecorator); - } - - /** - * Creates a new v2 SNS template with a custom ObjectMapper. - * - * @deprecated since 1.3.0, use {@link #builder(SnsClient, TopicProperty)} and {@link Builder#objectMapper(ObjectMapper)} instead - * - * @param amazonSnsClient the v2 {@link SnsClient} - * @param topicProperty the topic configuration - * @param objectMapper the Jackson ObjectMapper for payload serialization - */ - @Deprecated - public AmazonSnsTemplate( - final SnsClient amazonSnsClient, - final TopicProperty topicProperty, - final ObjectMapper objectMapper) { - this(amazonSnsClient, topicProperty, objectMapper, UnaryOperator.identity()); - } - - /** - * Creates a new v2 SNS template with a custom ObjectMapper and publish decorator. - * - * @deprecated since 1.3.0, use {@link #builder(SnsClient, TopicProperty)} with {@link Builder#objectMapper(ObjectMapper)} and {@link Builder#publishDecorator(UnaryOperator)} instead - * - * @param amazonSnsClient the v2 {@link SnsClient} - * @param topicProperty the topic configuration - * @param objectMapper the Jackson ObjectMapper for payload serialization - * @param publishDecorator a decorator for the publish batch request - */ - @Deprecated - public AmazonSnsTemplate( - final SnsClient amazonSnsClient, - final TopicProperty topicProperty, - final ObjectMapper objectMapper, - final UnaryOperator publishDecorator) { - this(amazonSnsClient, topicProperty, new RingBufferBlockingQueue<>(topicProperty.getMaximumPoolSize() * topicProperty.getMaxBatchSize()), objectMapper, publishDecorator); - } - - /** - * Creates a new v2 SNS template with a custom queue and ObjectMapper. - * - * @deprecated since 1.3.0, use {@link #builder(SnsClient, TopicProperty)} with {@link Builder#topicRequests(BlockingQueue)} and {@link Builder#objectMapper(ObjectMapper)} instead - * - * @param amazonSnsClient the v2 {@link SnsClient} - * @param topicProperty the topic configuration - * @param topicRequests the blocking queue for topic requests - * @param objectMapper the Jackson ObjectMapper for payload serialization - */ - @Deprecated - public AmazonSnsTemplate( - final SnsClient amazonSnsClient, - final TopicProperty topicProperty, - final BlockingQueue> topicRequests, - final ObjectMapper objectMapper) { - this(amazonSnsClient, topicProperty, topicRequests, objectMapper, UnaryOperator.identity()); - } - - /** - * Creates a new v2 SNS template with full custom configuration. - * - * @deprecated since 1.3.0, use {@link #builder(SnsClient, TopicProperty)} with builder setters instead - * - * @param amazonSnsClient the v2 {@link SnsClient} - * @param topicProperty the topic configuration - * @param topicRequests the blocking queue for topic requests - * @param objectMapper the Jackson ObjectMapper for payload serialization - * @param publishDecorator a decorator for the publish batch request - */ - @Deprecated - public AmazonSnsTemplate( - final SnsClient amazonSnsClient, - final TopicProperty topicProperty, - final BlockingQueue> topicRequests, - final ObjectMapper objectMapper, - final UnaryOperator publishDecorator) { - this(AmazonSnsTemplate.builder(amazonSnsClient, topicProperty) - .topicRequests(topicRequests) - .objectMapper(objectMapper) - .publishDecorator(publishDecorator) - ); - } - } // @formatter:on diff --git a/amazon-sns-java-messaging-lib-v2/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsProducerAsyncTest.java b/amazon-sns-java-messaging-lib-v2/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsProducerAsyncTest.java index ad7a5fc..53c08e4 100644 --- a/amazon-sns-java-messaging-lib-v2/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsProducerAsyncTest.java +++ b/amazon-sns-java-messaging-lib-v2/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsProducerAsyncTest.java @@ -217,7 +217,7 @@ void testSuccessBlockingSubmissionPolicy() { .topicArn("arn:aws:sns:us-east-2:000000000000:topic") .build(); - snsTemplate = new AmazonSnsTemplate<>(amazonSNS, topicProperty); + snsTemplate = AmazonSnsTemplate.builder(amazonSNS, topicProperty).build(); when(amazonSNS.publishBatch(any(PublishBatchRequest.class))).thenAnswer(invocation -> { while (true) { diff --git a/amazon-sns-java-messaging-lib-v2/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplateIntegrationTest.java b/amazon-sns-java-messaging-lib-v2/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplateIntegrationTest.java index c265344..617638d 100644 --- a/amazon-sns-java-messaging-lib-v2/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplateIntegrationTest.java +++ b/amazon-sns-java-messaging-lib-v2/src/test/java/com/amazon/sns/messaging/lib/core/AmazonSnsTemplateIntegrationTest.java @@ -38,6 +38,7 @@ import java.util.stream.IntStream; import org.apache.commons.lang3.RandomStringUtils; +import org.apache.fory.json.ForyJson; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -185,6 +186,7 @@ private AmazonSnsTemplate createTemplate( return AmazonSnsTemplate.builder(snsClient, topicProperty) .meterRegistry(new SimpleMeterRegistry()) .topicRequests(new RingBufferBlockingQueue<>(1024)) + .foryJson(ForyJson.builder().build()) .build(); } diff --git a/lombok.config b/lombok.config index a23edb4..80f94d6 100644 --- a/lombok.config +++ b/lombok.config @@ -1,2 +1,3 @@ config.stopBubbling = true -lombok.addLombokGeneratedAnnotation = true \ No newline at end of file +lombok.addLombokGeneratedAnnotation = true +lombok.nonNull.exceptionType = IllegalArgumentException \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9b9fe29..2672594 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ 1.3.14 4.11.0 5.10.2 - 2.16.1 + 2.16.1 4.3.0 3.24.2 3.20.0 @@ -37,6 +37,7 @@ 1.16.3 1.20.4 1.10.1 + 1.6.0 3.5.0 @@ -110,6 +111,33 @@ amazon-sns-java-messaging-lib-v2 ${project.version} + + com.fasterxml.jackson + jackson-bom + ${jackson.version} + pom + import + + + org.apache.fory + fory-json + ${fory.version} + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + org.apache.commons + commons-collections4 + ${commons-collections4.version} + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + io.micrometer micrometer-bom @@ -131,25 +159,26 @@ org.slf4j slf4j-api - ${slf4j-api.version} org.apache.commons commons-collections4 - ${commons-collections4.version} org.apache.commons commons-lang3 - ${commons-lang3.version} + + + + org.apache.fory + fory-json com.fasterxml.jackson.core jackson-databind - ${jackson-databind.version}