From 22298613521f624ed75bf9e79dddb12f353b3d9e Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 8 May 2025 20:41:33 +0100 Subject: [PATCH 01/69] Fix EventHandlerLoaderTest failing after PojoSerializerLoaderTest (#542) --- .../lambda/runtime/api/client/PojoSerializerLoaderTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java index 7c6e9dcb4..c2c887973 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java @@ -7,6 +7,7 @@ import com.amazonaws.services.lambda.runtime.CustomPojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,6 +32,7 @@ class PojoSerializerLoaderTest { @Mock private CustomPojoSerializer mockSerializer; + @AfterEach @BeforeEach void setUp() throws Exception { resetStaticFields(); From 317b5ada21d3d31b55c948b62b2f978d97c89419 Mon Sep 17 00:00:00 2001 From: Ramisa Alam <44811300+ramisa2108@users.noreply.github.com> Date: Mon, 26 May 2025 11:31:32 +0100 Subject: [PATCH 02/69] Add tenant id to Context interface (#545) --- .../com/amazonaws/services/lambda/runtime/Context.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java index a0850e78c..152765df1 100644 --- a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java @@ -100,4 +100,13 @@ public interface Context { */ LambdaLogger getLogger(); + /** + * + * Returns the tenant ID associated with the request. + * + * @return null by default + */ + default String getTenantId() { + return null; + } } From 45ffaeff9b22d0007e13daabc8fb33025e1910de Mon Sep 17 00:00:00 2001 From: Ramisa Alam <44811300+ramisa2108@users.noreply.github.com> Date: Mon, 26 May 2025 13:32:14 +0100 Subject: [PATCH 03/69] Bump core version to 1.3.0 (#546) --- README.md | 2 +- aws-lambda-java-core/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-core/pom.xml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fdc08a759..7f4970949 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ public class HandlerStream implements RequestStreamHandler { com.amazonaws aws-lambda-java-core - 1.2.3 + 1.3.0 ``` diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md index ebd0566ff..527e7dd46 100644 --- a/aws-lambda-java-core/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### May 26, 2025 +`1.3.0` +- Adding support for multi tenancy ([#545](https://github.com/aws/aws-lambda-java-libs/pull/545)) + ### August 17, 2023 `1.2.3`: - Extended logger interface with level-aware logging backend functions diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index 0dd848a96..2b3abc0ba 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-core - 1.2.3 + 1.3.0 jar AWS Lambda Java Core Library From 5a0f8ad9e9b8ff61ba626e71c4b5de62134e82e4 Mon Sep 17 00:00:00 2001 From: Ramisa Alam <44811300+ramisa2108@users.noreply.github.com> Date: Mon, 26 May 2025 15:23:45 +0100 Subject: [PATCH 04/69] Bump RIC to 2.7.0 (#544) --- README.md | 2 +- aws-lambda-java-runtime-interface-client/README.md | 4 ++-- aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-runtime-interface-client/pom.xml | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7f4970949..b1293c42d 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 8a95e7ded..d49201bd5 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 @@ -160,7 +160,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index 6a781b270..ac073ae85 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### May 21, 2025 +`2.7.0` +- Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540)) + ### August 7, 2024 `2.6.0` - Runtime API client improvements: use Lambda-Runtime-Function-Error-Type for reporting errors in format "Runtime." diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index e84cac0da..d4f7fd5e3 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 jar AWS Lambda Java Runtime Interface Client @@ -61,7 +61,7 @@ com.amazonaws aws-lambda-java-core - 1.2.3 + 1.3.0 com.amazonaws From f001ae78f6f6f8a76dd260305934951f4d61e5de Mon Sep 17 00:00:00 2001 From: karthikpswamy Date: Tue, 17 Jun 2025 09:43:00 -0700 Subject: [PATCH 05/69] Adding Key/Value Schema metadata attributes for KafkaEvent (#548) Co-authored-by: Karthik Puttaswamy --- .../services/lambda/runtime/events/KafkaEvent.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/KafkaEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/KafkaEvent.java index dd051d48f..aa6c00de3 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/KafkaEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/KafkaEvent.java @@ -43,6 +43,8 @@ public static class KafkaEventRecord { private String key; private String value; private List> headers; + private SchemaMetadata keySchemaMetadata; + private SchemaMetadata valueSchemaMetadata; } @Data @@ -59,4 +61,13 @@ public String toString() { return topic + "-" + partition; } } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder(setterPrefix = "with") + public static class SchemaMetadata { + private String schemaId; + private String dataFormat; + } } From 95dc035b2a3200be04eaa48cef6053404250e547 Mon Sep 17 00:00:00 2001 From: karthikpswamy Date: Wed, 18 Jun 2025 02:17:42 -0700 Subject: [PATCH 06/69] Bump aws-lambda-java-events version to 3.16.0 (#549) * Adding Key/Value Schema metadata attributes for KafkaEvent * Bump aws-lambda-java-events version to 3.16.0 --------- Co-authored-by: Karthik Puttaswamy --- README.md | 2 +- aws-lambda-java-events/README.md | 2 +- aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- .../custom-serialization/fastJson/HelloWorldFunction/pom.xml | 2 +- samples/custom-serialization/gson/HelloWorldFunction/pom.xml | 2 +- samples/custom-serialization/moshi/HelloWorldFunction/pom.xml | 2 +- .../request-stream-handler/HelloWorldFunction/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 2 +- 10 files changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b1293c42d..b5153a87f 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ public class SqsHandler implements RequestHandler { com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 ``` diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 87c61f345..43c25d76a 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -74,7 +74,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 ... diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index 6c1769751..a4bcd10a0 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 17, 2025 +`3.16.0`: +- Add Schema metadata related attributes in KafkaEvent ([#548](https://github.com/aws/aws-lambda-java-libs/pull/548)) + ### January 31, 2025 `3.15.0`: - Fix `CognitoUserPoolPreTokenGenerationEventV2` model ([#519](https://github.com/aws/aws-lambda-java-libs/pull/519)) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index f1364e7ab..8799966be 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 6bb4ac8a6..0ab074cce 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 org.junit.jupiter diff --git a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml index 7325c72a0..7d3c44246 100644 --- a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 diff --git a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml index dd3b8e9c5..fd4271824 100644 --- a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 com.google.code.gson diff --git a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml index f23214976..2773ef1f1 100644 --- a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 diff --git a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml index 68e7e81e9..f6ca21bf7 100644 --- a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 com.google.code.gson diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 3d03205d3..56c959244 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -46,7 +46,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 From e20395d6d6b5ec14cf9a868645dc7c1e68660cb8 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Thu, 10 Jul 2025 15:57:01 +0100 Subject: [PATCH 07/69] Add sample event --- aws-lambda-java-tests/src/test/resources/connect_event.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/aws-lambda-java-tests/src/test/resources/connect_event.json b/aws-lambda-java-tests/src/test/resources/connect_event.json index a9e04f7f8..b71bf6692 100644 --- a/aws-lambda-java-tests/src/test/resources/connect_event.json +++ b/aws-lambda-java-tests/src/test/resources/connect_event.json @@ -22,7 +22,10 @@ } }, "PreviousContactId": "4ca32fbd-8f92-46af-92a5-6b0f970f0efe", - "Queue": null, + "Queue": { + "Name": "SampleQueue", + "ARN": "arn:aws:connect:eu-central-1:123456789012:instance/9308c2a1-9bc6-4cea-8290-6c0b4a6d38fa" + }, "SystemEndpoint": { "Address": "+21234567890", "Type": "TELEPHONE_NUMBER" From 4c3ef97ec05950c00bdfbeeff46c3b6836559dc3 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Thu, 10 Jul 2025 16:00:31 +0100 Subject: [PATCH 08/69] Add class to ConnectEvent.java --- .../services/lambda/runtime/events/ConnectEvent.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ConnectEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ConnectEvent.java index 38547ac2a..e94875614 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ConnectEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ConnectEvent.java @@ -59,7 +59,7 @@ public static class ContactData implements Serializable, Cloneable { private String initiationMethod; private String instanceArn; private String previousContactId; - private String queue; + private Queue queue; private SystemEndpoint systemEndpoint; } @@ -80,4 +80,13 @@ public static class SystemEndpoint implements Serializable, Cloneable { private String address; private String type; } + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class Queue implements Serializable, Cloneable { + private String name; + private String ARN; + } + } From 8dea6c7534dcc3e495704239daf6e6179153df7d Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Thu, 10 Jul 2025 16:17:18 +0100 Subject: [PATCH 09/69] Add test --- .../services/lambda/runtime/tests/EventLoaderTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java index 86ad73228..752b84e27 100644 --- a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java @@ -333,6 +333,14 @@ public void testLoadConnectEvent() { assertThat(contactData.getSystemEndpoint()) .returns("+21234567890",from(ConnectEvent.SystemEndpoint::getAddress)) .returns("TELEPHONE_NUMBER",from(ConnectEvent.SystemEndpoint::getType)); + + assertThat(contactData.getQueue()) + .isNotNull() + .returns("SampleQueue", from(ConnectEvent.Queue::getName)) + .returns("arn:aws:connect:eu-central-1:123456789012:instance/9308c2a1-9bc6-4cea-8290-6c0b4a6d38fa", + from(ConnectEvent.Queue::getARN) + ); + } @Test From 19cdddc13cad041a600b386c2fe756750c0d71f3 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Thu, 10 Jul 2025 16:26:48 +0100 Subject: [PATCH 10/69] Add mixin, register it, and register the fields too. --- .../serialization/events/LambdaEventSerializers.java | 7 ++++++- .../serialization/events/mixins/ConnectEventMixin.java | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java index 4173211e1..3b10b198e 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java @@ -118,6 +118,7 @@ public class LambdaEventSerializers { ConnectEventMixin.ContactDataMixin.class), new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint", ConnectEventMixin.CustomerEndpointMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", ConnectEventMixin.QueueMixin.class), new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint", ConnectEventMixin.SystemEndpointMixin.class), new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", @@ -170,6 +171,7 @@ public class LambdaEventSerializers { new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"), new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"), new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"), new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))), new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", Arrays.asList( @@ -214,7 +216,10 @@ public class LambdaEventSerializers { */ private static final Map NAMING_STRATEGY_MAP = Stream.of( new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - new PropertyNamingStrategy.PascalCaseStrategy())) + new PropertyNamingStrategy.PascalCaseStrategy()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", + new PropertyNamingStrategy.PascalCaseStrategy()) + ) .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)); /** diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/mixins/ConnectEventMixin.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/mixins/ConnectEventMixin.java index 529a33b39..1645fdaee 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/mixins/ConnectEventMixin.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/mixins/ConnectEventMixin.java @@ -65,8 +65,8 @@ public abstract class ContactDataMixin { @JsonProperty("PreviousContactId") abstract void setPreviousContactId(String previousContactId); // needed because Jackson expects "queue" instead of "Queue" - @JsonProperty("Queue") abstract String getQueue(); - @JsonProperty("Queue") abstract void setQueue(String queue); + @JsonProperty("Queue") abstract Map getQueue(); + @JsonProperty("Queue") abstract void setQueue(Map queue); // needed because Jackson expects "systemEndpoint" instead of "SystemEndpoint" @JsonProperty("SystemEndpoint") abstract Map getSystemEndpoint(); @@ -95,4 +95,9 @@ public abstract class SystemEndpointMixin { @JsonProperty("Type") abstract String getType(); @JsonProperty("Type") abstract void setType(String type); } + + public abstract class QueueMixin { + @JsonProperty("Name") abstract String getName(); + @JsonProperty("Name") abstract void setName(String name); + } } From a9e47682bd11359cf2288194ec7600adc55ef8a0 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Tue, 15 Jul 2025 09:08:43 +0100 Subject: [PATCH 11/69] =?UTF-8?q?Version=20bump:=20serialization=201.1.5?= =?UTF-8?q?=E2=86=921.1.6,=20events=203.16.0=E2=86=923.16.1,=20update=20de?= =?UTF-8?q?pendencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump aws-lambda-java-serialization from 1.1.5 to 1.1.6 - Bump aws-lambda-java-events from 3.16.0 to 3.16.1 - Update aws-lambda-java-tests dependencies to use new versions - Update aws-lambda-java-runtime-interface-client serialization dependency 1.1.2โ†’1.1.6 - Update aws-lambda-java-events-sdk-transformer events dependency 3.11.2โ†’3.16.1 --- aws-lambda-java-events-sdk-transformer/pom.xml | 2 +- aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- aws-lambda-java-serialization/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index 6a2b1735c..e67ff81c3 100644 --- a/aws-lambda-java-events-sdk-transformer/pom.xml +++ b/aws-lambda-java-events-sdk-transformer/pom.xml @@ -63,7 +63,7 @@ com.amazonaws aws-lambda-java-events - 3.11.2 + 3.16.1 provided diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 8799966be..925273e9b 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.16.1 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index d4f7fd5e3..c82890b0f 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -66,7 +66,7 @@ com.amazonaws aws-lambda-java-serialization - 1.1.2 + 1.1.6 diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index 07ccecc8c..2f8e76138 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-lambda-java-serialization - 1.1.5 + 1.1.6 jar AWS Lambda Java Runtime Serialization diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 0ab074cce..aa028769e 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -40,12 +40,12 @@ com.amazonaws aws-lambda-java-serialization - 1.1.5 + 1.1.6 com.amazonaws aws-lambda-java-events - 3.16.0 + 3.16.1 org.junit.jupiter From 70e2d8b83dd6ffd2b1fe42f386b7bdf9a8d3f1c6 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Tue, 15 Jul 2025 09:26:46 +0100 Subject: [PATCH 12/69] Fix runtime interface client workflows to use local serialization dependency Add local build step for aws-lambda-java-serialization before building runtime interface client. ## Why This Fix is Needed The runtime interface client depends on aws-lambda-java-serialization version 1.1.6, but this version doesn't exist in Maven Central yet. By building and installing the serialization package locally first, we ensure: 1. The correct version (1.1.6) is available in the local Maven repository 2. The runtime interface client build won't fail looking for a non-existent version on Maven Central 3. The workflow tests the actual code changes together ## Changes Made - runtime-interface-client_merge_to_main.yml: Added local serialization build step - runtime-interface-client_pr.yml: Added local serialization build step to both smoke-test and build jobs This ensures CI/CD pipeline works correctly with the new dependency versions. --- .../workflows/runtime-interface-client_merge_to_main.yml | 4 ++++ .github/workflows/runtime-interface-client_pr.yml | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index e07b191e1..88f8afde2 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -47,6 +47,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 33c6df50b..94c034562 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -23,6 +23,10 @@ jobs: java-version: 8 distribution: corretto + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Runtime Interface Client smoke tests - Run 'pr' target working-directory: ./aws-lambda-java-runtime-interface-client run: make pr @@ -51,6 +55,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build From 37309c0d9f60bd2dfc80bd760c1fa9d05d80bba9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 11:20:22 +0000 Subject: [PATCH 13/69] Bump org.apache.commons:commons-lang3 in /aws-lambda-java-tests Bumps org.apache.commons:commons-lang3 from 3.12.0 to 3.18.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-lang3 dependency-version: 3.18.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- aws-lambda-java-tests/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index aa028769e..f73bee5f1 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -65,7 +65,7 @@ org.apache.commons commons-lang3 - 3.12.0 + 3.18.0 From d9f3a9c6f6ffd0074427e7cd7fcd35b84b58dd72 Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:09:19 +0100 Subject: [PATCH 14/69] Update sdk transformer pom.xml patch version for serialization and events changes --- aws-lambda-java-events-sdk-transformer/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index e67ff81c3..9b6afece1 100644 --- a/aws-lambda-java-events-sdk-transformer/pom.xml +++ b/aws-lambda-java-events-sdk-transformer/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events-sdk-transformer - 3.1.0 + 3.1.1 jar AWS Lambda Java Events SDK Transformer Library From e9320fa87361e9fb757fbf19748129d0cac8bcbd Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:12:48 +0100 Subject: [PATCH 15/69] Update ric pom.xml --- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index c82890b0f..979f4b7ba 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.7.1 jar AWS Lambda Java Runtime Interface Client From 8147e7afa30057d63733d79aadc2630da51094df Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:14:19 +0100 Subject: [PATCH 16/69] Update test pom.xml --- aws-lambda-java-tests/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index f73bee5f1..da07133c1 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-lambda-java-tests - 1.1.1 + 1.1.2 jar AWS Lambda Java Tests From e54cf69b3751868b1adaf4824bb82744ed1fdfd1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 13:58:32 +0100 Subject: [PATCH 17/69] Bump actions/checkout from 4 to 5 (#559) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/aws-lambda-java-core.yml | 2 +- .github/workflows/aws-lambda-java-events-sdk-transformer.yml | 2 +- .github/workflows/aws-lambda-java-events.yml | 2 +- .github/workflows/aws-lambda-java-log4j2.yml | 2 +- .github/workflows/aws-lambda-java-profiler.yml | 2 +- .github/workflows/aws-lambda-java-serialization.yml | 2 +- .github/workflows/aws-lambda-java-tests.yml | 2 +- .github/workflows/repo-sync.yml | 2 +- .github/workflows/runtime-interface-client_merge_to_main.yml | 2 +- .github/workflows/runtime-interface-client_pr.yml | 4 ++-- .github/workflows/samples.yml | 4 ++-- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml index 267d901c9..c8064513c 100644 --- a/.github/workflows/aws-lambda-java-core.yml +++ b/.github/workflows/aws-lambda-java-core.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml index 66f6b2bfe..285848a9f 100644 --- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml +++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml index 04ab53a50..b3b360b45 100644 --- a/.github/workflows/aws-lambda-java-events.yml +++ b/.github/workflows/aws-lambda-java-events.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml index 7ae54cbe1..03718e602 100644 --- a/.github/workflows/aws-lambda-java-log4j2.yml +++ b/.github/workflows/aws-lambda-java-log4j2.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index db9fc225e..880320953 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -22,7 +22,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK uses: actions/setup-java@v4 diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index c24c48d72..b2700e088 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml index a28bca886..1b818014a 100644 --- a/.github/workflows/aws-lambda-java-tests.yml +++ b/.github/workflows/aws-lambda-java-tests.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml index 25f05029a..300341c1f 100644 --- a/.github/workflows/repo-sync.yml +++ b/.github/workflows/repo-sync.yml @@ -16,7 +16,7 @@ jobs: env: IS_CONFIGURED: ${{ secrets.SOURCE_REPO != '' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 if: ${{ env.IS_CONFIGURED == 'true' }} - uses: repo-sync/github-sync@v2 name: Sync repo to branch diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 88f8afde2..8909d56bf 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -28,7 +28,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 94c034562..c090609c3 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -15,7 +15,7 @@ jobs: smoke-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 @@ -36,7 +36,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 8346b7c2f..2b5e7833f 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -18,7 +18,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: @@ -42,7 +42,7 @@ jobs: custom-serialization: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # Set up both Java 8 and 21 - name: Set up Java 8 and 21 uses: actions/setup-java@v4 From 640b7665b429e98d6b4941d39f24c2e43a4db25c Mon Sep 17 00:00:00 2001 From: AntoniaSzecsi <117035062+AntoniaSzecsi@users.noreply.github.com> Date: Thu, 21 Aug 2025 12:15:19 +0100 Subject: [PATCH 18/69] Enabling Local Testing with RIE and Improving Tests Coverability (#560) * Add local testing support with RIE * Improve code test coverage from 0.45 to 0.5 * Replace the hardcoded versions with RELEASE versions * Replace Dockerfile base image with image that has RIE preinstalled * Change from mvn central artifacts to locally built ones * Update from java 11 to java 21 and remove duplicate entries from pom * Clean up test coverage configuration * Update README to reflect changes --------- Co-authored-by: AntoniaSzecsi --- .../Dockerfile.rie | 8 ++++ .../Makefile | 6 ++- .../README.md | 43 +++++++++++++++++ .../pom.xml | 18 ++++++++ .../scripts/test-rie.sh | 46 +++++++++++++++++++ .../test-handlers/EchoHandler.java | 20 ++++++++ 6 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 aws-lambda-java-runtime-interface-client/Dockerfile.rie create mode 100755 aws-lambda-java-runtime-interface-client/scripts/test-rie.sh create mode 100644 aws-lambda-java-runtime-interface-client/test-handlers/EchoHandler.java diff --git a/aws-lambda-java-runtime-interface-client/Dockerfile.rie b/aws-lambda-java-runtime-interface-client/Dockerfile.rie new file mode 100644 index 000000000..66a01c834 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/Dockerfile.rie @@ -0,0 +1,8 @@ +FROM public.ecr.aws/lambda/java:21 + +COPY target/aws-lambda-java-runtime-interface-client-*.jar ${LAMBDA_TASK_ROOT}/ +COPY target/aws-lambda-java-core-*.jar ${LAMBDA_TASK_ROOT}/ +COPY target/aws-lambda-java-serialization-*.jar ${LAMBDA_TASK_ROOT}/ +COPY test-handlers/EchoHandler.class ${LAMBDA_TASK_ROOT}/ + +CMD ["EchoHandler::handleRequest"] \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/Makefile b/aws-lambda-java-runtime-interface-client/Makefile index b3a204213..6c3a268fb 100644 --- a/aws-lambda-java-runtime-interface-client/Makefile +++ b/aws-lambda-java-runtime-interface-client/Makefile @@ -65,6 +65,10 @@ publish: test-publish: ./ric-dev-environment/test-platform-specific-jar-snapshot.sh +.PHONY: test-rie +test-rie: + ./scripts/test-rie.sh "EchoHandler::handleRequest" + define HELP_MESSAGE Usage: $ make [TARGETS] @@ -74,5 +78,5 @@ TARGETS dev Run all development tests after a change. pr Perform all checks before submitting a Pull Request. test Run the Unit tests. - + test-rie Build and test RIC locally with Lambda Runtime Interface Emulator. (Requires building the project first) endef diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index d49201bd5..67a5972d6 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -138,6 +138,49 @@ This command invokes the function running in the container image and returns a r *Alternately, you can also include RIE as a part of your base image. See the AWS documentation on how to [Build RIE into your base image](https://docs.aws.amazon.com/lambda/latest/dg/images-test.html#images-test-alternative).* +### Automated Local Testing + +For developers working on this runtime interface client, we provide an automated testing script that handles RIE setup, dependency management, and Docker orchestration. + +*Prerequisites:* +- Build the project first: `mvn clean install` +- Docker must be installed and running + +*To run automated tests:* + +```shell script +make test-rie +``` + +This single command will: +- Automatically download required dependencies (aws-lambda-java-core, aws-lambda-java-serialization) +- Build a Docker image with RIE pre-installed +- Compile and run a test Lambda function (EchoHandler) +- Execute the function and validate the response +- Clean up containers automatically + +The test uses a simple EchoHandler that returns the input event, making it easy to verify the runtime interface client is working correctly. + +## Test Coverage + +This project uses JaCoCo for code coverage analysis. To exclude classes from JaCoCo coverage, add them to the `jacoco-maven-plugin` configuration: + +```xml + + org.jacoco + jacoco-maven-plugin + + + **/*Exception.class + **/dto/*.class + **/YourClassName.class + + + +``` + +This project excludes by default: exceptions, interfaces, DTOs, constants, and runtime-only classes. + ### Troubleshooting While running integration tests, you might encounter the Docker Hub rate limit error with the following body: diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 979f4b7ba..84c6d816c 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -214,6 +214,24 @@ org.jacoco jacoco-maven-plugin ${jacoco.maven.plugin.version} + + + + **/*Exception.class + + **/Resource.class + + **/dto/*.class + + **/ReservedRuntimeEnvironmentVariables.class + **/RapidErrorType.class + + **/FrameType.class + **/StructuredLogMessage.class + + **/AWSLambda.class + + default-prepare-agent diff --git a/aws-lambda-java-runtime-interface-client/scripts/test-rie.sh b/aws-lambda-java-runtime-interface-client/scripts/test-rie.sh new file mode 100755 index 000000000..b69c967a1 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/scripts/test-rie.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +SERIALIZATION_ROOT="$(dirname "$PROJECT_ROOT")/aws-lambda-java-serialization" + +if ! ls "$PROJECT_ROOT"/target/aws-lambda-java-runtime-interface-client-*.jar >/dev/null 2>&1; then + echo "RIC jar not found. Please build the project first with 'mvn package'." + exit 1 +fi + +IMAGE_TAG="java-ric-rie-test" + +HANDLER="${1:-EchoHandler::handleRequest}" + +echo "Starting RIE test setup for Java..." + +# Build local dependencies if not present +CORE_ROOT="$(dirname "$PROJECT_ROOT")/aws-lambda-java-core" +if ! ls "$PROJECT_ROOT"/target/aws-lambda-java-core-*.jar >/dev/null 2>&1; then + echo "Building local aws-lambda-java-core..." + (cd "$CORE_ROOT" && mvn package -DskipTests) + cp "$CORE_ROOT"/target/aws-lambda-java-core-*.jar "$PROJECT_ROOT/target/" +fi + +if ! ls "$PROJECT_ROOT"/target/aws-lambda-java-serialization-*.jar >/dev/null 2>&1; then + echo "Building local aws-lambda-java-serialization..." + (cd "$SERIALIZATION_ROOT" && mvn package -DskipTests) + cp "$SERIALIZATION_ROOT"/target/aws-lambda-java-serialization-*.jar "$PROJECT_ROOT/target/" +fi + +echo "Compiling EchoHandler..." +javac -source 21 -target 21 -cp "$(ls "$PROJECT_ROOT"/target/aws-lambda-java-runtime-interface-client-*.jar):$(ls "$PROJECT_ROOT"/target/aws-lambda-java-core-*.jar):$(ls "$PROJECT_ROOT"/target/aws-lambda-java-serialization-*.jar)" \ + -d "$PROJECT_ROOT/test-handlers/" "$PROJECT_ROOT/test-handlers/EchoHandler.java" + +echo "Building test Docker image..." +docker build -t "$IMAGE_TAG" -f "$PROJECT_ROOT/Dockerfile.rie" "$PROJECT_ROOT" + +echo "Starting test container on port 9000..." +echo "" +echo "In another terminal, invoke with:" +echo "curl -s -X POST -H 'Content-Type: application/json' \"http://localhost:9000/2015-03-31/functions/function/invocations\" -d '{\"message\":\"test\"}'" +echo "" + +exec docker run -it -p 9000:8080 -e _HANDLER="$HANDLER" "$IMAGE_TAG" \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/test-handlers/EchoHandler.java b/aws-lambda-java-runtime-interface-client/test-handlers/EchoHandler.java new file mode 100644 index 000000000..cb324e7f7 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/test-handlers/EchoHandler.java @@ -0,0 +1,20 @@ +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import java.util.Map; +import java.util.HashMap; + +public class EchoHandler implements RequestHandler, Map> { + + @Override + public Map handleRequest(Map event, Context context) { + context.getLogger().log("Processing event: " + event); + + Map response = new HashMap<>(event); + response.put("timestamp", System.currentTimeMillis()); + response.put("requestId", context.getAwsRequestId()); + response.put("functionName", context.getFunctionName()); + response.put("remainingTimeInMillis", context.getRemainingTimeInMillis()); + + return response; + } +} \ No newline at end of file From 434185b8f7cbd2033dcfebf52d4feb33004597d8 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Fri, 5 Sep 2025 17:55:34 +0100 Subject: [PATCH 19/69] Make Trace ID accessible through Context & aws-lambda-java-core version bump to 1.4 (#563) Co-authored-by: Mohammed Ehab --- .github/workflows/runtime-interface-client_pr.yml | 8 ++++++++ aws-lambda-java-core/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-core/pom.xml | 2 +- .../com/amazonaws/services/lambda/runtime/Context.java | 10 ++++++++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- .../lambda/runtime/api/client/EventHandlerLoader.java | 1 + .../lambda/runtime/api/client/api/LambdaContext.java | 7 +++++++ .../runtime/api/client/api/LambdaContextTest.java | 3 ++- .../api/client/logging/JsonLogFormatterTest.java | 2 ++ .../test/integration/codebuild/buildspec.os.alpine.yml | 1 + .../codebuild/buildspec.os.amazoncorretto.yml | 1 + .../codebuild/buildspec.os.amazonlinux.1.yml | 1 + .../codebuild/buildspec.os.amazonlinux.2.yml | 1 + .../test/integration/codebuild/buildspec.os.centos.yml | 1 + .../test/integration/codebuild/buildspec.os.debian.yml | 1 + .../test/integration/codebuild/buildspec.os.ubuntu.yml | 1 + 16 files changed, 43 insertions(+), 3 deletions(-) diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index c090609c3..35c6ca06b 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -22,6 +22,10 @@ jobs: with: java-version: 8 distribution: corretto + + - name: Build and install core dependency locally + working-directory: ./aws-lambda-java-core + run: mvn clean install - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization @@ -54,6 +58,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + + - name: Build and install core dependency locally + working-directory: ./aws-lambda-java-core + run: mvn clean install - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md index 527e7dd46..aebc8ecd9 100644 --- a/aws-lambda-java-core/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### September 3, 2025 +`1.4.0` +- Getter support for x-ray trace ID through the Context object + ### May 26, 2025 `1.3.0` - Adding support for multi tenancy ([#545](https://github.com/aws/aws-lambda-java-libs/pull/545)) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index 2b3abc0ba..5c1e1d02d 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-core - 1.3.0 + 1.4.0 jar AWS Lambda Java Core Library diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java index 152765df1..ed9311a11 100644 --- a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java @@ -109,4 +109,14 @@ public interface Context { default String getTenantId() { return null; } + + /** + * + * Returns the X-Ray trace ID associated with the request. + * + * @return null by default + */ + default String getXrayTraceId() { + return null; + } } diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 84c6d816c..4c5f73575 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -61,7 +61,7 @@ com.amazonaws aws-lambda-java-core - 1.3.0 + 1.4.0 com.amazonaws diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index db6ceceb2..2876499ec 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -582,6 +582,7 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep LambdaEnvironment.FUNCTION_VERSION, request.getInvokedFunctionArn(), request.getTenantId(), + request.getXrayTraceId(), clientContext ); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java index bd1463db6..20b77262d 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java @@ -23,6 +23,7 @@ public class LambdaContext implements Context { private final CognitoIdentity cognitoIdentity; private final ClientContext clientContext; private final String tenantId; + private final String xrayTraceId; private final LambdaLogger logger; public LambdaContext( @@ -36,6 +37,7 @@ public LambdaContext( String functionVersion, String invokedFunctionArn, String tenantId, + String xrayTraceId, ClientContext clientContext ) { this.memoryLimit = memoryLimit; @@ -49,6 +51,7 @@ public LambdaContext( this.functionVersion = functionVersion; this.invokedFunctionArn = invokedFunctionArn; this.tenantId = tenantId; + this.xrayTraceId = xrayTraceId; this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger(); } @@ -98,6 +101,10 @@ public String getTenantId() { return tenantId; } + public String getXrayTraceId() { + return xrayTraceId; + } + public LambdaLogger getLogger() { return logger; } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java index 58880be43..f7da76198 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java @@ -19,6 +19,7 @@ public class LambdaContextTest { private static final LambdaClientContext CLIENT_CONTEXT = new LambdaClientContext(); public static final int MEMORY_LIMIT = 128; public static final String TENANT_ID = "tenant-id"; + public static final String X_RAY_TRACE_ID = "x-ray-trace-id"; @Test public void getRemainingTimeInMillis() { @@ -55,6 +56,6 @@ public void getRemainingTimeInMillis_Deadline() throws InterruptedException { private LambdaContext createContextWithDeadline(long deadlineTimeInMs) { return new LambdaContext(MEMORY_LIMIT, deadlineTimeInMs, REQUEST_ID, LOG_GROUP_NAME, LOG_STREAM_NAME, - FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, CLIENT_CONTEXT); + FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, X_RAY_TRACE_ID, CLIENT_CONTEXT); } } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java index 531e9ca94..91ce9d2a3 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java @@ -30,6 +30,7 @@ void testFormattingWithLambdaContext() { null, "function-arn", null, + null, null ); assertFormatsString("test log", LogLevel.WARN, context); @@ -48,6 +49,7 @@ void testFormattingWithTenantIdInLambdaContext() { null, "function-arn", "tenant-id", + "xray-trace-id", null ); assertFormatsString("test log", LogLevel.WARN, context); diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml index cdc27a655..2a71cb1b0 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml @@ -43,6 +43,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml index 67dd7617d..db8bf2ba0 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml @@ -42,6 +42,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml index 04c486a88..e3773cf82 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml @@ -37,6 +37,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install -DmultiArch=false -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml index 8222bb41a..a9836fc6f 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml @@ -41,6 +41,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.centos.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.centos.yml index d718c2647..74d12b01d 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.centos.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.centos.yml @@ -41,6 +41,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install) - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml index d2772fbfc..222d14a36 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml @@ -42,6 +42,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install) - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml index 2a90017b3..ce153c547 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml @@ -44,6 +44,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install) - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) From 5c6d3fa10fce436a31889fc9b6d99951746cee16 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Tue, 9 Sep 2025 13:42:49 +0100 Subject: [PATCH 20/69] Migrate to Maven Central Portal from OSSRH for RIC & Core Packages. RIC Version Bump to 2.8.4 (#565) Migrate to Maven Central Portal Publishing instead of OSSRH for aws-lambda-java-core and aws-lambda-java-runtime-interface-client For Other Packages, they we will be following the same approach in aws-lambda-java-core. Only aws-lambda-java-runtime-interface-client is different since, per deployment, it has an uber-jar and platform-specific jars. --------- Co-authored-by: Mohammed Ehab --- aws-lambda-java-core/pom.xml | 17 ++----- .../pom.xml | 50 ++++++++++++++++--- .../test/integration/test-handler/pom.xml | 3 +- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index 5c1e1d02d..cca9d0cdf 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -36,13 +36,6 @@ 1.8 - - - sonatype-nexus-staging - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - dev @@ -115,14 +108,12 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 true - sonatype-nexus-staging - https://aws.oss.sonatype.org/ - false + central diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 4c5f73575..c854fabcd 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.1 + 2.8.4 jar AWS Lambda Java Runtime Interface Client @@ -365,16 +365,52 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 true - sonatype-nexus-staging - https://aws.oss.sonatype.org/ - false + central + + org.codehaus.mojo + build-helper-maven-plugin + 3.4.0 + + + attach-platform-artifacts + package + + attach-artifact + + + + + ${project.build.directory}/${project.build.finalName}-linux-x86_64.jar + jar + linux-x86_64 + + + ${project.build.directory}/${project.build.finalName}-linux-aarch_64.jar + jar + linux-aarch_64 + + + ${project.build.directory}/${project.build.finalName}-linux_musl-x86_64.jar + jar + linux_musl-x86_64 + + + ${project.build.directory}/${project.build.finalName}-linux_musl-aarch_64.jar + jar + linux_musl-aarch_64 + + + + + + diff --git a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml index 2e240fe34..46d191a74 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml +++ b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml @@ -15,7 +15,7 @@ com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.8.4 @@ -50,4 +50,3 @@ - From 199c9621eaa214720f099e0c9cb9b5496d8b813e Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 24 Mar 2025 14:56:05 +0000 Subject: [PATCH 21/69] misc: merge from public (#80) --- .github/workflows/runtime-interface-client_pr.yml | 8 -------- .github/workflows/samples.yml | 2 +- README.md | 4 ++-- aws-lambda-java-events/README.md | 2 +- aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ---- aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-runtime-interface-client/README.md | 4 ++-- .../RELEASE.CHANGELOG.md | 4 ---- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- .../runtime/api/client/PojoSerializerLoaderTest.java | 2 -- .../test/integration/test-handler/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- .../fastJson/HelloWorldFunction/pom.xml | 2 +- .../custom-serialization/gson/HelloWorldFunction/pom.xml | 2 +- .../custom-serialization/moshi/HelloWorldFunction/pom.xml | 2 +- .../request-stream-handler/HelloWorldFunction/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 2 +- 17 files changed, 15 insertions(+), 33 deletions(-) diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 35c6ca06b..cbc5b5ab1 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -58,14 +58,6 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} - - - name: Build and install core dependency locally - working-directory: ./aws-lambda-java-core - run: mvn clean install - - - name: Build and install serialization dependency locally - working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 2b5e7833f..6d63f423e 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -42,7 +42,7 @@ jobs: custom-serialization: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 # Set up both Java 8 and 21 - name: Set up Java 8 and 21 uses: actions/setup-java@v4 diff --git a/README.md b/README.md index b5153a87f..7f4970949 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ public class SqsHandler implements RequestHandler { com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 ``` @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.6.0 ``` diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 43c25d76a..87c61f345 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -74,7 +74,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 ... diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index a4bcd10a0..6c1769751 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,7 +1,3 @@ -### June 17, 2025 -`3.16.0`: -- Add Schema metadata related attributes in KafkaEvent ([#548](https://github.com/aws/aws-lambda-java-libs/pull/548)) - ### January 31, 2025 `3.15.0`: - Fix `CognitoUserPoolPreTokenGenerationEventV2` model ([#519](https://github.com/aws/aws-lambda-java-libs/pull/519)) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 925273e9b..f1364e7ab 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.16.1 + 3.15.0 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 67a5972d6..368ab710a 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.6.0 @@ -203,7 +203,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.6.0 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index ac073ae85..6a781b270 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,7 +1,3 @@ -### May 21, 2025 -`2.7.0` -- Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540)) - ### August 7, 2024 `2.6.0` - Runtime API client improvements: use Lambda-Runtime-Function-Error-Type for reporting errors in format "Runtime." diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index c854fabcd..fde515fda 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.4 + 2.6.0 jar AWS Lambda Java Runtime Interface Client diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java index c2c887973..7c6e9dcb4 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java @@ -7,7 +7,6 @@ import com.amazonaws.services.lambda.runtime.CustomPojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -32,7 +31,6 @@ class PojoSerializerLoaderTest { @Mock private CustomPojoSerializer mockSerializer; - @AfterEach @BeforeEach void setUp() throws Exception { resetStaticFields(); diff --git a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml index 46d191a74..5304d44b9 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml +++ b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml @@ -15,7 +15,7 @@ com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.4 + 2.6.0 diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index da07133c1..9e3419c69 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.16.1 + 3.15.0 org.junit.jupiter diff --git a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml index 7d3c44246..7325c72a0 100644 --- a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 diff --git a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml index fd4271824..dd3b8e9c5 100644 --- a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 com.google.code.gson diff --git a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml index 2773ef1f1..f23214976 100644 --- a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 diff --git a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml index f6ca21bf7..68e7e81e9 100644 --- a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 com.google.code.gson diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 56c959244..3d03205d3 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -46,7 +46,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 From a9c7fd1eea83423ed8d5887d31c588b433146be2 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 7 May 2025 11:00:21 +0100 Subject: [PATCH 22/69] Multi-concurrency mode for the Runtime Interface Client and thread_local C++ Curl Client (#82) Multi Concurrent RIC and Enable Curl Multi Concurrent Requests --- .github/dependabot.yml | 4 + .../lambda/runtime/api/client/AWSLambda.java | 69 +++-- .../api/client/EventHandlerLoader.java | 4 +- .../api/client/PojoSerializerLoader.java | 2 +- .../ReservedRuntimeEnvironmentVariables.java | 13 + .../api/client/util/ConcurrencyConfig.java | 55 ++++ .../include/aws/lambda-runtime/runtime.h | 1 - .../deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 87 ++++--- .../runtime/api/client/AWSLambdaTest.java | 238 ++++++++++++++++++ .../client/util/ConcurrencyConfigTest.java | 94 +++++++ 10 files changed, 502 insertions(+), 65 deletions(-) create mode 100644 aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java create mode 100644 aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java create mode 100644 aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3722537ae..34d287eec 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,9 @@ version: 2 updates: + - package-ecosystem: "maven" + directory: "/aws-lambda-java-runtime-interface" + schedule: + interval: "weekly" - package-ecosystem: "github-actions" directory: "/" diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java index 2eeb14e3d..fdd090077 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java @@ -19,6 +19,7 @@ import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.LambdaErrorConverter; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.XRayErrorCauseConverter; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; +import com.amazonaws.services.lambda.runtime.api.client.util.ConcurrencyConfig; import com.amazonaws.services.lambda.runtime.api.client.util.LambdaOutputStream; import com.amazonaws.services.lambda.runtime.api.client.util.UnsafeUtil; import com.amazonaws.services.lambda.runtime.logging.LogFormat; @@ -35,6 +36,8 @@ import java.net.URLClassLoader; import java.security.Security; import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * The entrypoint of this class is {@link AWSLambda#startRuntime}. It performs two main tasks: @@ -49,8 +52,8 @@ */ public class AWSLambda { - protected static URLClassLoader customerClassLoader; - + private static URLClassLoader customerClassLoader; + private static final String TRUST_STORE_PROPERTY = "javax.net.ssl.trustStore"; private static final String JAVA_SECURITY_PROPERTIES = "java.security.properties"; @@ -68,9 +71,7 @@ public class AWSLambda { private static final String INIT_TYPE_SNAP_START = "snap-start"; private static final String AWS_LAMBDA_INITIALIZATION_TYPE = System.getenv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_INITIALIZATION_TYPE); - - private static LambdaRuntimeApiClient runtimeClient; - + static { // Override the disabledAlgorithms setting to match configuration for openjdk8-u181. // This is to keep DES ciphers around while we deploying security updates. @@ -137,15 +138,13 @@ private static LambdaRequestHandler findRequestHandler(final String handlerStrin return requestHandler; } - private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler, LambdaContextLogger lambdaLogger) throws ClassNotFoundException, IOException { + private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws ClassNotFoundException, IOException { UnsafeUtil.disableIllegalAccessWarning(); System.setOut(new PrintStream(new LambdaOutputStream(System.out), false, "UTF-8")); System.setErr(new PrintStream(new LambdaOutputStream(System.err), false, "UTF-8")); setupRuntimeLogger(lambdaLogger); - runtimeClient = new LambdaRuntimeApiClientImpl(LambdaEnvironment.RUNTIME_API); - String taskRoot = System.getProperty("user.dir"); String libRoot = "/opt/java"; // Make system classloader the customer classloader's parent to ensure any aws-lambda-java-core classes @@ -167,13 +166,13 @@ private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler } if (INIT_TYPE_SNAP_START.equals(AWS_LAMBDA_INITIALIZATION_TYPE)) { - onInitComplete(lambdaLogger); + onInitComplete(lambdaLogger, runtimeClient); } return requestHandler; } - public static void setupRuntimeLogger(LambdaLogger lambdaLogger) + private static void setupRuntimeLogger(LambdaLogger lambdaLogger) throws ClassNotFoundException { ReflectUtil.setStaticField( Class.forName("com.amazonaws.services.lambda.runtime.LambdaRuntime"), @@ -213,10 +212,11 @@ private static LogSink createLogSink() { } public static void main(String[] args) throws Throwable { - try (LambdaContextLogger logger = initLogger()) { - LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandlerObject(args[0], logger); - startRuntimeLoop(lambdaRequestHandler, logger); - + try (LambdaContextLogger lambdaLogger = initLogger()) { + LambdaRuntimeApiClient runtimeClient = new LambdaRuntimeApiClientImpl(LambdaEnvironment.RUNTIME_API); + LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandlerObject(args[0], lambdaLogger, runtimeClient); + ConcurrencyConfig concurrencyConfig = new ConcurrencyConfig(lambdaLogger); + startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); } catch (IOException | ClassNotFoundException t) { throw new Error(t); } @@ -232,7 +232,38 @@ private static LambdaContextLogger initLogger() { return logger; } - private static void startRuntimeLoop(LambdaRequestHandler requestHandler, LambdaContextLogger lambdaLogger) throws Throwable { + private static void startRuntimeLoopWithExecutor(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, ExecutorService executorService, LambdaRuntimeApiClient runtimeClient) { + executorService.submit(() -> { + try { + startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient); + } catch (Exception e) { + lambdaLogger.log(String.format("Runtime Loop on Thread ID: %s Failed.\n%s", Thread.currentThread().getName(), UserFault.trace(e)), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + } + }); + } + + protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, ConcurrencyConfig concurrencyConfig, LambdaRuntimeApiClient runtimeClient) throws Exception { + if (concurrencyConfig.isMultiConcurrent()) { + lambdaLogger.log(concurrencyConfig.getConcurrencyConfigMessage(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.INFO : LogLevel.UNDEFINED); + ExecutorService platformThreadExecutor = Executors.newFixedThreadPool(concurrencyConfig.getNumberOfPlatformThreads()); + try { + for (int i = 0; i < concurrencyConfig.getNumberOfPlatformThreads(); i++) { + startRuntimeLoopWithExecutor(lambdaRequestHandler, lambdaLogger, platformThreadExecutor, runtimeClient); + } + } finally { + platformThreadExecutor.shutdown(); + while (true) { + if (platformThreadExecutor.isTerminated()) { + break; + } + } + } + } else { + startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient); + } + } + + private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws Exception { boolean shouldExit = false; while (!shouldExit) { UserFault userFault = null; @@ -245,7 +276,7 @@ private static void startRuntimeLoop(LambdaRequestHandler requestHandler, Lambda ByteArrayOutputStream payload; try { - payload = requestHandler.call(request); + payload = lambdaRequestHandler.call(request); runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray()); // clear interrupted flag in case if it was set by user's code Thread.interrupted(); @@ -275,7 +306,7 @@ private static void startRuntimeLoop(LambdaRequestHandler requestHandler, Lambda } } - static void onInitComplete(final LambdaContextLogger lambdaLogger) throws IOException { + private static void onInitComplete(final LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws IOException { try { Core.getGlobalContext().beforeCheckpoint(null); runtimeClient.restoreNext(); @@ -303,4 +334,8 @@ private static void logExceptionCloudWatch(LambdaContextLogger lambdaLogger, Exc UserFault userFault = UserFault.makeUserFault(exc, true); lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); } + + protected static URLClassLoader getCustomerClassLoader() { + return customerClassLoader; + } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index 2876499ec..8fab27fed 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -116,7 +116,7 @@ private static PojoSerializer getSerializer(Platform platform, Type type if (type instanceof Class) { Class clazz = ((Class) type); if (LambdaEventSerializers.isLambdaSupportedEvent(clazz.getName())) { - return LambdaEventSerializers.serializerFor(clazz, AWSLambda.customerClassLoader); + return LambdaEventSerializers.serializerFor(clazz, AWSLambda.getCustomerClassLoader()); } } // else platform dependent (Android uses GSON but all other platforms use Jackson) @@ -533,7 +533,7 @@ private static LambdaRequestHandler wrapRequestStreamHandler(final RequestStream private void safeAddRequestIdToLog4j(String log4jContextClassName, InvocationRequest request, Class contextMapValueClass) { try { - Class log4jContextClass = ReflectUtil.loadClass(AWSLambda.customerClassLoader, log4jContextClassName); + Class log4jContextClass = ReflectUtil.loadClass(AWSLambda.getCustomerClassLoader(), log4jContextClassName); log4jContextPutMethod = ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass); log4jContextPutMethod.call("AWSRequestId", request.getId()); } catch (Exception e) { diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java index daea5911f..da37f7ca7 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java @@ -28,7 +28,7 @@ private static CustomPojoSerializer loadSerializer() return customPojoSerializer; } - ServiceLoader loader = ServiceLoader.load(CustomPojoSerializer.class, AWSLambda.customerClassLoader); + ServiceLoader loader = ServiceLoader.load(CustomPojoSerializer.class, AWSLambda.getCustomerClassLoader()); Iterator serializers = loader.iterator(); if (!serializers.hasNext()) { diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java index 7500a4943..2d32d4048 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java @@ -106,4 +106,17 @@ public interface ReservedRuntimeEnvironmentVariables { * The environment's time zone (UTC). The execution environment uses NTP to synchronize the system clock. */ String TZ = "TZ"; + + /** + * Boolean determining whether the RIC should run multi-concurrent runtime loops. Default value is "false". + * In case it is set to "true", AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY can be used to set the required number of concurrent runtime loops. + */ + String AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC = "AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC"; + + /* + * Used to set the required number of concurrent runtime loops, + * Only used if AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC is set to "true", + * If AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores. + */ + String AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY = "AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY"; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java new file mode 100644 index 000000000..db1f6c218 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java @@ -0,0 +1,55 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.api.client.util; + +import com.amazonaws.services.lambda.runtime.api.client.ReservedRuntimeEnvironmentVariables; +import com.amazonaws.services.lambda.runtime.api.client.UserFault; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +public class ConcurrencyConfig { + private static final int AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT = 1000; + private final int numberOfPlatformThreads; + private final boolean isConcurrencyEnabled; + + public ConcurrencyConfig(LambdaContextLogger logger) { + this(logger, new EnvReader()); + } + + public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) { + this.isConcurrencyEnabled = Boolean.parseBoolean(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)); + int platformThreads = this.isConcurrencyEnabled ? Runtime.getRuntime().availableProcessors() : 0; + + if (this.isConcurrencyEnabled) { + try { + int readNumOfPlatformThreads = Integer.parseUnsignedInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)); + if (readNumOfPlatformThreads > 0 && readNumOfPlatformThreads <= AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT) { + platformThreads = readNumOfPlatformThreads; + } else { + throw new IllegalArgumentException(); + } + } catch (Exception e) { + String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to number of cores as number of platform threads.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY, AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT, UserFault.trace(e)); + logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); + } + } + + this.numberOfPlatformThreads = platformThreads; + } + + public String getConcurrencyConfigMessage() { + return String.format("Starting %d concurrent function handler threads.", this.numberOfPlatformThreads); + } + + public boolean isMultiConcurrent() { + return this.isConcurrencyEnabled && this.numberOfPlatformThreads >= 2; + } + + public int getNumberOfPlatformThreads() { + return numberOfPlatformThreads; + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h index d7db5f183..c4868c1ba 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h @@ -172,7 +172,6 @@ class runtime { private: std::string const m_user_agent_header; std::array const m_endpoints; - CURL* const m_curl_handle; }; inline std::chrono::milliseconds invocation_request::get_time_remaining() const diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp index eeaf0e7b9..e713da985 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp @@ -40,7 +40,7 @@ static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context"; static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity"; static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms"; static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn"; -static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id"; +thread_local static CURL* m_curl_handle = curl_easy_init(); enum Endpoints { INIT, @@ -163,63 +163,62 @@ runtime::runtime(std::string const& endpoint) : runtime(endpoint, "AWS_Lambda_Cp runtime::runtime(std::string const& endpoint, std::string const& user_agent) : m_user_agent_header("User-Agent: " + user_agent), m_endpoints{{endpoint + "/2018-06-01/runtime/init/error", endpoint + "/2018-06-01/runtime/invocation/next", - endpoint + "/2018-06-01/runtime/invocation/"}}, - m_curl_handle(curl_easy_init()) + endpoint + "/2018-06-01/runtime/invocation/"}} { - if (!m_curl_handle) { + if (!lambda_runtime::m_curl_handle) { logging::log_error(LOG_TAG, "Failed to acquire curl easy handle for next."); } } runtime::~runtime() { - curl_easy_cleanup(m_curl_handle); + curl_easy_cleanup(lambda_runtime::m_curl_handle); } void runtime::set_curl_next_options() { // lambda freezes the container when no further tasks are available. The freezing period could be longer than the // request timeout, which causes the following get_next request to fail with a timeout error. - curl_easy_reset(m_curl_handle); - curl_easy_setopt(m_curl_handle, CURLOPT_TIMEOUT, 0L); - curl_easy_setopt(m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_TCP_NODELAY, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_reset(lambda_runtime::m_curl_handle); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TIMEOUT, 0L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TCP_NODELAY, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTPGET, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_URL, m_endpoints[Endpoints::NEXT].c_str()); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, m_endpoints[Endpoints::NEXT].c_str()); - curl_easy_setopt(m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); - curl_easy_setopt(m_curl_handle, CURLOPT_PROXY, ""); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_PROXY, ""); #ifndef NDEBUG - curl_easy_setopt(m_curl_handle, CURLOPT_VERBOSE, 1); - curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_VERBOSE, 1); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); #endif } void runtime::set_curl_post_result_options() { - curl_easy_reset(m_curl_handle); - curl_easy_setopt(m_curl_handle, CURLOPT_TIMEOUT, 0L); - curl_easy_setopt(m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_TCP_NODELAY, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_reset(lambda_runtime::m_curl_handle); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TIMEOUT, 0L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TCP_NODELAY, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - curl_easy_setopt(m_curl_handle, CURLOPT_POST, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_READFUNCTION, read_data); - curl_easy_setopt(m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_POST, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_READFUNCTION, read_data); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); - curl_easy_setopt(m_curl_handle, CURLOPT_PROXY, ""); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_PROXY, ""); #ifndef NDEBUG - curl_easy_setopt(m_curl_handle, CURLOPT_VERBOSE, 1); - curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_VERBOSE, 1); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); #endif } @@ -227,15 +226,15 @@ runtime::next_outcome runtime::get_next() { http::response resp; set_curl_next_options(); - curl_easy_setopt(m_curl_handle, CURLOPT_WRITEDATA, &resp); - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, &resp); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEDATA, &resp); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERDATA, &resp); curl_slist* headers = nullptr; headers = curl_slist_append(headers, m_user_agent_header.c_str()); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPHEADER, headers); logging::log_debug(LOG_TAG, "Making request to %s", m_endpoints[Endpoints::NEXT].c_str()); - CURLcode curl_code = curl_easy_perform(m_curl_handle); + CURLcode curl_code = curl_easy_perform(lambda_runtime::m_curl_handle); logging::log_debug(LOG_TAG, "Completed request to %s", m_endpoints[Endpoints::NEXT].c_str()); curl_slist_free_all(headers); @@ -247,13 +246,13 @@ runtime::next_outcome runtime::get_next() { long resp_code; - curl_easy_getinfo(m_curl_handle, CURLINFO_RESPONSE_CODE, &resp_code); + curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_RESPONSE_CODE, &resp_code); resp.set_response_code(static_cast(resp_code)); } { char* content_type = nullptr; - curl_easy_getinfo(m_curl_handle, CURLINFO_CONTENT_TYPE, &content_type); + curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_CONTENT_TYPE, &content_type); resp.set_content_type(content_type); } @@ -327,7 +326,7 @@ runtime::post_outcome runtime::do_post( invocation_response const& handler_response) { set_curl_post_result_options(); - curl_easy_setopt(m_curl_handle, CURLOPT_URL, url.c_str()); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, url.c_str()); logging::log_info(LOG_TAG, "Making request to %s", url.c_str()); curl_slist* headers = nullptr; @@ -348,11 +347,11 @@ runtime::post_outcome runtime::do_post( std::pair ctx{payload, 0}; aws::http::response resp; - curl_easy_setopt(m_curl_handle, CURLOPT_WRITEDATA, &resp); - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, &resp); - curl_easy_setopt(m_curl_handle, CURLOPT_READDATA, &ctx); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, headers); - CURLcode curl_code = curl_easy_perform(m_curl_handle); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEDATA, &resp); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERDATA, &resp); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_READDATA, &ctx); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPHEADER, headers); + CURLcode curl_code = curl_easy_perform(lambda_runtime::m_curl_handle); curl_slist_free_all(headers); if (curl_code != CURLE_OK) { @@ -366,7 +365,7 @@ runtime::post_outcome runtime::do_post( } long http_response_code; - curl_easy_getinfo(m_curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code); + curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code); if (!is_success(aws::http::response_code(http_response_code))) { logging::log_error( diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java new file mode 100644 index 000000000..4412214e9 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java @@ -0,0 +1,238 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.api.client; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; +import com.amazonaws.services.lambda.runtime.api.client.util.ConcurrencyConfig; +import com.amazonaws.services.lambda.runtime.api.client.util.EnvReader; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class AWSLambdaTest { + + private static class SampleHandler implements RequestHandler, String> { + public static final String ADD_ENTRY_TO_MAP_ID_OP_MODE = "ADD_ENTRY_TO_MAP_ID"; + public static final String FAIL_IMMEDIATELY_OP_MODE = "FAIL_IMMEDIATELY"; + + public static final int nOfIterations = 10; + public static final int perIterationDelayMS = 10; + public static Map hashMap = new ConcurrentHashMap(); + public static AtomicInteger globalCounter = new AtomicInteger(); + + public static void resetStaticFields() { + hashMap.clear(); + globalCounter = new AtomicInteger(); + } + + private static void addEntryToMapImplementation(String name) { + int i = 0; + while (i++ < nOfIterations) { + hashMap.put(name, hashMap.getOrDefault(name, 0) + 1); + globalCounter.incrementAndGet(); + try { + Thread.sleep(perIterationDelayMS); + } catch (InterruptedException e) { + } + } + } + + @Override + public String handleRequest(Map event, Context context) { + // Thread.currentThread().getId() instead of Thread.currentThread().getName() when upgrading JAVA + String name = "Thread " + Thread.currentThread().getName(); + String opMode = event.get("id"); + + switch (opMode) { + case ADD_ENTRY_TO_MAP_ID_OP_MODE: + addEntryToMapImplementation(name); + break; + case FAIL_IMMEDIATELY_OP_MODE: + String[] sArr = {}; + return sArr[1]; + default: + break; + } + + return name; + } + } + + @Mock + private LambdaRuntimeApiClientImpl runtimeClient; + + @Mock + private LambdaContextLogger lambdaLogger; + + @Mock + private EnvReader envReader; + + @Mock + private ConcurrencyConfig concurrencyConfig; + + private LambdaRequestHandler lambdaRequestHandler = new LambdaRequestHandler() { + private SampleHandler sHandler = new SampleHandler(); + + @Override + public ByteArrayOutputStream call(InvocationRequest request) throws Error, Exception { + HashMap eventMap = new HashMap(); + eventMap.put("id", request.getId()); + sHandler.handleRequest(eventMap, null); + return new ByteArrayOutputStream(); + } + }; + + private static InvocationRequest getFakeInvocationRequest(String id) { + InvocationRequest request = new InvocationRequest(); + request.setId(id); + request.setDeadlineTimeInMs(Long.MAX_VALUE); + request.setContent("".getBytes()); + return request; + } + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + SampleHandler.resetStaticFields(); + } + + /* + * com.amazonaws.services.lambda.runtime.api.client.util.SampleHandler contains static fields. These fields are expected to be shared if initialization is behaving as expected. + * After execution of the Runtime loops, we should see that the SampleHandler.globalCounter has been acted on by all the threads. + * The concurrent hashmap in SampleHandler.hashMap should also have all the correct count of Threads that ran. + * IMPORTANT: This test fails through only timeout. + */ + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testConcurrentRunWithPlatformThreads() throws Throwable { + when(concurrencyConfig.isMultiConcurrent()).thenReturn(true); + when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(4); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + + when(runtimeClient.nextInvocation()) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(null) + .thenReturn(null) + .thenReturn(null) + .thenReturn(null); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // Success Reports Must Equal number of tasks that ran successfully. + verify(runtimeClient, times(7)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + // Hashmap keys should equal the number of threads (runtime loops). + assertEquals(4, SampleHandler.hashMap.size()); + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(7 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(true); + when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(4); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); + InvocationRequest userFaultRequest = mock(InvocationRequest.class); + final String UserFaultID = "Injected Fault Request ID"; + when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + when(runtimeClient.nextInvocation()) + .thenReturn(failImmediatelyRequest) + .thenReturn(userFaultRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(null) + .thenReturn(null) + .thenReturn(null); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // One for each of failImmediatelyRequest and userFaultRequest in finally block + three from the uncaught null expections. + verify(lambdaLogger, times(5)).log(anyString(), eq(LogLevel.ERROR)); + + // Failed invokes should be reported. + verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); + verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); + + // Success Reports Must Equal number of tasks that ran successfully. + verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + + // Hashmap keys should equal the minumum between(number of threads (runtime loops) AND number of tasks that ran successfully). + assertEquals(2, SampleHandler.hashMap.size()); + + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testSequentialWithFailures() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(false); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); + InvocationRequest userFaultRequest = mock(InvocationRequest.class); + final String UserFaultID = "Injected Fault Request ID"; + when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + when(runtimeClient.nextInvocation()) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(failImmediatelyRequest) + .thenReturn(userFaultRequest); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // One for failImmediatelyRequest and userFaultRequest in finally block. + verify(lambdaLogger, times(2)).log(anyString(), eq(LogLevel.ERROR)); + + // Failed invokes should be reported. + verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); + verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); + + // Success Reports Must Equal number of tasks that ran successfully. + verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + + // Hashmap keys should equal one as it is not multithreaded. + assertEquals(1, SampleHandler.hashMap.size()); + + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } +} \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java new file mode 100644 index 000000000..bbb43cd14 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java @@ -0,0 +1,94 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.api.client.util; + +import com.amazonaws.services.lambda.runtime.api.client.ReservedRuntimeEnvironmentVariables; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ConcurrencyConfigTest { + @Mock + private LambdaContextLogger lambdaLogger; + + @Mock + private EnvReader envReader; + + @Test + void testDefaultConfiguration() { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn(null); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + } + + @Test + void testValidPlatformThreadsConfig() { + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(4, config.getNumberOfPlatformThreads()); + assertEquals(true, config.isMultiConcurrent()); + } + + @Test + void testInvalidPlatformThreadsConfig() { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("invalid"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); + assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + } + + @Test + void testExceedingPlatformThreadsLimit() { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("1001"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); + assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + } + + @Test + void testGetConcurrencyConfigMessage() { + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + String expectedMessage = "Starting 4 concurrent function handler threads."; + assertEquals(expectedMessage, config.getConcurrencyConfigMessage()); + assertEquals(true, config.isMultiConcurrent()); + } + + @Test + void testGetConcurrencyConfigWithNoConcurrency() { + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("false"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(0, config.getNumberOfPlatformThreads()); + assertEquals(false, config.isMultiConcurrent()); + } +} From 2769f83b79ce0a83a388468c831c77bd79863b18 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 8 May 2025 13:29:35 +0100 Subject: [PATCH 23/69] Merge from Public Repo (#83) * Merge from Public Repo * Fix Failing Tests --------- Co-authored-by: Mohammed Ehab --- .../workflows/runtime-interface-client_merge_to_main.yml | 6 +----- .../lambda/runtime/api/client/EventHandlerLoader.java | 1 - .../lambda/runtime/api/client/api/LambdaContext.java | 7 ------- .../src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 1 + .../runtime/api/client/PojoSerializerLoaderTest.java | 2 ++ .../lambda/runtime/api/client/api/LambdaContextTest.java | 3 +-- .../runtime/api/client/logging/JsonLogFormatterTest.java | 2 -- 7 files changed, 5 insertions(+), 17 deletions(-) diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 8909d56bf..e07b191e1 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -28,7 +28,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 @@ -47,10 +47,6 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} - - name: Build and install serialization dependency locally - working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests - - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index 8fab27fed..af146fd93 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -582,7 +582,6 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep LambdaEnvironment.FUNCTION_VERSION, request.getInvokedFunctionArn(), request.getTenantId(), - request.getXrayTraceId(), clientContext ); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java index 20b77262d..bd1463db6 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java @@ -23,7 +23,6 @@ public class LambdaContext implements Context { private final CognitoIdentity cognitoIdentity; private final ClientContext clientContext; private final String tenantId; - private final String xrayTraceId; private final LambdaLogger logger; public LambdaContext( @@ -37,7 +36,6 @@ public LambdaContext( String functionVersion, String invokedFunctionArn, String tenantId, - String xrayTraceId, ClientContext clientContext ) { this.memoryLimit = memoryLimit; @@ -51,7 +49,6 @@ public LambdaContext( this.functionVersion = functionVersion; this.invokedFunctionArn = invokedFunctionArn; this.tenantId = tenantId; - this.xrayTraceId = xrayTraceId; this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger(); } @@ -101,10 +98,6 @@ public String getTenantId() { return tenantId; } - public String getXrayTraceId() { - return xrayTraceId; - } - public LambdaLogger getLogger() { return logger; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp index e713da985..3897d37db 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp @@ -40,6 +40,7 @@ static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context"; static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity"; static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms"; static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn"; +static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id"; thread_local static CURL* m_curl_handle = curl_easy_init(); enum Endpoints { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java index 7c6e9dcb4..4ebcf5d7e 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java @@ -8,6 +8,7 @@ import com.amazonaws.services.lambda.runtime.CustomPojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -31,6 +32,7 @@ class PojoSerializerLoaderTest { @Mock private CustomPojoSerializer mockSerializer; + @AfterEach @BeforeEach void setUp() throws Exception { resetStaticFields(); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java index f7da76198..58880be43 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java @@ -19,7 +19,6 @@ public class LambdaContextTest { private static final LambdaClientContext CLIENT_CONTEXT = new LambdaClientContext(); public static final int MEMORY_LIMIT = 128; public static final String TENANT_ID = "tenant-id"; - public static final String X_RAY_TRACE_ID = "x-ray-trace-id"; @Test public void getRemainingTimeInMillis() { @@ -56,6 +55,6 @@ public void getRemainingTimeInMillis_Deadline() throws InterruptedException { private LambdaContext createContextWithDeadline(long deadlineTimeInMs) { return new LambdaContext(MEMORY_LIMIT, deadlineTimeInMs, REQUEST_ID, LOG_GROUP_NAME, LOG_STREAM_NAME, - FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, X_RAY_TRACE_ID, CLIENT_CONTEXT); + FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, CLIENT_CONTEXT); } } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java index 91ce9d2a3..531e9ca94 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java @@ -30,7 +30,6 @@ void testFormattingWithLambdaContext() { null, "function-arn", null, - null, null ); assertFormatsString("test log", LogLevel.WARN, context); @@ -49,7 +48,6 @@ void testFormattingWithTenantIdInLambdaContext() { null, "function-arn", "tenant-id", - "xray-trace-id", null ); assertFormatsString("test log", LogLevel.WARN, context); From a66ed4bdd0a461f486253c1f9adb9f20f5783f86 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:17:36 +0100 Subject: [PATCH 24/69] Support Multiconcurrent Logging (#86) * Support Multiconcurrent Logging --------- Co-authored-by: Mohammed Ehab --- .../api/client/logging/JsonLogFormatter.java | 16 ++++--- .../api/client/logging/StdOutLogSink.java | 2 +- .../logging/AbstractLambdaLoggerTest.java | 43 ++++++++++++++++++- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java index f463e7ee5..f1051a216 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java @@ -22,7 +22,7 @@ public class JsonLogFormatter implements LogFormatter { withZone(ZoneId.of("UTC")); private final PojoSerializer serializer = GsonFactory.getInstance().getSerializer(StructuredLogMessage.class); - private LambdaContext lambdaContext; + private ThreadLocal lambdaContext = new ThreadLocal<>(); @Override public String format(String message, LogLevel logLevel) { @@ -39,10 +39,12 @@ private StructuredLogMessage createLogMessage(String message, LogLevel logLevel) msg.message = message; msg.level = logLevel; - if (lambdaContext != null) { - msg.AWSRequestId = lambdaContext.getAwsRequestId(); - msg.tenantId = lambdaContext.getTenantId(); + LambdaContext lambdaContextForCurrentThread = lambdaContext.get(); + if (lambdaContextForCurrentThread != null) { + msg.AWSRequestId = lambdaContextForCurrentThread.getAwsRequestId(); + msg.tenantId = lambdaContextForCurrentThread.getTenantId(); } + return msg; } @@ -53,6 +55,10 @@ private StructuredLogMessage createLogMessage(String message, LogLevel logLevel) */ @Override public void setLambdaContext(LambdaContext context) { - this.lambdaContext = context; + if (context == null) { + lambdaContext.remove(); + } else { + lambdaContext.set(context); + } } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java index 873e6fde5..90e7d39c2 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java @@ -15,7 +15,7 @@ public void log(byte[] message) { log(LogLevel.UNDEFINED, LogFormat.TEXT, message); } - public void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { + public synchronized void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { try { System.out.write(message); } catch (IOException e) { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java index baeb4c242..151e702af 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java @@ -5,10 +5,15 @@ import com.amazonaws.services.lambda.runtime.logging.LogFormat; import org.junit.jupiter.api.Test; +import java.nio.charset.StandardCharsets; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import com.amazonaws.lambda.thirdparty.org.json.JSONObject; import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.api.client.api.LambdaContext; import com.amazonaws.services.lambda.runtime.logging.LogLevel; @@ -20,12 +25,12 @@ public TestSink() { } @Override - public void log(byte[] message) { + public synchronized void log(byte[] message) { messages.add(message); } @Override - public void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { + public synchronized void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { messages.add(message); } @@ -62,6 +67,40 @@ public void testLoggingNullValuesWithoutLogLevelInText() { assertEquals("null", new String(sink.getMessages().get(1))); } + // Makes Sure Logging Contexts are thread local. + @Test + public void testMultiConcurrentLoggingWithoutLogLevelInJSON() { + TestSink sink = new TestSink(); + LambdaContextLogger logger = new LambdaContextLogger(sink, LogLevel.INFO, LogFormat.JSON); + + String someMessagePrefix = "Some Message from "; + String reqIDPrefix = "Thread ID as request# "; + + final int nThreads = 5; + ExecutorService es = Executors.newFixedThreadPool(nThreads); + for (int i = 0; i < nThreads; i++) { + es.submit(() -> logger.setLambdaContext(new LambdaContext(Integer.MAX_VALUE, Long.MAX_VALUE, reqIDPrefix + Thread.currentThread().getName(), "", "", "", null, "", "", "", null))); + } + + final int nMessages = 100_000; + for (int i = 0; i < nMessages; i++) { + es.submit(() -> logger.log(someMessagePrefix + Thread.currentThread().getName())); + } + + es.shutdown(); + while (!es.isTerminated()) { + ; + } + + assertEquals(nMessages, sink.getMessages().size()); + for (byte[] message : sink.getMessages()) { + JSONObject parsedLog = new JSONObject(new String(message, StandardCharsets.UTF_8)); + String parsedMessage = parsedLog.getString("message"); + String parsedReqID = parsedLog.getString("AWSRequestId"); + assertEquals(parsedMessage.substring(someMessagePrefix.length()), parsedReqID.substring(reqIDPrefix.length())); + } + } + @Test public void testLoggingNullValuesWithoutLogLevelInJSON() { TestSink sink = new TestSink(); From 1fde59f45783a8bb150b4bd10fe3a4cccb563675 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:24:33 +0100 Subject: [PATCH 25/69] Modified RIC Behavior for Retries and Failure Handling (#84) * Modified RIC Behavior for Retries and Failure Handling + Refactoring --- .../lambda/runtime/api/client/AWSLambda.java | 97 ++++++----- .../ReservedRuntimeEnvironmentVariables.java | 11 +- .../lambda/runtime/api/client/UserFault.java | 2 +- .../runtimeapi/LambdaRuntimeApiClient.java | 6 + .../LambdaRuntimeApiClientImpl.java | 70 ++++++++ ...timeClientMaxRetriesExceededException.java | 15 ++ .../api/client/util/ConcurrencyConfig.java | 30 ++-- .../runtime/api/client/AWSLambdaTest.java | 150 ++++++++++++++++-- .../logging/AbstractLambdaLoggerTest.java | 7 +- .../LambdaRuntimeApiClientImplTest.java | 83 +++++++++- .../client/util/ConcurrencyConfigTest.java | 29 ++-- 11 files changed, 399 insertions(+), 101 deletions(-) create mode 100644 aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java index fdd090077..9c6dd78b1 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java @@ -15,6 +15,7 @@ import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaError; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClient; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientMaxRetriesExceededException; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.RapidErrorType; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.LambdaErrorConverter; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.XRayErrorCauseConverter; @@ -235,7 +236,7 @@ private static LambdaContextLogger initLogger() { private static void startRuntimeLoopWithExecutor(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, ExecutorService executorService, LambdaRuntimeApiClient runtimeClient) { executorService.submit(() -> { try { - startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient); + startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient, false); } catch (Exception e) { lambdaLogger.log(String.format("Runtime Loop on Thread ID: %s Failed.\n%s", Thread.currentThread().getName(), UserFault.trace(e)), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); } @@ -259,49 +260,73 @@ protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandle } } } else { - startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient); + startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient, true); } } - private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws Exception { + private static LambdaError createLambdaErrorFromThrowableOrUserFault(Throwable t) { + if (t instanceof UserFault) { + return new LambdaError( + LambdaErrorConverter.fromUserFault((UserFault) t), + RapidErrorType.BadFunctionCode); + } else { + return new LambdaError( + LambdaErrorConverter.fromThrowable(t), + XRayErrorCauseConverter.fromThrowable(t), + RapidErrorType.UserException); + } + } + + private static void setEnvVarForXrayTraceId(InvocationRequest request) { + if (request.getXrayTraceId() != null) { + System.setProperty(LAMBDA_TRACE_HEADER_PROP, request.getXrayTraceId()); + } else { + System.clearProperty(LAMBDA_TRACE_HEADER_PROP); + } + } + + private static void reportNonLoopTerminatingException(LambdaContextLogger lambdaLogger, Throwable t) { + lambdaLogger.log( + String.format( + "Runtime Loop on Thread ID: %s Faced and Exception. This exception will not stop the runtime loop.\nException:\n%s", + Thread.currentThread().getName(), UserFault.trace(t)), + lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + } + + /* + * In multiconcurrent mode (exitLoopOnErrors = false), The Runtime Loop will not exit unless LambdaRuntimeClientMaxRetriesExceededException is thrown when calling nextInvocationWithExponentialBackoff. + * In normal/sequential mode (exitLoopOnErrors = true), The Runtime Loop will exit if nextInvocation call fails, when UserFault is fatal, or an Error of type VirtualMachineError or IOError is thrown. + */ + private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient, boolean exitLoopOnErrors) throws Exception { boolean shouldExit = false; while (!shouldExit) { - UserFault userFault = null; - InvocationRequest request = runtimeClient.nextInvocation(); - if (request.getXrayTraceId() != null) { - System.setProperty(LAMBDA_TRACE_HEADER_PROP, request.getXrayTraceId()); - } else { - System.clearProperty(LAMBDA_TRACE_HEADER_PROP); - } - - ByteArrayOutputStream payload; try { - payload = lambdaRequestHandler.call(request); - runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray()); - // clear interrupted flag in case if it was set by user's code - Thread.interrupted(); - } catch (UserFault f) { - shouldExit = f.fatal; - userFault = f; - UserFault.filterStackTrace(f); - LambdaError error = new LambdaError( - LambdaErrorConverter.fromUserFault(f), - RapidErrorType.BadFunctionCode); - runtimeClient.reportInvocationError(request.getId(), error); + UserFault userFault = null; + InvocationRequest request = exitLoopOnErrors ? runtimeClient.nextInvocation() : runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger); + setEnvVarForXrayTraceId(request); + + try { + ByteArrayOutputStream payload = lambdaRequestHandler.call(request); + runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray()); + // clear interrupted flag in case if it was set by user's code + Thread.interrupted(); + } catch (Throwable t) { + UserFault.filterStackTrace(t); + userFault = UserFault.makeUserFault(t); + shouldExit = exitLoopOnErrors && (t instanceof VirtualMachineError || t instanceof IOError || userFault.fatal); + LambdaError error = createLambdaErrorFromThrowableOrUserFault(t); + runtimeClient.reportInvocationError(request.getId(), error); + } finally { + if (userFault != null) { + lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + } + } } catch (Throwable t) { - shouldExit = t instanceof VirtualMachineError || t instanceof IOError; - UserFault.filterStackTrace(t); - userFault = UserFault.makeUserFault(t); - - LambdaError error = new LambdaError( - LambdaErrorConverter.fromThrowable(t), - XRayErrorCauseConverter.fromThrowable(t), - RapidErrorType.UserException); - runtimeClient.reportInvocationError(request.getId(), error); - } finally { - if (userFault != null) { - lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + if (exitLoopOnErrors || t instanceof LambdaRuntimeClientMaxRetriesExceededException) { + throw t; } + + reportNonLoopTerminatingException(lambdaLogger, t); } } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java index 2d32d4048..1de3dca99 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java @@ -107,16 +107,9 @@ public interface ReservedRuntimeEnvironmentVariables { */ String TZ = "TZ"; - /** - * Boolean determining whether the RIC should run multi-concurrent runtime loops. Default value is "false". - * In case it is set to "true", AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY can be used to set the required number of concurrent runtime loops. - */ - String AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC = "AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC"; - /* * Used to set the required number of concurrent runtime loops, - * Only used if AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC is set to "true", - * If AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores. + * If AWS_LAMBDA_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores. */ - String AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY = "AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY"; + String AWS_LAMBDA_MAX_CONCURRENCY = "AWS_LAMBDA_MAX_CONCURRENCY"; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java index c7c5c9ddf..7d8a50347 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java @@ -38,7 +38,7 @@ public UserFault(String msg, String exception, String trace, Boolean fatal) { * No more user code should run after a fault. */ public static UserFault makeUserFault(Throwable t) { - return makeUserFault(t, false); + return t instanceof UserFault ? (UserFault) t : makeUserFault(t, false); } public static UserFault makeUserFault(Throwable t, boolean fatal) { diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java index e2ae0969a..a62aeb9b8 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java @@ -4,6 +4,7 @@ */ package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; import java.io.IOException; @@ -24,6 +25,11 @@ public interface LambdaRuntimeApiClient { */ InvocationRequest nextInvocation() throws IOException; + /** + * Get next invocation with exponential backoff + */ + InvocationRequest nextInvocationWithExponentialBackoff(LambdaContextLogger lambdaLogger) throws Exception; + /** * Report invocation success * @param requestId request id diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java index 65024b98e..caca69aa7 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java @@ -4,7 +4,11 @@ */ package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; +import com.amazonaws.services.lambda.runtime.api.client.UserFault; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -14,6 +18,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; import static java.net.HttpURLConnection.HTTP_ACCEPTED; import static java.net.HttpURLConnection.HTTP_OK; import static java.nio.charset.StandardCharsets.UTF_8; @@ -30,6 +36,11 @@ public class LambdaRuntimeApiClientImpl implements LambdaRuntimeApiClient { private static final String ERROR_TYPE_HEADER = "Lambda-Runtime-Function-Error-Type"; // 1MiB private static final int XRAY_ERROR_CAUSE_MAX_HEADER_SIZE = 1024 * 1024; + + // ~32 Seconds Max Backoff. + private static final long MAX_BACKOFF_PERIOD_MS = 1024 * 32; + private static final long INITIAL_BACKOFF_PERIOD_MS = 100; + private static final int MAX_NUMBER_OF_RETRIALS = 5; private final String baseUrl; private final String invocationEndpoint; @@ -52,6 +63,65 @@ public InvocationRequest nextInvocation() { return NativeClient.next(); } + /* + * Retry immediately then retry with exponential backoff. + */ + public static T getSupplierResultWithExponentialBackoff(LambdaContextLogger lambdaLogger, long initialDelayMS, long maxBackoffPeriodMS, int maxNumOfAttempts, Supplier supplier, Function exceptionMessageComposer, Exception maxRetriesException) throws Exception { + long delayMS = initialDelayMS; + for (int attempts = 0; attempts < maxNumOfAttempts; attempts++) { + boolean isFirstAttempt = attempts == 0; + boolean isLastAttempt = (attempts + 1) == maxNumOfAttempts; + + // Try and log whichever exceptions happened + try { + return supplier.get(); + } catch (Exception e) { + String logMessage = exceptionMessageComposer.apply(e); + if (!isLastAttempt) { + logMessage += String.format("\nRetrying%s", isFirstAttempt ? "." : String.format(" in %d ms.", delayMS)); + } + + lambdaLogger.log(logMessage, lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + } + + // throw if ran out of attempts. + if (isLastAttempt) { + throw maxRetriesException; + } + + // update the delay duration. + if (!isFirstAttempt) { + try { + Thread.sleep(delayMS); + delayMS = Math.min(delayMS * 2, maxBackoffPeriodMS); + } catch (InterruptedException e) { + Thread.interrupted(); + } + } + } + + // Should Not be reached. + throw new IllegalStateException(); + } + + @Override + public InvocationRequest nextInvocationWithExponentialBackoff(LambdaContextLogger lambdaLogger) throws Exception { + Supplier nextInvocationSupplier = () -> nextInvocation(); + Function exceptionMessageComposer = (e) -> { + return String.format("Runtime Loop on Thread ID: %s Failed to fetch next invocation.\n%s", Thread.currentThread().getName(), UserFault.trace(e)); + }; + + return getSupplierResultWithExponentialBackoff( + lambdaLogger, + INITIAL_BACKOFF_PERIOD_MS, + MAX_BACKOFF_PERIOD_MS, + MAX_NUMBER_OF_RETRIALS, + nextInvocationSupplier, + exceptionMessageComposer, + new LambdaRuntimeClientMaxRetriesExceededException("Get Next Invocation") + ); + } + @Override public void reportInvocationSuccess(String requestId, byte[] response) { NativeClient.postInvocationResponse(requestId.getBytes(UTF_8), response); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java new file mode 100644 index 000000000..467afa25c --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java @@ -0,0 +1,15 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; + +public class LambdaRuntimeClientMaxRetriesExceededException extends LambdaRuntimeClientException { + // 429 is possible; however, that is more appropriate when a server is responding to a spamming client that it wants to rate limit. + // In Our case, however, the RIC is a client that is not able to get a response from an upstream server, so 500 is more appropriate. + public LambdaRuntimeClientMaxRetriesExceededException(String operationName) { + super("Maximum Number of retries have been exceed" + (operationName.equals(null) + ? String.format(" for the %s operation.", operationName) + : "."), 500); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java index db1f6c218..96fbd4b79 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java @@ -12,33 +12,27 @@ import com.amazonaws.services.lambda.runtime.logging.LogLevel; public class ConcurrencyConfig { - private static final int AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT = 1000; + private static final int AWS_LAMBDA_MAX_CONCURRENCY_LIMIT = 1000; private final int numberOfPlatformThreads; - private final boolean isConcurrencyEnabled; public ConcurrencyConfig(LambdaContextLogger logger) { this(logger, new EnvReader()); } public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) { - this.isConcurrencyEnabled = Boolean.parseBoolean(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)); - int platformThreads = this.isConcurrencyEnabled ? Runtime.getRuntime().availableProcessors() : 0; - - if (this.isConcurrencyEnabled) { - try { - int readNumOfPlatformThreads = Integer.parseUnsignedInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)); - if (readNumOfPlatformThreads > 0 && readNumOfPlatformThreads <= AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT) { - platformThreads = readNumOfPlatformThreads; - } else { - throw new IllegalArgumentException(); - } - } catch (Exception e) { - String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to number of cores as number of platform threads.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY, AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT, UserFault.trace(e)); - logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); + int readNumOfPlatformThreads = 0; + try { + readNumOfPlatformThreads = Integer.parseInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)); + if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { + throw new IllegalArgumentException(); } + } catch (Exception e) { + String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, UserFault.trace(e)); + logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); + readNumOfPlatformThreads = 0; } - this.numberOfPlatformThreads = platformThreads; + this.numberOfPlatformThreads = readNumOfPlatformThreads; } public String getConcurrencyConfigMessage() { @@ -46,7 +40,7 @@ public String getConcurrencyConfigMessage() { } public boolean isMultiConcurrent() { - return this.isConcurrencyEnabled && this.numberOfPlatformThreads >= 2; + return this.numberOfPlatformThreads > 1; } public int getNumberOfPlatformThreads() { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java index 4412214e9..de1f9e73f 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java @@ -12,6 +12,7 @@ import static org.mockito.Mockito.*; import java.io.ByteArrayOutputStream; +import java.io.IOError; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -19,6 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientMaxRetriesExceededException; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; import com.amazonaws.services.lambda.runtime.api.client.util.ConcurrencyConfig; import com.amazonaws.services.lambda.runtime.api.client.util.EnvReader; @@ -115,6 +117,8 @@ private static InvocationRequest getFakeInvocationRequest(String id) { return request; } + private static final LambdaRuntimeClientMaxRetriesExceededException fakelambdaRuntimeClientMaxRetriesExceededException = new LambdaRuntimeClientMaxRetriesExceededException("Fake max retries happened"); + @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); @@ -135,7 +139,7 @@ void testConcurrentRunWithPlatformThreads() throws Throwable { InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); - when(runtimeClient.nextInvocation()) + when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger)) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) @@ -143,10 +147,10 @@ void testConcurrentRunWithPlatformThreads() throws Throwable { .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) - .thenReturn(null) - .thenReturn(null) - .thenReturn(null) - .thenReturn(null); + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException); AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); @@ -171,20 +175,23 @@ void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable { final String UserFaultID = "Injected Fault Request ID"; when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); - when(runtimeClient.nextInvocation()) + when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger)) .thenReturn(failImmediatelyRequest) .thenReturn(userFaultRequest) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) - .thenReturn(null) - .thenReturn(null) - .thenReturn(null); + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException); AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); - // One for each of failImmediatelyRequest and userFaultRequest in finally block + three from the uncaught null expections. - verify(lambdaLogger, times(5)).log(anyString(), eq(LogLevel.ERROR)); - + // One for each of failImmediatelyRequest and userFaultRequest in finally block + // Four for crashing the Four runtime loops in the outermost catch of the runtime loop after the Null responses. + // 2 + 4 = 6 + verify(lambdaLogger, times(6)).log(anyString(), eq(LogLevel.ERROR)); + // Failed invokes should be reported. verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); @@ -198,24 +205,86 @@ void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable { // Hashmap total count should equal all tasks that ran * number of iterations per task assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testConcurrentModeLoopDoesNotExitExceptForLambdaRuntimeClientMaxRetriesExceededException() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(true); + when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(1); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); + + InvocationRequest userFaultRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String UserFaultID = "Injected Fault Request ID"; + when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + InvocationRequest virtualMachineErrorRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String IOErrorID = "ioerr1"; + when(virtualMachineErrorRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn(IOErrorID); + + when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger)) + .thenReturn(failImmediatelyRequest) + .thenReturn(userFaultRequest) + .thenReturn(virtualMachineErrorRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenReturn(successfullInvocationRequest); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // One for each of failImmediatelyRequest, userFaultRequest, and virtualMachineErrorRequest + One for the runtime loop thread crashing. + verify(lambdaLogger, times(4)).log(anyString(), eq(LogLevel.ERROR)); + + // Failed invokes should be reported. + verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); + verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); + verify(runtimeClient).reportInvocationError(eq(IOErrorID), any()); + + // Success Reports Must Equal number of tasks that ran successfully. + verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + + // Hashmap keys should equal the minumum between(number of threads (runtime loops) AND number of tasks that ran successfully). + assertEquals(1, SampleHandler.hashMap.size()); + + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } + + + /* + * + * NON-CONCURRENT-MODE TESTS + * + */ @Test @Timeout(value = 1, unit = TimeUnit.MINUTES) - void testSequentialWithFailures() throws Throwable { + void testSequentialWithFatalUserFaultErrorStopsLoop() throws Throwable { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); when(concurrencyConfig.isMultiConcurrent()).thenReturn(false); InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); - InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); - InvocationRequest userFaultRequest = mock(InvocationRequest.class); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); // recoverable error in all modes. + + InvocationRequest userFaultRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. final String UserFaultID = "Injected Fault Request ID"; when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + InvocationRequest virtualMachineErrorRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String IOErrorID = "ioerr1"; + when(virtualMachineErrorRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn(IOErrorID); when(runtimeClient.nextInvocation()) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) .thenReturn(failImmediatelyRequest) - .thenReturn(userFaultRequest); + .thenReturn(userFaultRequest) + // these two should not be even feltched since userFaultRequest is not recoverable. + .thenReturn(successfullInvocationRequest) + .thenReturn(virtualMachineErrorRequest); AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); @@ -226,8 +295,55 @@ void testSequentialWithFailures() throws Throwable { verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); - // Success Reports Must Equal number of tasks that ran successfully. + // Success Reports Must Equal number of tasks that ran successfully. And only 2 Error reports for failImmediatelyRequest and userFaultRequest. + verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + verify(runtimeClient, times(2)).reportInvocationError(any(), any()); + + // Hashmap keys should equal one as it is not multithreaded. + assertEquals(1, SampleHandler.hashMap.size()); + + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testSequentialWithVirtualMachineErrorStopsLoop() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(false); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); // recoverable error in all modes. + + InvocationRequest userFaultRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String UserFaultID = "Injected Fault Request ID"; + when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + InvocationRequest virtualMachineErrorRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String IOErrorID = "ioerr1"; + when(virtualMachineErrorRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn(IOErrorID); + + when(runtimeClient.nextInvocation()) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(failImmediatelyRequest) + .thenReturn(virtualMachineErrorRequest) + // these two should not be even feltched since userFaultRequest is not recoverable. + .thenReturn(successfullInvocationRequest) + .thenReturn(userFaultRequest); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // One for failImmediatelyRequest and userFaultRequest in finally block. + verify(lambdaLogger, times(2)).log(anyString(), eq(LogLevel.ERROR)); + + // Failed invokes should be reported. + verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); + verify(runtimeClient).reportInvocationError(eq(IOErrorID), any()); + + // Success Reports Must Equal number of tasks that ran successfully. And only 2 Error reports for failImmediatelyRequest and virtualMachineErrorRequest. verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + verify(runtimeClient, times(2)).reportInvocationError(any(), any()); // Hashmap keys should equal one as it is not multithreaded. assertEquals(1, SampleHandler.hashMap.size()); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java index 151e702af..f97602e37 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java @@ -67,7 +67,12 @@ public void testLoggingNullValuesWithoutLogLevelInText() { assertEquals("null", new String(sink.getMessages().get(1))); } - // Makes Sure Logging Contexts are thread local. + /* + * Makes Sure Logging Contexts are thread local. + * We start `setLambdaContext` operations using the **single** shared `logger` object on a fixed thread pool, differentiating them with thread IDs. + * We then start concurrent `log` operations which are scheduled using that fixed pool. + * It is then verified that a given log operation, which logs the thread ID it is running on, used a context that had the same thread ID. + */ @Test public void testMultiConcurrentLoggingWithoutLogLevelInJSON() { TestSink sink = new TestSink(); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java index 473e2aef3..710c1565e 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java @@ -14,6 +14,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.ErrorRequest; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.StackElement; @@ -22,8 +24,18 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import okhttp3.HttpUrl; import static java.net.HttpURLConnection.HTTP_ACCEPTED; import static java.net.HttpURLConnection.HTTP_OK; @@ -36,6 +48,14 @@ @DisabledOnOs(OS.MAC) public class LambdaRuntimeApiClientImplTest { + @SuppressWarnings("rawtypes") + private final Supplier mockSupplier = mock(Supplier.class); + @SuppressWarnings("rawtypes") + private final Function mockExceptionMessageComposer = mock(Function.class); + private final LambdaContextLogger mockLambdaContextLogger = mock(LambdaContextLogger.class); + private final LambdaRuntimeClientMaxRetriesExceededException retriesExceededException = new LambdaRuntimeClientMaxRetriesExceededException("Testing Invocations"); + final String fakeExceptionMessage = "Something bad"; + MockWebServer mockWebServer; LambdaRuntimeApiClientImpl lambdaRuntimeApiClientImpl; @@ -43,7 +63,7 @@ public class LambdaRuntimeApiClientImplTest { ErrorRequest errorRequest = new ErrorRequest("testErrorMessage", "testErrorType", errorStackStrace); String requestId = "1234"; - + @BeforeEach void setUp() { mockWebServer = new MockWebServer(); @@ -51,6 +71,67 @@ void setUp() { lambdaRuntimeApiClientImpl = new LambdaRuntimeApiClientImpl(hostnamePort); } + @SuppressWarnings("unchecked") + @Test + public void testgetSupplierResultWithExponentialBackoffAllFailing() throws Exception { + + when(mockSupplier.get()).thenThrow(new RuntimeException(new Exception(fakeExceptionMessage))); + when(mockExceptionMessageComposer.apply(any())).thenReturn(fakeExceptionMessage); + + try { + LambdaRuntimeApiClientImpl.getSupplierResultWithExponentialBackoff(mockLambdaContextLogger, 5, 200, 5, mockSupplier, mockExceptionMessageComposer, retriesExceededException); + } catch (LambdaRuntimeClientMaxRetriesExceededException e) { } + + verify(mockSupplier, times(5)).get(); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 5 ms."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 10 ms."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 20 ms."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage), any()); + verify(mockLambdaContextLogger, times(5)).log(anyString(), any()); + } + + @SuppressWarnings("unchecked") + @Test + public void testgetSupplierResultWithExponentialBackoffTwoFailingThenSuccess() throws Exception { + InvocationRequest fakeRequest = new InvocationRequest(); + + when(mockExceptionMessageComposer.apply(any())).thenReturn(fakeExceptionMessage); + + when(mockSupplier.get()) + .thenThrow(new RuntimeException(new Exception(fakeExceptionMessage))) + .thenThrow(new RuntimeException(new Exception(fakeExceptionMessage))) + .thenReturn(fakeRequest); + + InvocationRequest invocationRequest = (InvocationRequest) LambdaRuntimeApiClientImpl.getSupplierResultWithExponentialBackoff(mockLambdaContextLogger, 5, 200, 5, mockSupplier, mockExceptionMessageComposer, retriesExceededException); + + assertEquals(fakeRequest, invocationRequest); + verify(mockSupplier, times(3)).get(); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 5 ms."), any()); + verify(mockLambdaContextLogger, times(2)).log(anyString(), any()); + } + + @SuppressWarnings("unchecked") + @Test + public void testgetSupplierResultWithExponentialBackoffDoesntGoAboveMax() throws Exception { + + when(mockSupplier.get()).thenThrow(new RuntimeException(new Exception(fakeExceptionMessage))); + + when(mockExceptionMessageComposer.apply(any())).thenReturn(fakeExceptionMessage); + + try { + LambdaRuntimeApiClientImpl.getSupplierResultWithExponentialBackoff(mockLambdaContextLogger, 100, 200, 5, mockSupplier, mockExceptionMessageComposer, retriesExceededException); + } catch (LambdaRuntimeClientMaxRetriesExceededException e) { } + + verify(mockSupplier, times(5)).get(); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 100 ms."), any()); + verify(mockLambdaContextLogger, times(2)).log(eq(fakeExceptionMessage + "\nRetrying in 200 ms."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage), any()); + verify(mockLambdaContextLogger, times(5)).log(anyString(), any()); + } + @Test public void reportInitErrorTest() { try { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java index bbb43cd14..ef1d832ec 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java @@ -30,18 +30,16 @@ class ConcurrencyConfigTest { @Test void testDefaultConfiguration() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn(null); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn(null); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); - assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + assertEquals(0, config.getNumberOfPlatformThreads()); + assertEquals(false, config.isMultiConcurrent()); } @Test void testValidPlatformThreadsConfig() { - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("4"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); assertEquals(4, config.getNumberOfPlatformThreads()); @@ -51,31 +49,28 @@ void testValidPlatformThreadsConfig() { @Test void testInvalidPlatformThreadsConfig() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("invalid"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("invalid"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + assertEquals(0, config.getNumberOfPlatformThreads()); verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); - assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + assertEquals(false, config.isMultiConcurrent()); } @Test void testExceedingPlatformThreadsLimit() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("1001"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1001"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + assertEquals(0, config.getNumberOfPlatformThreads()); verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); - assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + assertEquals(false, config.isMultiConcurrent()); } @Test void testGetConcurrencyConfigMessage() { - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("4"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); String expectedMessage = "Starting 4 concurrent function handler threads."; @@ -85,8 +80,6 @@ void testGetConcurrencyConfigMessage() { @Test void testGetConcurrencyConfigWithNoConcurrency() { - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("false"); - ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); assertEquals(0, config.getNumberOfPlatformThreads()); assertEquals(false, config.isMultiConcurrent()); From 93221da3c6d2c74d7b69f91a12a99f7eaabc1360 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:52:16 +0100 Subject: [PATCH 26/69] Merge from public repo (#88) Merge from Public to Private Repo --- .github/dependabot.yml | 1 - README.md | 4 ++-- aws-lambda-java-core/RELEASE.CHANGELOG.md | 4 ---- aws-lambda-java-core/pom.xml | 2 +- .../com/amazonaws/services/lambda/runtime/Context.java | 10 ---------- aws-lambda-java-events/README.md | 2 +- aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-runtime-interface-client/README.md | 4 ++-- .../RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-runtime-interface-client/pom.xml | 4 ++-- aws-lambda-java-tests/pom.xml | 2 +- .../fastJson/HelloWorldFunction/pom.xml | 2 +- .../gson/HelloWorldFunction/pom.xml | 2 +- .../moshi/HelloWorldFunction/pom.xml | 2 +- .../request-stream-handler/HelloWorldFunction/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 2 +- 17 files changed, 23 insertions(+), 30 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 34d287eec..88f18ea29 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,6 @@ updates: directory: "/aws-lambda-java-runtime-interface" schedule: interval: "weekly" - - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/README.md b/README.md index 7f4970949..b5153a87f 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ public class SqsHandler implements RequestHandler { com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 ``` @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 ``` diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md index aebc8ecd9..527e7dd46 100644 --- a/aws-lambda-java-core/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md @@ -1,7 +1,3 @@ -### September 3, 2025 -`1.4.0` -- Getter support for x-ray trace ID through the Context object - ### May 26, 2025 `1.3.0` - Adding support for multi tenancy ([#545](https://github.com/aws/aws-lambda-java-libs/pull/545)) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index cca9d0cdf..77245c9be 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-core - 1.4.0 + 1.3.0 jar AWS Lambda Java Core Library diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java index ed9311a11..152765df1 100644 --- a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java @@ -109,14 +109,4 @@ public interface Context { default String getTenantId() { return null; } - - /** - * - * Returns the X-Ray trace ID associated with the request. - * - * @return null by default - */ - default String getXrayTraceId() { - return null; - } } diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 87c61f345..43c25d76a 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -74,7 +74,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 ... diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index 6c1769751..a4bcd10a0 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 17, 2025 +`3.16.0`: +- Add Schema metadata related attributes in KafkaEvent ([#548](https://github.com/aws/aws-lambda-java-libs/pull/548)) + ### January 31, 2025 `3.15.0`: - Fix `CognitoUserPoolPreTokenGenerationEventV2` model ([#519](https://github.com/aws/aws-lambda-java-libs/pull/519)) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index f1364e7ab..8799966be 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 368ab710a..67a5972d6 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 @@ -203,7 +203,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index 6a781b270..ac073ae85 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### May 21, 2025 +`2.7.0` +- Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540)) + ### August 7, 2024 `2.6.0` - Runtime API client improvements: use Lambda-Runtime-Function-Error-Type for reporting errors in format "Runtime." diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index fde515fda..866520092 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 jar AWS Lambda Java Runtime Interface Client @@ -61,7 +61,7 @@ com.amazonaws aws-lambda-java-core - 1.4.0 + 1.3.0 com.amazonaws diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 9e3419c69..6589ca742 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 org.junit.jupiter diff --git a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml index 7325c72a0..7d3c44246 100644 --- a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 diff --git a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml index dd3b8e9c5..fd4271824 100644 --- a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 com.google.code.gson diff --git a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml index f23214976..2773ef1f1 100644 --- a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 diff --git a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml index 68e7e81e9..f6ca21bf7 100644 --- a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 com.google.code.gson diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 3d03205d3..56c959244 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -46,7 +46,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 From d7e2a6d188528ec22ade0e2ae8f6cd45fcdb8de5 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 26 Jun 2025 17:49:55 +0100 Subject: [PATCH 27/69] Version Bump to 2.8 (#89) Co-authored-by: Mohammed Ehab --- README.md | 2 +- aws-lambda-java-runtime-interface-client/README.md | 2 +- aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b5153a87f..4b22852c1 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.8.0 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 67a5972d6..512e66fe0 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.8.0 diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index ac073ae85..a030d288b 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 26, 2025 +`2.8.0` +- Refactoring + ### May 21, 2025 `2.7.0` - Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540)) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 866520092..a29be4279 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.8.0 jar AWS Lambda Java Runtime Interface Client From 2cf626b817042b65016d4c8532259d8c5a819ff2 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:39:45 +0100 Subject: [PATCH 28/69] Only Log Concurrency Warning Message when AWS_LAMBDA_MAX_CONCURRENCY is set (#90) * Only Log Concurrency Warning Message when AWS_LAMBDA_MAX_CONCURRENCY is set * Var name change --------- Co-authored-by: Mohammed Ehab --- .../api/client/util/ConcurrencyConfig.java | 19 ++++++++++++++----- .../client/util/ConcurrencyConfigTest.java | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java index 96fbd4b79..d98ed71c8 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java @@ -22,14 +22,23 @@ public ConcurrencyConfig(LambdaContextLogger logger) { public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) { int readNumOfPlatformThreads = 0; try { - readNumOfPlatformThreads = Integer.parseInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)); - if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { - throw new IllegalArgumentException(); + String readLambdaMaxConcurrencyEnvVar = envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY); + + // Log only if env var is actually set to an invalid value. Otherwise default to no concurrency silently. + if (!(readLambdaMaxConcurrencyEnvVar == null || readLambdaMaxConcurrencyEnvVar.isEmpty())) { + readNumOfPlatformThreads = Integer.parseInt(readLambdaMaxConcurrencyEnvVar); + if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { + throw new IllegalArgumentException(); + } } } catch (Exception e) { - String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, UserFault.trace(e)); - logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); readNumOfPlatformThreads = 0; + String message = String.format( + "User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", + ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, + AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, + UserFault.trace(e)); + logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); } this.numberOfPlatformThreads = readNumOfPlatformThreads; diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java index ef1d832ec..248625e37 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java @@ -29,10 +29,10 @@ class ConcurrencyConfigTest { @Test void testDefaultConfiguration() { - when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn(null); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + verifyNoInteractions(lambdaLogger); assertEquals(0, config.getNumberOfPlatformThreads()); assertEquals(false, config.isMultiConcurrent()); } From e35157b9751492ea65a80716144205b248a500de Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Tue, 8 Jul 2025 10:05:09 +0100 Subject: [PATCH 29/69] Version Bump RIC to 2.8.1 (#91) * Version Bump RIC to 2.8.1 --------- Authored-by: Mohammed Ehab --- README.md | 2 +- aws-lambda-java-runtime-interface-client/README.md | 2 +- aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4b22852c1..0870ec2b7 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.0 + 2.8.1 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 512e66fe0..90e2c0bb5 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.0 + 2.8.1 diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index a030d288b..daa8bde90 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 26, 2025 +`2.8.1` +- Refactoring + ### June 26, 2025 `2.8.0` - Refactoring diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index a29be4279..92d9d0f40 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.0 + 2.8.1 jar AWS Lambda Java Runtime Interface Client From ddb34451478eb57042fdd332b9caa2afd7a0bdf2 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:08:43 +0100 Subject: [PATCH 30/69] Allow AWS_LAMBDA_MAX_CONCURRENCY to be one and crash the RIC Otherwise and Version Bump to 2.8.2 (#92) * Allow AWS_LAMBDA_MAX_CONCURRENCY to be one and crash the RIC Otherwise. * Version Bump to 2.8.2 --------- Authored-by: Mohammed Ehab --- README.md | 2 +- .../README.md | 2 +- .../RELEASE.CHANGELOG.md | 4 ++ .../pom.xml | 2 +- .../ReservedRuntimeEnvironmentVariables.java | 4 +- .../api/client/util/ConcurrencyConfig.java | 19 ++++---- .../client/util/ConcurrencyConfigTest.java | 47 ++++++++++++++----- 7 files changed, 51 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 0870ec2b7..70d7c7316 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.1 + 2.8.2 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 90e2c0bb5..e3c68fe8a 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.1 + 2.8.2 diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index daa8bde90..ff3d8bec5 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 26, 2025 +`2.8.2` +- Allow AWS_LAMBDA_MAX_CONCURRENCY to be One. Crash the RIC if it is set to an un-parsable string to an integer or an out of bounds value. + ### June 26, 2025 `2.8.1` - Refactoring diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 92d9d0f40..59f33145e 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.1 + 2.8.2 jar AWS Lambda Java Runtime Interface Client diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java index 1de3dca99..9fdec6b9f 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java @@ -108,8 +108,8 @@ public interface ReservedRuntimeEnvironmentVariables { String TZ = "TZ"; /* - * Used to set the required number of concurrent runtime loops, - * If AWS_LAMBDA_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores. + * If set to a string parsable as an integer > 0, It enables multiconcurrency mode. + * Otherwise, if it is set to an invalid value, it will crash the whole RIC process. */ String AWS_LAMBDA_MAX_CONCURRENCY = "AWS_LAMBDA_MAX_CONCURRENCY"; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java index d98ed71c8..7108c7801 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java @@ -14,6 +14,8 @@ public class ConcurrencyConfig { private static final int AWS_LAMBDA_MAX_CONCURRENCY_LIMIT = 1000; private final int numberOfPlatformThreads; + private final String INVALID_CONFIG_MESSAGE_PREFIX = String.format("User configured %s is invalid. Please make sure it is a positive number more than zero and less than or equal %d", + ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, AWS_LAMBDA_MAX_CONCURRENCY_LIMIT); public ConcurrencyConfig(LambdaContextLogger logger) { this(logger, new EnvReader()); @@ -24,21 +26,16 @@ public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) { try { String readLambdaMaxConcurrencyEnvVar = envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY); - // Log only if env var is actually set to an invalid value. Otherwise default to no concurrency silently. - if (!(readLambdaMaxConcurrencyEnvVar == null || readLambdaMaxConcurrencyEnvVar.isEmpty())) { + if (readLambdaMaxConcurrencyEnvVar != null) { readNumOfPlatformThreads = Integer.parseInt(readLambdaMaxConcurrencyEnvVar); - if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { + if (readNumOfPlatformThreads < 1 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { throw new IllegalArgumentException(); } } } catch (Exception e) { - readNumOfPlatformThreads = 0; - String message = String.format( - "User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", - ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, - AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, - UserFault.trace(e)); - logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); + String message = String.format("%s\n%s", INVALID_CONFIG_MESSAGE_PREFIX, UserFault.trace(e)); + logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + throw e; } this.numberOfPlatformThreads = readNumOfPlatformThreads; @@ -49,7 +46,7 @@ public String getConcurrencyConfigMessage() { } public boolean isMultiConcurrent() { - return this.numberOfPlatformThreads > 1; + return this.numberOfPlatformThreads >= 1; } public int getNumberOfPlatformThreads() { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java index 248625e37..096d52bb0 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java @@ -14,8 +14,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -27,6 +28,8 @@ class ConcurrencyConfigTest { @Mock private EnvReader envReader; + private static final String exitingRuntimeString = String.format("User configured %s is invalid.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY); + @Test void testDefaultConfiguration() { when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn(null); @@ -37,35 +40,51 @@ void testDefaultConfiguration() { assertEquals(false, config.isMultiConcurrent()); } + @Test + void testBelowMinPlatformThreadsLimit() { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("0"); + + assertThrows(IllegalArgumentException.class, () -> new ConcurrencyConfig(lambdaLogger, envReader)); + verify(lambdaLogger).log(contains(exitingRuntimeString), eq(LogLevel.ERROR)); + } + + @Test + void testMinValidPlatformThreadsConfig() { + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + verifyNoInteractions(lambdaLogger); + assertEquals(1, config.getNumberOfPlatformThreads()); + assertEquals(true, config.isMultiConcurrent()); + } + @Test void testValidPlatformThreadsConfig() { when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("4"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + verifyNoInteractions(lambdaLogger); assertEquals(4, config.getNumberOfPlatformThreads()); assertEquals(true, config.isMultiConcurrent()); } @Test - void testInvalidPlatformThreadsConfig() { + void testExceedingPlatformThreadsLimit() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("invalid"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1001"); - ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(0, config.getNumberOfPlatformThreads()); - verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); - assertEquals(false, config.isMultiConcurrent()); + assertThrows(IllegalArgumentException.class, () -> new ConcurrencyConfig(lambdaLogger, envReader)); + verify(lambdaLogger).log(contains(exitingRuntimeString), eq(LogLevel.ERROR)); } @Test - void testExceedingPlatformThreadsLimit() { + void testInvalidPlatformThreadsConfig() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1001"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("invalid"); - ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(0, config.getNumberOfPlatformThreads()); - verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); - assertEquals(false, config.isMultiConcurrent()); + assertThrows(NumberFormatException.class, () -> new ConcurrencyConfig(lambdaLogger, envReader)); + verify(lambdaLogger).log(contains(exitingRuntimeString), eq(LogLevel.ERROR)); } @Test @@ -74,6 +93,7 @@ void testGetConcurrencyConfigMessage() { ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); String expectedMessage = "Starting 4 concurrent function handler threads."; + verifyNoInteractions(lambdaLogger); assertEquals(expectedMessage, config.getConcurrencyConfigMessage()); assertEquals(true, config.isMultiConcurrent()); } @@ -81,6 +101,7 @@ void testGetConcurrencyConfigMessage() { @Test void testGetConcurrencyConfigWithNoConcurrency() { ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + verifyNoInteractions(lambdaLogger); assertEquals(0, config.getNumberOfPlatformThreads()); assertEquals(false, config.isMultiConcurrent()); } From 8fe3580bbb419c3adfc6de99abe5fd27fdbf8720 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Tue, 15 Jul 2025 09:08:43 +0100 Subject: [PATCH 31/69] =?UTF-8?q?Version=20bump:=20serialization=201.1.5?= =?UTF-8?q?=E2=86=921.1.6,=20events=203.16.0=E2=86=923.16.1,=20update=20de?= =?UTF-8?q?pendencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump aws-lambda-java-serialization from 1.1.5 to 1.1.6 - Bump aws-lambda-java-events from 3.16.0 to 3.16.1 - Update aws-lambda-java-tests dependencies to use new versions - Update aws-lambda-java-runtime-interface-client serialization dependency 1.1.2โ†’1.1.6 - Update aws-lambda-java-events-sdk-transformer events dependency 3.11.2โ†’3.16.1 --- aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 8799966be..925273e9b 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.16.1 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 6589ca742..da07133c1 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.16.1 org.junit.jupiter From 428d1255901ba2280ba533bcc90b98f6c46611a8 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Tue, 15 Jul 2025 09:26:46 +0100 Subject: [PATCH 32/69] Fix runtime interface client workflows to use local serialization dependency Add local build step for aws-lambda-java-serialization before building runtime interface client. ## Why This Fix is Needed The runtime interface client depends on aws-lambda-java-serialization version 1.1.6, but this version doesn't exist in Maven Central yet. By building and installing the serialization package locally first, we ensure: 1. The correct version (1.1.6) is available in the local Maven repository 2. The runtime interface client build won't fail looking for a non-existent version on Maven Central 3. The workflow tests the actual code changes together ## Changes Made - runtime-interface-client_merge_to_main.yml: Added local serialization build step - runtime-interface-client_pr.yml: Added local serialization build step to both smoke-test and build jobs This ensures CI/CD pipeline works correctly with the new dependency versions. --- .../workflows/runtime-interface-client_merge_to_main.yml | 4 ++++ .github/workflows/runtime-interface-client_pr.yml | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index e07b191e1..88f8afde2 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -47,6 +47,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index cbc5b5ab1..d1821d2ce 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -31,6 +31,10 @@ jobs: working-directory: ./aws-lambda-java-serialization run: mvn clean install -DskipTests + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Runtime Interface Client smoke tests - Run 'pr' target working-directory: ./aws-lambda-java-runtime-interface-client run: make pr @@ -59,6 +63,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build From 477c0805ed9247d9c6f7abc567d68f6693ce7826 Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:12:48 +0100 Subject: [PATCH 33/69] Update ric pom.xml --- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 59f33145e..7a0d0264a 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.2 + 2.8.3 jar AWS Lambda Java Runtime Interface Client From 44cd05e502c4717400a398a4a6b9276044588b6c Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Thu, 17 Jul 2025 13:41:48 +0100 Subject: [PATCH 34/69] Update runtime-interface-client_merge_to_main.yml --- .github/workflows/runtime-interface-client_merge_to_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 88f8afde2..94294501c 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -49,7 +49,7 @@ jobs: - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests + run: mvn clean install - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client From ba4ef1c0862e5997ac9764bf07a7e108d11bece3 Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Thu, 17 Jul 2025 14:02:05 +0100 Subject: [PATCH 35/69] Update runtime-interface-client_pr.yml --- .github/workflows/runtime-interface-client_pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index d1821d2ce..b5631d2b4 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -33,7 +33,7 @@ jobs: - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests + run: mvn clean install - name: Runtime Interface Client smoke tests - Run 'pr' target working-directory: ./aws-lambda-java-runtime-interface-client @@ -65,7 +65,7 @@ jobs: - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests + run: mvn clean install - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client From 3ac07d05773a299bfc10faa0dd0ce3e854f07af4 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Fri, 25 Jul 2025 18:06:33 +0100 Subject: [PATCH 36/69] Ensure EventHandlerLoader Thread Safety. (#95) * Make handler response buffers thread safe. * Add multiconcurrency tests * ThreadLocal instead of Allocating new buffers every invoke. * Thread local log4jContextPutMethod * Fix indentations * Add CountDownLatch to ensure all calls are done simultaneously. --------- Co-authored-by: Mohammed Ehab --- .../api/client/EventHandlerLoader.java | 37 ++++----- .../api/client/EventHandlerLoaderTest.java | 79 ++++++++++++++++++- 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index af146fd93..afb3e3df7 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -57,10 +57,10 @@ private enum Platform { UNKNOWN } - private static volatile PojoSerializer contextSerializer; - private static volatile PojoSerializer cognitoSerializer; + private static volatile ThreadLocal> contextSerializer = new ThreadLocal<>(); + private static volatile ThreadLocal> cognitoSerializer = new ThreadLocal<>(); - private static final EnumMap>> typeCache = new EnumMap<>(Platform.class); + private static final ThreadLocal>>> typeCache = ThreadLocal.withInitial(() -> new EnumMap<>(Platform.class)); private static final Comparator methodPriority = new Comparator() { public int compare(Method lhs, Method rhs) { @@ -127,10 +127,11 @@ private static PojoSerializer getSerializer(Platform platform, Type type } private static PojoSerializer getSerializerCached(Platform platform, Type type) { - Map> cache = typeCache.get(platform); + EnumMap>> threadTypeCache = typeCache.get(); + Map> cache = threadTypeCache.get(platform); if (cache == null) { cache = new HashMap<>(); - typeCache.put(platform, cache); + threadTypeCache.put(platform, cache); } PojoSerializer serializer = cache.get(type); @@ -143,17 +144,17 @@ private static PojoSerializer getSerializerCached(Platform platform, Typ } private static PojoSerializer getContextSerializer() { - if (contextSerializer == null) { - contextSerializer = GsonFactory.getInstance().getSerializer(LambdaClientContext.class); + if (contextSerializer.get() == null) { + contextSerializer.set(GsonFactory.getInstance().getSerializer(LambdaClientContext.class)); } - return contextSerializer; + return contextSerializer.get(); } private static PojoSerializer getCognitoSerializer() { - if (cognitoSerializer == null) { - cognitoSerializer = GsonFactory.getInstance().getSerializer(LambdaCognitoIdentity.class); + if (cognitoSerializer.get() == null) { + cognitoSerializer.set(GsonFactory.getInstance().getSerializer(LambdaCognitoIdentity.class)); } - return cognitoSerializer; + return cognitoSerializer.get(); } @@ -527,15 +528,14 @@ private static LambdaRequestHandler wrapPojoHandler(RequestHandler instance, Typ private static LambdaRequestHandler wrapRequestStreamHandler(final RequestStreamHandler handler) { return new LambdaRequestHandler() { - private final ByteArrayOutputStream output = new ByteArrayOutputStream(1024); - private Functions.V2 log4jContextPutMethod = null; + private final ThreadLocal outputBuffers = ThreadLocal.withInitial(() -> new ByteArrayOutputStream(1024)); + private ThreadLocal> log4jContextPutMethod = new ThreadLocal<>(); - private void safeAddRequestIdToLog4j(String log4jContextClassName, - InvocationRequest request, Class contextMapValueClass) { + private void safeAddRequestIdToLog4j(String log4jContextClassName, InvocationRequest request, Class contextMapValueClass) { try { Class log4jContextClass = ReflectUtil.loadClass(AWSLambda.getCustomerClassLoader(), log4jContextClassName); - log4jContextPutMethod = ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass); - log4jContextPutMethod.call("AWSRequestId", request.getId()); + log4jContextPutMethod.set(ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass)); + log4jContextPutMethod.get().call("AWSRequestId", request.getId()); } catch (Exception e) { // nothing to do here } @@ -558,6 +558,7 @@ private void safeAddContextToLambdaLogger(LambdaContext context) { } public ByteArrayOutputStream call(InvocationRequest request) throws Error, Exception { + ByteArrayOutputStream output = outputBuffers.get(); output.reset(); LambdaCognitoIdentity cognitoIdentity = null; @@ -591,7 +592,7 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep safeAddRequestIdToLog4j("org.apache.log4j.MDC", request, Object.class); safeAddRequestIdToLog4j("org.apache.logging.log4j.ThreadContext", request, String.class); // if put method not assigned in either call to safeAddRequestIdtoLog4j then log4jContextPutMethod = null - if (log4jContextPutMethod == null) { + if (log4jContextPutMethod.get() == null) { System.err.println("Customer using log4j appender but unable to load either " + "org.apache.log4j.MDC or org.apache.logging.log4j.ThreadContext. " + "Customer cannot see RequestId in log4j log lines."); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java index 76e6f0249..aae2f1afe 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java @@ -4,8 +4,16 @@ import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; class EventHandlerLoaderTest { @@ -37,7 +45,6 @@ void PojoHandlerTest_oneParamEvent() throws Exception { assertSuccessfulInvocation(lambdaRequestHandler); } - @Test void PojoHandlerTest_oneParamContext() throws Exception { String handler = "test.lambda.handlers.POJOHanlderImpl::oneParamHandler_context"; @@ -74,4 +81,72 @@ private static InvocationRequest getTestInvocationRequest() { invocationRequest.setXrayTraceId("traceId"); return invocationRequest; } -} \ No newline at end of file + + // Multithreaded test methods + + @Test + void RequestHandlerTest_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.RequestHandlerImpl"); + } + + @Test + void RequestStreamHandlerTest_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.RequestStreamHandlerImpl"); + } + + @Test + void PojoHandlerTest_noParams_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::noParamsHandler"); + } + + @Test + void PojoHandlerTest_oneParamEvent_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::oneParamHandler_event"); + } + + @Test + void PojoHandlerTest_oneParamContext_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::oneParamHandler_context"); + } + + @Test + void PojoHandlerTest_twoParams_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::twoParamsHandler"); + } + + private void testHandlerConcurrency(String handlerName) throws Exception { + // Create one handler instance + LambdaRequestHandler handler = getLambdaRequestHandler(handlerName); + + int threadCount = 10; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List> futures = new ArrayList<>(); + CountDownLatch startLatch = new CountDownLatch(1); + + try { + for (int i = 0; i < threadCount; i++) { + futures.add(executor.submit(() -> { + try { + InvocationRequest request = getTestInvocationRequest(); + startLatch.await(); + ByteArrayOutputStream result = handler.call(request); + return result.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + })); + } + + // Release all threads simultaneously and Verify all invocations return the expected result + startLatch.countDown(); + + for (Future future : futures) { + String result = future.get(5, TimeUnit.SECONDS); + assertEquals("\"success\"", result); + } + } finally { + executor.shutdown(); + assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + } + } +} From 1a7af454aa66eb4ea095c65de1f977c8f0dc26a0 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:43:27 +0100 Subject: [PATCH 37/69] Make Trace ID accessible through Context (#101) * Make Trace ID accessible through Context * Change Constructor --------- Co-authored-by: Mohammed Ehab --- .github/workflows/runtime-interface-client_pr.yml | 8 ++++---- aws-lambda-java-core/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-core/pom.xml | 2 +- .../com/amazonaws/services/lambda/runtime/Context.java | 10 ++++++++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- .../lambda/runtime/api/client/EventHandlerLoader.java | 1 + .../lambda/runtime/api/client/api/LambdaContext.java | 7 +++++++ .../runtime/api/client/api/LambdaContextTest.java | 3 ++- .../api/client/logging/AbstractLambdaLoggerTest.java | 2 +- .../api/client/logging/JsonLogFormatterTest.java | 2 ++ 10 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index b5631d2b4..5819218c9 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -27,10 +27,6 @@ jobs: working-directory: ./aws-lambda-java-core run: mvn clean install - - name: Build and install serialization dependency locally - working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests - - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization run: mvn clean install @@ -62,6 +58,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + + - name: Build and install core dependency locally + working-directory: ./aws-lambda-java-core + run: mvn clean install - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md index 527e7dd46..aebc8ecd9 100644 --- a/aws-lambda-java-core/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### September 3, 2025 +`1.4.0` +- Getter support for x-ray trace ID through the Context object + ### May 26, 2025 `1.3.0` - Adding support for multi tenancy ([#545](https://github.com/aws/aws-lambda-java-libs/pull/545)) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index 77245c9be..cca9d0cdf 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-core - 1.3.0 + 1.4.0 jar AWS Lambda Java Core Library diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java index 152765df1..ed9311a11 100644 --- a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java @@ -109,4 +109,14 @@ public interface Context { default String getTenantId() { return null; } + + /** + * + * Returns the X-Ray trace ID associated with the request. + * + * @return null by default + */ + default String getXrayTraceId() { + return null; + } } diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 7a0d0264a..189743054 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -61,7 +61,7 @@ com.amazonaws aws-lambda-java-core - 1.3.0 + 1.4.0 com.amazonaws diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index afb3e3df7..f679c217c 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -583,6 +583,7 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep LambdaEnvironment.FUNCTION_VERSION, request.getInvokedFunctionArn(), request.getTenantId(), + request.getXrayTraceId(), clientContext ); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java index bd1463db6..20b77262d 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java @@ -23,6 +23,7 @@ public class LambdaContext implements Context { private final CognitoIdentity cognitoIdentity; private final ClientContext clientContext; private final String tenantId; + private final String xrayTraceId; private final LambdaLogger logger; public LambdaContext( @@ -36,6 +37,7 @@ public LambdaContext( String functionVersion, String invokedFunctionArn, String tenantId, + String xrayTraceId, ClientContext clientContext ) { this.memoryLimit = memoryLimit; @@ -49,6 +51,7 @@ public LambdaContext( this.functionVersion = functionVersion; this.invokedFunctionArn = invokedFunctionArn; this.tenantId = tenantId; + this.xrayTraceId = xrayTraceId; this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger(); } @@ -98,6 +101,10 @@ public String getTenantId() { return tenantId; } + public String getXrayTraceId() { + return xrayTraceId; + } + public LambdaLogger getLogger() { return logger; } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java index 58880be43..f7da76198 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java @@ -19,6 +19,7 @@ public class LambdaContextTest { private static final LambdaClientContext CLIENT_CONTEXT = new LambdaClientContext(); public static final int MEMORY_LIMIT = 128; public static final String TENANT_ID = "tenant-id"; + public static final String X_RAY_TRACE_ID = "x-ray-trace-id"; @Test public void getRemainingTimeInMillis() { @@ -55,6 +56,6 @@ public void getRemainingTimeInMillis_Deadline() throws InterruptedException { private LambdaContext createContextWithDeadline(long deadlineTimeInMs) { return new LambdaContext(MEMORY_LIMIT, deadlineTimeInMs, REQUEST_ID, LOG_GROUP_NAME, LOG_STREAM_NAME, - FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, CLIENT_CONTEXT); + FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, X_RAY_TRACE_ID, CLIENT_CONTEXT); } } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java index f97602e37..3a5ee8d5f 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java @@ -84,7 +84,7 @@ public void testMultiConcurrentLoggingWithoutLogLevelInJSON() { final int nThreads = 5; ExecutorService es = Executors.newFixedThreadPool(nThreads); for (int i = 0; i < nThreads; i++) { - es.submit(() -> logger.setLambdaContext(new LambdaContext(Integer.MAX_VALUE, Long.MAX_VALUE, reqIDPrefix + Thread.currentThread().getName(), "", "", "", null, "", "", "", null))); + es.submit(() -> logger.setLambdaContext(new LambdaContext(Integer.MAX_VALUE, Long.MAX_VALUE, reqIDPrefix + Thread.currentThread().getName(), "", "", "", null, "", "", "", null, null))); } final int nMessages = 100_000; diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java index 531e9ca94..91ce9d2a3 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java @@ -30,6 +30,7 @@ void testFormattingWithLambdaContext() { null, "function-arn", null, + null, null ); assertFormatsString("test log", LogLevel.WARN, context); @@ -48,6 +49,7 @@ void testFormattingWithTenantIdInLambdaContext() { null, "function-arn", "tenant-id", + "xray-trace-id", null ); assertFormatsString("test log", LogLevel.WARN, context); From e9c084ea084f26b5aed8bcae9d466b20dc7149ba Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:46:22 +0100 Subject: [PATCH 38/69] Migrate to Deploying using Maven Central (#103) Co-authored-by: Mohammed Ehab --- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 189743054..c854fabcd 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.3 + 2.8.4 jar AWS Lambda Java Runtime Interface Client From 2250e99a2ed537a2a3dde63375944ce4b70e28ff Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:30:26 +0100 Subject: [PATCH 39/69] Fix performance Issue By Using Blocking Calls instead of Busy Waiting (#106) Co-authored-by: Mohammed Ehab --- .../services/lambda/runtime/api/client/AWSLambda.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java index 9c6dd78b1..10d595c7b 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java @@ -39,6 +39,7 @@ import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * The entrypoint of this class is {@link AWSLambda#startRuntime}. It performs two main tasks: @@ -253,10 +254,10 @@ protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandle } } finally { platformThreadExecutor.shutdown(); - while (true) { - if (platformThreadExecutor.isTerminated()) { - break; - } + try { + platformThreadExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } } } else { From 085c9754f497b6ba3240bea3a8cef3286ba823b3 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:30:55 +0100 Subject: [PATCH 40/69] Log errorType and errorMessage from RAPID (#105) Co-authored-by: Mohammed Ehab --- .../src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp index 3897d37db..84a84b439 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp @@ -370,7 +370,7 @@ runtime::post_outcome runtime::do_post( if (!is_success(aws::http::response_code(http_response_code))) { logging::log_error( - LOG_TAG, "Failed to post handler success response. Http response code: %ld.", http_response_code); + LOG_TAG, "Failed to post handler success response. Http response code: %ld. %s", http_response_code, resp.get_body().c_str()); return aws::http::response_code(http_response_code); } From e61bc6342a6d731e8699f3e8e558e4664a1249a0 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:52:42 +0100 Subject: [PATCH 41/69] Version Bump RIC to 2.8.5 (#107) Co-authored-by: Mohammed Ehab --- README.md | 2 +- aws-lambda-java-runtime-interface-client/README.md | 4 ++-- .../RELEASE.CHANGELOG.md | 13 +++++++++++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- .../test/integration/test-handler/pom.xml | 2 +- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 70d7c7316..609485625 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.2 + 2.8.5 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index e3c68fe8a..c63dcfeef 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.2 + 2.8.5 @@ -203,7 +203,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.8.5 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index ff3d8bec5..a8c34dec8 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,16 @@ +### September 17, 2025 +`2.8.5` +- Log errorType and errorMessage from RAPID in C++ Client. +- Performance Upgrade for Multiconcurrency Mode. + +### September 9, 2025 +`2.8.4` +- Make Trace ID Accessible through Context Object. + +### July 19, 2025 +`2.8.3` +- Ensure EventHandlerLoader Thread Safety. + ### June 26, 2025 `2.8.2` - Allow AWS_LAMBDA_MAX_CONCURRENCY to be One. Crash the RIC if it is set to an un-parsable string to an integer or an out of bounds value. diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index c854fabcd..b77c38e00 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.4 + 2.8.5 jar AWS Lambda Java Runtime Interface Client diff --git a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml index 5304d44b9..35ea694ab 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml +++ b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml @@ -15,7 +15,7 @@ com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.8.5 From f93664f998fcf3c52787fae1dc2bceaf639c2f82 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:13:11 +0100 Subject: [PATCH 42/69] MultiConcurrent XRAY TraceID using utils-lite (#96) Co-authored-by: Mohammed Ehab --- .../pom.xml | 38 +-- .../lambda/runtime/api/client/AWSLambda.java | 11 +- .../runtime/api/client/AWSLambdaTest.java | 230 +++++++++++++++++- 3 files changed, 258 insertions(+), 21 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index b77c38e00..0d3e7fb04 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -1,6 +1,6 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client @@ -47,9 +47,9 @@ separately from the Runtime Interface Client functionality until we figure something else out. --> true - - - + + + 4.0.0 @@ -40,7 +41,7 @@ com.amazonaws aws-lambda-java-serialization - 1.3.0 + 1.2.0 com.amazonaws From af730be04a435a588c7e90d2110dfb2d605357c1 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Thu, 19 Mar 2026 17:54:59 +0000 Subject: [PATCH 61/69] Revert upgrade in Jackson serialization (#593) * chore: reverted the poms * chore: upgrading the poms * chore: update docs * chore: add targets to tests * chore: add workflow_dispatch * chore: add comment to trigger testing * chore: downgrade jackson library * chore: removing comment * chore: new version * chore: remove file --------- Co-authored-by: Davide Melfi --- .../RELEASE.CHANGELOG.md | 5 + aws-lambda-java-serialization/pom.xml | 4 +- .../events/LambdaEventSerializers.java | 524 +++++++++--------- 3 files changed, 281 insertions(+), 252 deletions(-) diff --git a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md index 59ed3c8cb..4a0e8ca2a 100644 --- a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md @@ -1,3 +1,8 @@ +### March 19, 2026 +`1.3.1`: +- Revert `jackson-databind` dependency from 2.18.6 to 2.15.4 +- Revert `PropertyNamingStrategies.UpperCamelCaseStrategy` to `PropertyNamingStrategy.PascalCaseStrategy` + ### March 11, 2026 `1.3.0`: - Update `jackson-databind` dependency from 2.15.4 to 2.18.6 diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index 93c27d879..a71eb1f8d 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-lambda-java-serialization - 1.3.0 + 1.3.1 jar AWS Lambda Java Runtime Serialization @@ -32,7 +32,7 @@ 1.8 1.8 com.amazonaws.lambda.thirdparty - 2.18.6 + 2.15.4 2.10.1 20231013 7.3.2 diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java index 89401a91e..3f01d99b1 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java @@ -19,7 +19,6 @@ import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.util.ReflectUtil; import com.amazonaws.services.lambda.runtime.serialization.util.SerializeUtil; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.amazonaws.services.lambda.runtime.serialization.events.modules.DateModule; import com.amazonaws.services.lambda.runtime.serialization.events.modules.DateTimeModule; @@ -40,279 +39,304 @@ * * Option 1 (Preferred): * 1. Add Class name to SUPPORTED_EVENTS - * 2. Add Mixin Class to com.amazonaws.services.lambda.runtime.serialization.events.mixins package (if needed) + * 2. Add Mixin Class to + * com.amazonaws.services.lambda.runtime.serialization.events.mixins package (if + * needed) * 3. Add entries to MIXIN_MAP for event class and sub classes (if needed) - * 4. Add entries to NESTED_CLASS_MAP for event class and sub classes (if needed) - * 5. Add entry to NAMING_STRATEGY_MAP (if needed i.e. Could be used in place of a mixin) + * 4. Add entries to NESTED_CLASS_MAP for event class and sub classes (if + * needed) + * 5. Add entry to NAMING_STRATEGY_MAP (if needed i.e. Could be used in place of + * a mixin) * * Option 2 (longer - for event models that do not work with Jackson or GSON): * 1. Add Class name to SUPPORTED_EVENTS - * 2. Add serializer (using org.json) to com.amazonaws.services.lambda.runtime.serialization.events.serializers + * 2. Add serializer (using org.json) to + * com.amazonaws.services.lambda.runtime.serialization.events.serializers * 3. Add class name and serializer to SERIALIZER_MAP */ public class LambdaEventSerializers { - /** - * list of supported events - */ - private static final List SUPPORTED_EVENTS = Stream.of( - "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", - "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", - "com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", - "com.amazonaws.services.lambda.runtime.events.CloudFrontEvent", - "com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", - "com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", - "com.amazonaws.services.lambda.runtime.events.CognitoEvent", - "com.amazonaws.services.lambda.runtime.events.ConfigEvent", - "com.amazonaws.services.lambda.runtime.events.ConnectEvent", - "com.amazonaws.services.lambda.runtime.events.DynamodbEvent", - "com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", - "com.amazonaws.services.lambda.runtime.events.IoTButtonEvent", - "com.amazonaws.services.lambda.runtime.events.KinesisEvent", - "com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent", - "com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent", - "com.amazonaws.services.lambda.runtime.events.LambdaDestinationEvent", - "com.amazonaws.services.lambda.runtime.events.LexEvent", - "com.amazonaws.services.lambda.runtime.events.ScheduledEvent", - "com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent", - "com.amazonaws.services.s3.event.S3EventNotification", - "com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", - "com.amazonaws.services.lambda.runtime.events.S3Event", - "com.amazonaws.services.lambda.runtime.events.SNSEvent", - "com.amazonaws.services.lambda.runtime.events.SQSEvent") - .collect(Collectors.toList()); + /** + * list of supported events + */ + private static final List SUPPORTED_EVENTS = Stream.of( + "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", + "com.amazonaws.services.lambda.runtime.events.CloudFrontEvent", + "com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", + "com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", + "com.amazonaws.services.lambda.runtime.events.CognitoEvent", + "com.amazonaws.services.lambda.runtime.events.ConfigEvent", + "com.amazonaws.services.lambda.runtime.events.ConnectEvent", + "com.amazonaws.services.lambda.runtime.events.DynamodbEvent", + "com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", + "com.amazonaws.services.lambda.runtime.events.IoTButtonEvent", + "com.amazonaws.services.lambda.runtime.events.KinesisEvent", + "com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent", + "com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent", + "com.amazonaws.services.lambda.runtime.events.LambdaDestinationEvent", + "com.amazonaws.services.lambda.runtime.events.LexEvent", + "com.amazonaws.services.lambda.runtime.events.ScheduledEvent", + "com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent", + "com.amazonaws.services.s3.event.S3EventNotification", + "com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", + "com.amazonaws.services.lambda.runtime.events.S3Event", + "com.amazonaws.services.lambda.runtime.events.SNSEvent", + "com.amazonaws.services.lambda.runtime.events.SQSEvent") + .collect(Collectors.toList()); - /** - * list of events incompatible with Jackson, with serializers explicitly defined - * Classes are incompatible with Jackson for any of the following reasons: - * 1. different constructor/setter types from getter types - * 2. various bugs within Jackson - */ - private static final Map SERIALIZER_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.s3.event.S3EventNotification", new S3EventSerializer<>()), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", new S3EventSerializer<>()), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.S3Event", new S3EventSerializer<>())) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * list of events incompatible with Jackson, with serializers explicitly defined + * Classes are incompatible with Jackson for any of the following reasons: + * 1. different constructor/setter types from getter types + * 2. various bugs within Jackson + */ + private static final Map SERIALIZER_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.s3.event.S3EventNotification", + new S3EventSerializer<>()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", + new S3EventSerializer<>()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.S3Event", + new S3EventSerializer<>())) + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); - /** - * Maps supported event classes to mixin classes with Jackson annotations. - * Jackson annotations are not loaded through the ClassLoader so if a Java field is serialized or deserialized from a - * json field that does not match the Jave field name, then a Mixin is required. - */ - @SuppressWarnings("rawtypes") - private static final Map MIXIN_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", - CloudFormationCustomResourceEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFrontEvent", - CloudFrontEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", - CloudWatchLogsEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", - CodeCommitEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record", - CodeCommitEventMixin.RecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", - ConnectEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details", - ConnectEventMixin.DetailsMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData", - ConnectEventMixin.ContactDataMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint", - ConnectEventMixin.CustomerEndpointMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", ConnectEventMixin.QueueMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint", - ConnectEventMixin.SystemEndpointMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", - DynamodbEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", - DynamodbEventMixin.DynamodbStreamRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.StreamRecord", - DynamodbEventMixin.StreamRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - DynamodbEventMixin.StreamRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.AttributeValue", - DynamodbEventMixin.AttributeValueMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - DynamodbEventMixin.AttributeValueMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", - DynamodbTimeWindowEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", - KinesisEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record", - KinesisEventMixin.RecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent", - KinesisTimeWindowEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ScheduledEvent", - ScheduledEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent", - SecretsManagerRotationEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - SNSEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord", - SNSEventMixin.SNSRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", - SQSEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage", - SQSEventMixin.SQSMessageMixin.class)) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * Maps supported event classes to mixin classes with Jackson annotations. + * Jackson annotations are not loaded through the ClassLoader so if a Java field + * is serialized or deserialized from a + * json field that does not match the Jave field name, then a Mixin is required. + */ + @SuppressWarnings("rawtypes") + private static final Map MIXIN_MAP = Stream.of( + new SimpleEntry<>( + "com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", + CloudFormationCustomResourceEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFrontEvent", + CloudFrontEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", + CloudWatchLogsEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", + CodeCommitEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record", + CodeCommitEventMixin.RecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", + ConnectEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details", + ConnectEventMixin.DetailsMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData", + ConnectEventMixin.ContactDataMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint", + ConnectEventMixin.CustomerEndpointMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", + ConnectEventMixin.QueueMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint", + ConnectEventMixin.SystemEndpointMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", + DynamodbEventMixin.class), + new SimpleEntry<>( + "com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", + DynamodbEventMixin.DynamodbStreamRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.StreamRecord", + DynamodbEventMixin.StreamRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + DynamodbEventMixin.StreamRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.AttributeValue", + DynamodbEventMixin.AttributeValueMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + DynamodbEventMixin.AttributeValueMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", + DynamodbTimeWindowEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", + KinesisEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record", + KinesisEventMixin.RecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent", + KinesisTimeWindowEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ScheduledEvent", + ScheduledEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent", + SecretsManagerRotationEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", + SNSEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord", + SNSEventMixin.SNSRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", + SQSEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage", + SQSEventMixin.SQSMessageMixin.class)) + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); - /** - * If mixins are required for inner classes of an event, then those nested classes must be specified here. - */ - @SuppressWarnings("rawtypes") - private static final Map> NESTED_CLASS_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CognitoEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.CognitoEvent$DatasetRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", - Arrays.asList( - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - "com.amazonaws.services.dynamodbv2.model.AttributeValue"), - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - "com.amazonaws.services.dynamodbv2.model.StreamRecord"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", - Arrays.asList( - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - "com.amazonaws.services.dynamodbv2.model.AttributeValue"), - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - "com.amazonaws.services.dynamodbv2.model.StreamRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", - Arrays.asList( - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - "com.amazonaws.services.dynamodbv2.model.AttributeValue"), - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - "com.amazonaws.services.dynamodbv2.model.StreamRecord"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage")))) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * If mixins are required for inner classes of an event, then those nested + * classes must be specified here. + */ + @SuppressWarnings("rawtypes") + private static final Map> NESTED_CLASS_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CognitoEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.CognitoEvent$DatasetRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", + Arrays.asList( + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + "com.amazonaws.services.dynamodbv2.model.AttributeValue"), + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + "com.amazonaws.services.dynamodbv2.model.StreamRecord"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), + new SimpleEntry<>( + "com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", + Arrays.asList( + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + "com.amazonaws.services.dynamodbv2.model.AttributeValue"), + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + "com.amazonaws.services.dynamodbv2.model.StreamRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", + Arrays.asList( + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + "com.amazonaws.services.dynamodbv2.model.AttributeValue"), + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + "com.amazonaws.services.dynamodbv2.model.StreamRecord"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage")))) + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); - /** - * If event requires a naming strategy. For example, when someone names the getter method getSNS and the setter - * method setSns, for some magical reasons, using both mixins and a naming strategy works - */ - private static final Map NAMING_STRATEGY_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - new PropertyNamingStrategies.UpperCamelCaseStrategy()), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", - new PropertyNamingStrategies.UpperCamelCaseStrategy()) - ) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * If event requires a naming strategy. For example, when someone names the + * getter method getSNS and the setter + * method setSns, for some magical reasons, using both mixins and a naming + * strategy works + */ + private static final Map NAMING_STRATEGY_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", + new PropertyNamingStrategy.PascalCaseStrategy()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", + new PropertyNamingStrategy.PascalCaseStrategy())) + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); - /** - * Returns whether the class name is a Lambda supported event model. - * @param className class name as string - * @return whether the event model is supported - */ - public static boolean isLambdaSupportedEvent(String className) { - return SUPPORTED_EVENTS.contains(className); - } - - /** - * Return a serializer for the event class - * @return a specific PojoSerializer or modified JacksonFactory instance with mixins and modules added in - */ - @SuppressWarnings({"unchecked"}) - public static PojoSerializer serializerFor(Class eventClass, ClassLoader classLoader) { - // if serializer specifically defined for event then use that - if (SERIALIZER_MAP.containsKey(eventClass.getName())) { - return SERIALIZER_MAP.get(eventClass.getName()).withClass(eventClass).withClassLoader(classLoader); - } - // else use a Jackson ObjectMapper instance - JacksonFactory factory = JacksonFactory.getInstance(); - // if mixins required for class, then apply - if (MIXIN_MAP.containsKey(eventClass.getName())) { - factory = factory.withMixin(eventClass, MIXIN_MAP.get(eventClass.getName())); + /** + * Returns whether the class name is a Lambda supported event model. + * + * @param className class name as string + * @return whether the event model is supported + */ + public static boolean isLambdaSupportedEvent(String className) { + return SUPPORTED_EVENTS.contains(className); } - // if event model has nested classes then load those classes and check if mixins apply - if (NESTED_CLASS_MAP.containsKey(eventClass.getName())) { - List nestedClasses = NESTED_CLASS_MAP.get(eventClass.getName()); - for (NestedClass nestedClass: nestedClasses) { - // if mixin exists for nested class then apply - if (MIXIN_MAP.containsKey(nestedClass.className)) { - factory = tryLoadingNestedClass(classLoader, factory, nestedClass); + + /** + * Return a serializer for the event class + * + * @return a specific PojoSerializer or modified JacksonFactory instance with + * mixins and modules added in + */ + @SuppressWarnings({ "unchecked" }) + public static PojoSerializer serializerFor(Class eventClass, ClassLoader classLoader) { + // if serializer specifically defined for event then use that + if (SERIALIZER_MAP.containsKey(eventClass.getName())) { + return SERIALIZER_MAP.get(eventClass.getName()).withClass(eventClass) + .withClassLoader(classLoader); } - } - } - // load DateModules - factory.getMapper().registerModules(new DateModule(), new DateTimeModule(classLoader)); - // load naming strategy if needed - if (NAMING_STRATEGY_MAP.containsKey(eventClass.getName())) { - factory = factory.withNamingStrategy(NAMING_STRATEGY_MAP.get(eventClass.getName())); + // else use a Jackson ObjectMapper instance + JacksonFactory factory = JacksonFactory.getInstance(); + // if mixins required for class, then apply + if (MIXIN_MAP.containsKey(eventClass.getName())) { + factory = factory.withMixin(eventClass, MIXIN_MAP.get(eventClass.getName())); + } + // if event model has nested classes then load those classes and check if mixins + // apply + if (NESTED_CLASS_MAP.containsKey(eventClass.getName())) { + List nestedClasses = NESTED_CLASS_MAP.get(eventClass.getName()); + for (NestedClass nestedClass : nestedClasses) { + // if mixin exists for nested class then apply + if (MIXIN_MAP.containsKey(nestedClass.className)) { + factory = tryLoadingNestedClass(classLoader, factory, nestedClass); + } + } + } + // load DateModules + factory.getMapper().registerModules(new DateModule(), new DateTimeModule(classLoader)); + // load naming strategy if needed + if (NAMING_STRATEGY_MAP.containsKey(eventClass.getName())) { + factory = factory.withNamingStrategy(NAMING_STRATEGY_MAP.get(eventClass.getName())); + } + return factory.getSerializer(eventClass); } - return factory.getSerializer(eventClass); - } - /** - * Tries to load a nested class with its defined mixin from {@link #MIXIN_MAP} into the {@link JacksonFactory} object. - * Will allow initial failure for {@link AlternateNestedClass} objects and try again with their alternate class name - * @return a modified JacksonFactory instance with mixins added in - */ - private static JacksonFactory tryLoadingNestedClass(ClassLoader classLoader, JacksonFactory factory, NestedClass nestedClass) { - Class eventClazz; - Class mixinClazz; - try { - eventClazz = SerializeUtil.loadCustomerClass(nestedClass.getClassName(), classLoader); - mixinClazz = MIXIN_MAP.get(nestedClass.getClassName()); - } catch (ReflectUtil.ReflectException e) { - if (nestedClass instanceof AlternateNestedClass) { - AlternateNestedClass alternateNestedClass = (AlternateNestedClass) nestedClass; - eventClazz = SerializeUtil.loadCustomerClass(alternateNestedClass.getAlternateClassName(), classLoader); - mixinClazz = MIXIN_MAP.get(alternateNestedClass.getAlternateClassName()); - } else { - throw e; - } - } + /** + * Tries to load a nested class with its defined mixin from {@link #MIXIN_MAP} + * into the {@link JacksonFactory} object. + * Will allow initial failure for {@link AlternateNestedClass} objects and try + * again with their alternate class name + * + * @return a modified JacksonFactory instance with mixins added in + */ + private static JacksonFactory tryLoadingNestedClass(ClassLoader classLoader, JacksonFactory factory, + NestedClass nestedClass) { + Class eventClazz; + Class mixinClazz; + try { + eventClazz = SerializeUtil.loadCustomerClass(nestedClass.getClassName(), classLoader); + mixinClazz = MIXIN_MAP.get(nestedClass.getClassName()); + } catch (ReflectUtil.ReflectException e) { + if (nestedClass instanceof AlternateNestedClass) { + AlternateNestedClass alternateNestedClass = (AlternateNestedClass) nestedClass; + eventClazz = SerializeUtil.loadCustomerClass( + alternateNestedClass.getAlternateClassName(), classLoader); + mixinClazz = MIXIN_MAP.get(alternateNestedClass.getAlternateClassName()); + } else { + throw e; + } + } - return factory.withMixin(eventClazz, mixinClazz); - } + return factory.withMixin(eventClazz, mixinClazz); + } - private static class NestedClass { - private final String className; + private static class NestedClass { + private final String className; - protected NestedClass(String className) { - this.className = className; - } + protected NestedClass(String className) { + this.className = className; + } - protected String getClassName() { - return className; + protected String getClassName() { + return className; + } } - } - private static class AlternateNestedClass extends NestedClass { - private final String alternateClassName; + private static class AlternateNestedClass extends NestedClass { + private final String alternateClassName; - private AlternateNestedClass(String className, String alternateClassName) { - super(className); - this.alternateClassName = alternateClassName; - } + private AlternateNestedClass(String className, String alternateClassName) { + super(className); + this.alternateClassName = alternateClassName; + } - private String getAlternateClassName() { - return alternateClassName; + private String getAlternateClassName() { + return alternateClassName; + } } - } } From c201b4dfcfe2fc663671b6fe27617d0f57d1313e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:38:28 +0000 Subject: [PATCH 62/69] Bump actions/checkout from 5 to 6 (#574) Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Davide Melfi --- .github/workflows/aws-lambda-java-core.yml | 2 +- .github/workflows/aws-lambda-java-events-sdk-transformer.yml | 2 +- .github/workflows/aws-lambda-java-events.yml | 2 +- .github/workflows/aws-lambda-java-log4j2.yml | 2 +- .github/workflows/aws-lambda-java-profiler.yml | 2 +- .github/workflows/aws-lambda-java-serialization.yml | 2 +- .github/workflows/aws-lambda-java-tests.yml | 2 +- .github/workflows/repo-sync.yml | 2 +- .github/workflows/runtime-interface-client_merge_to_main.yml | 2 +- .github/workflows/runtime-interface-client_pr.yml | 4 ++-- .github/workflows/samples.yml | 4 ++-- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml index b1bed919f..270beef8d 100644 --- a/.github/workflows/aws-lambda-java-core.yml +++ b/.github/workflows/aws-lambda-java-core.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml index 1f1f08870..d36bdfd13 100644 --- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml +++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml index 2d101018d..fc4576a9b 100644 --- a/.github/workflows/aws-lambda-java-events.yml +++ b/.github/workflows/aws-lambda-java-events.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml index e9f6a56c1..051498ca7 100644 --- a/.github/workflows/aws-lambda-java-log4j2.yml +++ b/.github/workflows/aws-lambda-java-log4j2.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index a3afe3729..472c3b57d 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -22,7 +22,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK uses: actions/setup-java@v4 diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index 13b7e08b0..fd55b22f2 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml index 363647cc4..bea7ff7d8 100644 --- a/.github/workflows/aws-lambda-java-tests.yml +++ b/.github/workflows/aws-lambda-java-tests.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml index 2d97bc868..6a918fde3 100644 --- a/.github/workflows/repo-sync.yml +++ b/.github/workflows/repo-sync.yml @@ -20,7 +20,7 @@ jobs: env: IS_CONFIGURED: ${{ secrets.SOURCE_REPO != '' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 if: ${{ env.IS_CONFIGURED == 'true' }} - uses: repo-sync/github-sync@v2 name: Sync repo to branch diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 3560207f3..73dd61f89 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -28,7 +28,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index dcad4fa0a..f243afcc6 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -18,7 +18,7 @@ jobs: smoke-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 @@ -43,7 +43,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index aebb708a7..f8a2f1dd7 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: @@ -45,7 +45,7 @@ jobs: custom-serialization: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # Set up both Java 8 and 21 - name: Set up Java 8 and 21 uses: actions/setup-java@v4 From d8ffbf1c5e13280b1503bc487816eb33827c0766 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Mon, 23 Mar 2026 22:03:09 +0000 Subject: [PATCH 63/69] Github Actions Improvements (#594) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: ๐Ÿšš Github Actions improvements * ci: dependent library test fan out * ci: add workflow_dispatch * build: upgrading junit-jupiter everywhere --------- Co-authored-by: Davide Melfi --- .github/workflows/aws-lambda-java-core.yml | 18 ++++-------------- .../aws-lambda-java-events-sdk-transformer.yml | 12 ++++++++---- .github/workflows/aws-lambda-java-events.yml | 14 ++++---------- .github/workflows/aws-lambda-java-log4j2.yml | 13 ++++++++----- .github/workflows/aws-lambda-java-profiler.yml | 1 + .../aws-lambda-java-serialization.yml | 12 ++++++++---- .github/workflows/aws-lambda-java-tests.yml | 14 ++++++-------- .../runtime-interface-client_merge_to_main.yml | 3 ++- .../workflows/runtime-interface-client_pr.yml | 16 +++++++++++----- .github/workflows/samples.yml | 17 ++++++++++++----- .gitignore | 2 +- aws-lambda-java-events-sdk-transformer/pom.xml | 3 ++- aws-lambda-java-events/pom.xml | 3 ++- .../pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- .../examples/cdk/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 3 ++- 17 files changed, 74 insertions(+), 63 deletions(-) diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml index 270beef8d..373acf757 100644 --- a/.github/workflows/aws-lambda-java-core.yml +++ b/.github/workflows/aws-lambda-java-core.yml @@ -1,9 +1,10 @@ # This workflow will be triggered if there will be changes to aws-lambda-java-core -# package and it builds the package and the packages that depend on it. +# package and it builds the package. name: Java CI aws-lambda-java-core on: + workflow_dispatch: push: branches: [ main ] paths: @@ -29,18 +30,7 @@ jobs: with: java-version: 8 distribution: corretto - - # Install base module + cache: maven + - name: Install core with Maven run: mvn -B install --file aws-lambda-java-core/pom.xml - - # Package modules that depend on base module - - name: Package log4j2 with Maven - run: mvn -B package --file aws-lambda-java-log4j2/pom.xml - - # Test Runtime Interface Client - - name: Run 'pr' target - working-directory: ./aws-lambda-java-runtime-interface-client - run: make pr - env: - IS_JAVA_8: true diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml index d36bdfd13..713f97211 100644 --- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml +++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml @@ -1,17 +1,21 @@ -# This workflow will be triggered if there will be changes to -# aws-lambda-java-events-sdk-transformer package and it builds the package. +# This workflow will be triggered if there will be changes to +# aws-lambda-java-events-sdk-transformer package or its dependency (events), +# and it builds the package. name: Java CI aws-lambda-java-events-sdk-transformer on: + workflow_dispatch: push: branches: [ main ] paths: - 'aws-lambda-java-events-sdk-transformer/**' + - 'aws-lambda-java-events/**' pull_request: branches: [ '*' ] paths: - 'aws-lambda-java-events-sdk-transformer/**' + - 'aws-lambda-java-events/**' - '.github/workflows/aws-lambda-java-events-sdk-transformer.yml' permissions: @@ -29,11 +33,11 @@ jobs: with: java-version: 8 distribution: corretto + cache: maven - # Install base module + # Install dependency - name: Install events with Maven run: mvn -B install --file aws-lambda-java-events/pom.xml # Package target module - name: Package events-sdk-transformer with Maven run: mvn -B package --file aws-lambda-java-events-sdk-transformer/pom.xml - diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml index fc4576a9b..0a8725339 100644 --- a/.github/workflows/aws-lambda-java-events.yml +++ b/.github/workflows/aws-lambda-java-events.yml @@ -1,9 +1,10 @@ # This workflow will be triggered if there will be changes to aws-lambda-java-events -# package and it builds the package and the packages that depend on it. +# package and it builds the package. name: Java CI aws-lambda-java-events on: + workflow_dispatch: push: branches: [ main ] paths: @@ -29,14 +30,7 @@ jobs: with: java-version: 8 distribution: corretto - - # Install base module + cache: maven + - name: Install events with Maven run: mvn -B install --file aws-lambda-java-events/pom.xml - - # Package modules that depend on base module - - name: Package serialization with Maven - run: mvn -B package --file aws-lambda-java-serialization/pom.xml - - name: Package events-sdk-transformer with Maven - run: mvn -B package --file aws-lambda-java-events-sdk-transformer/pom.xml - diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml index 051498ca7..315678339 100644 --- a/.github/workflows/aws-lambda-java-log4j2.yml +++ b/.github/workflows/aws-lambda-java-log4j2.yml @@ -1,17 +1,20 @@ -# This workflow will be triggered if there will be changes to -# aws-lambda-java-log4j2 package and it builds the package. +# This workflow will be triggered if there will be changes to +# aws-lambda-java-log4j2 package or its dependency (core), and it builds the package. name: Java CI aws-lambda-java-log4j2 on: push: + workflow_dispatch: branches: [ main ] paths: - 'aws-lambda-java-log4j2/**' + - 'aws-lambda-java-core/**' pull_request: branches: [ '*' ] paths: - 'aws-lambda-java-log4j2/**' + - 'aws-lambda-java-core/**' - '.github/workflows/aws-lambda-java-log4j2.yml' permissions: @@ -29,11 +32,11 @@ jobs: with: java-version: 8 distribution: corretto - - # Install base module + cache: maven + + # Install dependency - name: Install core with Maven run: mvn -B install --file aws-lambda-java-core/pom.xml # Package target module - name: Package log4j2 with Maven run: mvn -B package --file aws-lambda-java-log4j2/pom.xml - diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index 472c3b57d..1007e7124 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -29,6 +29,7 @@ jobs: with: java-version: 21 distribution: corretto + cache: maven - name: Issue AWS credentials uses: aws-actions/configure-aws-credentials@v4 diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index fd55b22f2..78d23c37b 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -1,17 +1,20 @@ -# This workflow will be triggered if there will be changes to aws-lambda-java-serialization -# package and it builds the package and the packages that depend on it. +# This workflow will be triggered if there will be changes to aws-lambda-java-serialization +# package or its dependency (events), and it builds the package. name: Java CI aws-lambda-java-serialization on: + workflow_dispatch: push: branches: [ main ] paths: - 'aws-lambda-java-serialization/**' + - 'aws-lambda-java-events/**' pull_request: branches: [ '*' ] paths: - 'aws-lambda-java-serialization/**' + - 'aws-lambda-java-events/**' - '.github/workflows/aws-lambda-java-serialization.yml' permissions: @@ -29,8 +32,9 @@ jobs: with: java-version: 8 distribution: corretto - - # Install base module + cache: maven + + # Install dependency - name: Install events with Maven run: mvn -B install --file aws-lambda-java-events/pom.xml diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml index bea7ff7d8..a52181dff 100644 --- a/.github/workflows/aws-lambda-java-tests.yml +++ b/.github/workflows/aws-lambda-java-tests.yml @@ -1,5 +1,5 @@ # This workflow will be triggered if there will be changes to aws-lambda-java-tests -# package and it builds the package and the packages that depend on it. +# package or its dependencies (events, serialization), and it builds the package. name: Java CI aws-lambda-java-tests @@ -9,16 +9,14 @@ on: branches: [ main ] paths: - 'aws-lambda-java-tests/**' - - 'aws-lambda-java-runtime-interface-client/**' - - 'aws-lambda-java-serialization/**' - 'aws-lambda-java-events/**' + - 'aws-lambda-java-serialization/**' pull_request: branches: [ '*' ] paths: - 'aws-lambda-java-tests/**' - - 'aws-lambda-java-runtime-interface-client/**' - - 'aws-lambda-java-serialization/**' - 'aws-lambda-java-events/**' + - 'aws-lambda-java-serialization/**' - '.github/workflows/aws-lambda-java-tests.yml' permissions: @@ -36,8 +34,9 @@ jobs: with: java-version: 8 distribution: corretto - - # Install base module + cache: maven + + # Install dependencies - name: Install events with Maven run: mvn -B install --file aws-lambda-java-events/pom.xml - name: Install serialization with Maven @@ -46,4 +45,3 @@ jobs: # Package target module - name: Package tests with Maven run: mvn -B package --file aws-lambda-java-tests/pom.xml - diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 73dd61f89..da2389d73 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -11,12 +11,12 @@ name: Publish artifact for aws-lambda-java-runtime-interface-client on: + workflow_dispatch: push: branches: [ main ] paths: - 'aws-lambda-java-runtime-interface-client/**' - '.github/workflows/runtime-interface-client_*.yml' - workflow_dispatch: jobs: @@ -35,6 +35,7 @@ jobs: with: java-version: 8 distribution: corretto + cache: maven - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index f243afcc6..59b1c8aed 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -1,13 +1,17 @@ -# This workflow will be triggered if there will be changes to -# aws-lambda-java-runtime-interface-client package and it builds the package. +# This workflow will be triggered if there will be changes to +# aws-lambda-java-runtime-interface-client package or its dependencies (core, serialization), +# and it builds the package. name: PR to runtime-interface-client on: + workflow_dispatch: pull_request: branches: [ '*' ] paths: - 'aws-lambda-java-runtime-interface-client/**' + - 'aws-lambda-java-core/**' + - 'aws-lambda-java-serialization/**' - '.github/workflows/runtime-interface-client_*.yml' permissions: @@ -25,7 +29,8 @@ jobs: with: java-version: 8 distribution: corretto - + cache: maven + - name: Build and install core dependency locally working-directory: ./aws-lambda-java-core run: mvn clean install @@ -39,7 +44,7 @@ jobs: run: make pr env: IS_JAVA_8: true - + build: runs-on: ubuntu-latest steps: @@ -50,6 +55,7 @@ jobs: with: java-version: 8 distribution: corretto + cache: maven - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -61,7 +67,7 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} - + - name: Build and install core dependency locally working-directory: ./aws-lambda-java-core run: mvn clean install diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index f8a2f1dd7..a76c3fb25 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -1,17 +1,24 @@ -# This workflow will be triggered if there will be changes to aws-lambda-java-core -# package and it builds the package and the packages that depend on it. +# This workflow will be triggered if there will be changes to samples +# or their dependencies (events, serialization, tests). name: Java CI samples on: + workflow_dispatch: push: branches: [ main ] paths: - 'samples/**' + - 'aws-lambda-java-events/**' + - 'aws-lambda-java-serialization/**' + - 'aws-lambda-java-tests/**' pull_request: branches: [ '*' ] paths: - 'samples/**' + - 'aws-lambda-java-events/**' + - 'aws-lambda-java-serialization/**' + - 'aws-lambda-java-tests/**' - '.github/workflows/samples.yml' permissions: @@ -27,14 +34,13 @@ jobs: with: java-version: 8 distribution: corretto + cache: maven - # Install events module + # Install dependencies - name: Install events with Maven run: mvn -B install --file aws-lambda-java-events/pom.xml - # Install serialization module - name: Install serialization with Maven run: mvn -B install --file aws-lambda-java-serialization/pom.xml - # Install tests module - name: Install tests with Maven run: mvn -B install --file aws-lambda-java-tests/pom.xml @@ -54,6 +60,7 @@ jobs: 8 21 distribution: corretto + cache: maven # Install events module using Java 8 - name: Install events with Maven diff --git a/.gitignore b/.gitignore index 5eb5456a1..b5d289a8b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,4 @@ experimental/aws-lambda-java-profiler/integration_tests/helloworld/bin /scratch/ .vscode .kiro -build \ No newline at end of file +build diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index d719ec8ac..7c72e98c4 100644 --- a/aws-lambda-java-events-sdk-transformer/pom.xml +++ b/aws-lambda-java-events-sdk-transformer/pom.xml @@ -38,6 +38,7 @@ 1.8 1.11.914 2.15.40 + 5.14.3 @@ -70,7 +71,7 @@ org.junit.jupiter junit-jupiter-engine - 5.7.0 + ${junit-jupiter.version} test diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 714c825d9..ba93601dc 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -39,6 +39,7 @@ UTF-8 2.20.1 2.40.1 + 5.14.3 @@ -58,7 +59,7 @@ org.junit.jupiter junit-jupiter-engine - 5.9.2 + ${junit-jupiter.version} test diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 65304c61a..f84ffe558 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -37,7 +37,7 @@ 0.8.12 2.4 3.1.1 - 5.9.2 + 5.14.3 3.4.0 + 5.9.2 0.8.7 @@ -250,6 +255,9 @@ org.apache.maven.plugins maven-surefire-plugin 2.22.2 + + true + diff --git a/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml b/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml index d84b2bd1e..4b46f4e2b 100644 --- a/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml +++ b/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml @@ -11,7 +11,7 @@ UTF-8 2.155.0 [10.0.0,11.0.0) - 5.14.3 + 5.12.2 diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 3bb0051ba..0db8ed83a 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -35,7 +35,9 @@ 1.8 1.8 UTF-8 - 5.14.3 + 5.12.2 + 3.5.4 + @@ -69,7 +71,10 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + ${maven-surefire-plugin.version} + + true + From 3e23521fbbae085a21610d74055cc3b8b77c2026 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Mon, 30 Mar 2026 15:43:44 +0100 Subject: [PATCH 65/69] Add serialization round-trip tests for event types (#598) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: ๐Ÿงช add roundtrip serialization test utility * test: fix false positives epoch format errors, added comment about this in the serialization package. * test: fixed false positives DateTime differences * test: fixing error in lex event fixture * test: fixing connect event * test: fixing api gateway proxy event false negative * test: fixing CloudFront and S3 event false negatives * build: adding mise to .gitignore * test: fix MSKFirehose, LexEvent, RabbitMQ, APIGatewayV2Auth and ActiveMQ serialization test fixtures * test: Add round-trip fixtures for 4 registered events * test: Add round-trip tests for 11 response event types * test: including IAM Policy Response roundtrip test * test: add test for JsonNodeUtils * docs: add tests 1.1.2 changelog entry --------- Co-authored-by: Davide Melfi --- .gitignore | 1 + aws-lambda-java-serialization/mise.toml | 2 - .../events/modules/DateModule.java | 15 +- .../lambda/runtime/tests/JsonNodeUtils.java | 110 +++++++++++++ .../runtime/tests/LambdaEventAssert.java | 147 +++++++++++++++++ .../lambda/runtime/tests/EventLoaderTest.java | 15 ++ .../runtime/tests/JsonNodeUtilsTest.java | 155 ++++++++++++++++++ .../runtime/tests/LambdaEventAssertTest.java | 71 ++++++++ ...sponseEventSerializationRoundTripTest.java | 59 +++++++ .../tests/SerializationRoundTripTest.java | 108 ++++++++++++ ...steredEventSerializationRoundTripTest.java | 90 ++++++++++ .../src/test/resources/apigw_http_event.json | 12 -- .../src/test/resources/apigw_rest_event.json | 25 +-- .../test/resources/apigw_websocket_event.json | 88 ++++++++++ .../resources/appsync_authorizer_event.json | 15 ++ .../appsync_authorizer_response.json | 11 ++ .../src/test/resources/cloudfront_event.json | 1 - ...ognito_userpool_create_auth_challenge.json | 37 +++++ .../cognito_userpool_custom_message.json | 28 ++++ ...ognito_userpool_define_auth_challenge.json | 32 ++++ .../cognito_userpool_migrate_user.json | 35 ++++ .../cognito_userpool_postauthentication.json | 20 +++ .../cognito_userpool_postconfirmation.json | 20 +++ ...cognito_userpool_pre_token_generation.json | 48 ++++++ .../cognito_userpool_preauthentication.json | 20 +++ .../cognito/cognito_userpool_presignup.json | 27 +++ ...ognito_userpool_verify_auth_challenge.json | 27 +++ .../test/resources/cognito_sync_event.json | 20 +++ .../src/test/resources/connect_event.json | 9 - .../resources/ddb/dynamo_event_roundtrip.json | 97 +++++++++++ .../ddb/dynamo_time_window_event.json | 75 +++++++++ .../src/test/resources/iot_button_event.json | 5 + .../test/resources/kafka_event_roundtrip.json | 22 +++ ...nalytics_firehose_input_preprocessing.json | 14 ++ ...nalytics_input_preprocessing_response.json | 9 + .../kinesis_analytics_output_delivery.json | 13 ++ ...is_analytics_output_delivery_response.json | 8 + ...analytics_streams_input_preprocessing.json | 17 ++ .../kinesis/kinesis_event_roundtrip.json | 21 +++ .../kinesis/kinesis_time_window_event.json | 32 ++++ .../test/resources/lex_event_roundtrip.json | 24 +++ .../src/test/resources/mq_event.json | 24 +-- .../test/resources/msk_firehose_event.json | 2 +- .../msk_firehose_event_roundtrip.json | 18 ++ .../src/test/resources/partial_pojo.json | 4 + .../resources/rabbitmq_event_roundtrip.json | 51 ++++++ .../test/resources/response/alb_response.json | 15 ++ .../response/apigw_proxy_response.json | 15 ++ .../response/apigw_v2_http_response.json | 16 ++ .../response/apigw_v2_websocket_response.json | 14 ++ .../response/msk_firehose_response.json | 9 + .../resources/response/s3_batch_response.json | 12 ++ .../response/simple_iam_policy_response.json | 7 + .../response/sqs_batch_response.json | 7 + .../src/test/resources/s3_batch_event.json | 15 ++ .../src/test/resources/s3_event.json | 2 + .../resources/s3_object_lambda_event.json | 29 ++++ .../resources/time_window_event_response.json | 10 ++ .../src/test/resources/unstable_pojo.json | 3 + 59 files changed, 1783 insertions(+), 55 deletions(-) delete mode 100644 aws-lambda-java-serialization/mise.toml create mode 100644 aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtils.java create mode 100644 aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtilsTest.java create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssertTest.java create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/ResponseEventSerializationRoundTripTest.java create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/UnregisteredEventSerializationRoundTripTest.java create mode 100644 aws-lambda-java-tests/src/test/resources/apigw_websocket_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/appsync_authorizer_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/appsync_authorizer_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_create_auth_challenge.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_custom_message.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_define_auth_challenge.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_migrate_user.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postauthentication.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postconfirmation.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_pre_token_generation.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_preauthentication.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_presignup.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_verify_auth_challenge.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito_sync_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/ddb/dynamo_event_roundtrip.json create mode 100644 aws-lambda-java-tests/src/test/resources/ddb/dynamo_time_window_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/iot_button_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/kafka_event_roundtrip.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_firehose_input_preprocessing.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_input_preprocessing_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_streams_input_preprocessing.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_event_roundtrip.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_time_window_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/lex_event_roundtrip.json create mode 100644 aws-lambda-java-tests/src/test/resources/msk_firehose_event_roundtrip.json create mode 100644 aws-lambda-java-tests/src/test/resources/partial_pojo.json create mode 100644 aws-lambda-java-tests/src/test/resources/rabbitmq_event_roundtrip.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/alb_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/apigw_proxy_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/apigw_v2_http_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/apigw_v2_websocket_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/msk_firehose_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/s3_batch_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/simple_iam_policy_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/sqs_batch_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/s3_batch_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/s3_object_lambda_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/time_window_event_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/unstable_pojo.json diff --git a/.gitignore b/.gitignore index b5d289a8b..5a277e5d6 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ experimental/aws-lambda-java-profiler/integration_tests/helloworld/bin .vscode .kiro build +mise.toml diff --git a/aws-lambda-java-serialization/mise.toml b/aws-lambda-java-serialization/mise.toml deleted file mode 100644 index 854b93b56..000000000 --- a/aws-lambda-java-serialization/mise.toml +++ /dev/null @@ -1,2 +0,0 @@ -[tools] -java = "corretto-8" diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateModule.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateModule.java index 8a6954e34..acc8bde2a 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateModule.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateModule.java @@ -15,10 +15,17 @@ import com.fasterxml.jackson.databind.module.SimpleModule; /** - * The AWS API represents a date as a double, which specifies the fractional - * number of seconds since the epoch. Java's Date, however, represents a date as - * a long, which specifies the number of milliseconds since the epoch. This - * class is used to translate between these two formats. + * The AWS API represents a date as a double (fractional seconds since epoch). + * Java's Date uses a long (milliseconds since epoch). This module translates + * between the two formats. + * + *

+ * Round-trip caveats: The serializer always writes via + * {@link JsonGenerator#writeNumber(double)}, so integer epochs + * (e.g. {@code 1428537600}) round-trip as decimal ({@code 1.4285376E9}). + * Sub-millisecond precision is lost because {@link java.util.Date} + * has milliseconds precision. + *

* * This class is copied from LambdaEventBridgeservice * com.amazon.aws.lambda.stream.ddb.DateModule diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtils.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtils.java new file mode 100644 index 000000000..f9f4e1eb6 --- /dev/null +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtils.java @@ -0,0 +1,110 @@ +package com.amazonaws.services.lambda.runtime.tests; + +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonNode; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.Iterator; +import java.util.List; +import java.util.TreeSet; +import java.util.regex.Pattern; + +import org.joda.time.DateTime; + +/** + * Utility methods for working with shaded Jackson {@link JsonNode} trees. + * + *

+ * Package-private โ€” not part of the public API. + *

+ */ +class JsonNodeUtils { + + private static final Pattern ISO_DATE_REGEX = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T.+"); + + private JsonNodeUtils() { + } + + /** + * Recursively removes all fields whose value is {@code null} from the + * tree. This mirrors the serializer's {@code Include.NON_NULL} behaviour + * so that explicit nulls in the fixture don't cause false-positive diffs. + */ + static JsonNode stripNulls(JsonNode node) { + if (node.isObject()) { + ObjectNode obj = (ObjectNode) node; + Iterator fieldNames = obj.fieldNames(); + while (fieldNames.hasNext()) { + String field = fieldNames.next(); + if (obj.get(field).isNull()) { + fieldNames.remove(); + } else { + stripNulls(obj.get(field)); + } + } + } else if (node.isArray()) { + for (JsonNode element : node) { + stripNulls(element); + } + } + return node; + } + + /** + * Recursively walks both trees and collects human-readable diff lines. + */ + static void diffNodes(String path, JsonNode expected, JsonNode actual, List diffs) { + if (expected.equals(actual)) + return; + + // Compares two datetime strings by parsed instant, because DateTimeModule + // normalizes the format on serialization (e.g. "+0000" โ†’ "Z", "Z" โ†’ ".000Z") + if (areSameDateTime(expected.textValue(), actual.textValue())) { + return; + } + + if (expected.isObject() && actual.isObject()) { + TreeSet allKeys = new TreeSet<>(); + expected.fieldNames().forEachRemaining(allKeys::add); + actual.fieldNames().forEachRemaining(allKeys::add); + for (String key : allKeys) { + diffChild(path + "." + key, expected.get(key), actual.get(key), diffs); + } + } else if (expected.isArray() && actual.isArray()) { + for (int i = 0; i < Math.max(expected.size(), actual.size()); i++) { + diffChild(path + "[" + i + "]", expected.get(i), actual.get(i), diffs); + } + } else { + diffs.add("CHANGED " + path + " : " + summarize(expected) + " -> " + summarize(actual)); + } + } + + /** + * Compares two strings by parsed instant when both look like ISO-8601 dates, + * because DateTimeModule normalizes format on serialization + * (e.g. "+0000" โ†’ "Z", "Z" โ†’ ".000Z"). + */ + private static boolean areSameDateTime(String expected, String actual) { + if (expected == null || actual == null + || !ISO_DATE_REGEX.matcher(expected).matches() + || !ISO_DATE_REGEX.matcher(actual).matches()) { + return false; + } + return DateTime.parse(expected).equals(DateTime.parse(actual)); + } + + private static void diffChild(String path, JsonNode expected, JsonNode actual, List diffs) { + if (expected == null) + diffs.add("ADDED " + path + " = " + summarize(actual)); + else if (actual == null) + diffs.add("MISSING " + path + " (was " + summarize(expected) + ")"); + else + diffNodes(path, expected, actual, diffs); + } + + private static String summarize(JsonNode node) { + if (node == null) { + return ""; + } + String text = node.toString(); + return text.length() > 80 ? text.substring(0, 77) + "..." : text; + } +} diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java new file mode 100644 index 000000000..e189b5e2b --- /dev/null +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java @@ -0,0 +1,147 @@ +package com.amazonaws.services.lambda.runtime.tests; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; +import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonNode; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.ArrayList; +import java.util.List; + +/** + * Framework-agnostic assertion utilities for verifying Lambda event + * serialization. + * + *

+ * When opentest4j is on the classpath (e.g. JUnit 5.x / JUnit Platform), + * assertion failures are reported as + * {@code org.opentest4j.AssertionFailedError} + * which enables rich diff support in IDEs. Otherwise, falls back to plain + * {@link AssertionError}. + *

+ * + *

+ * This class is intentionally package-private to support updates to + * the aws-lambda-java-events and aws-lambda-java-serialization packages. + * Consider making it public if there's a real request for it. + *

+ */ +class LambdaEventAssert { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + /** + * Round-trip using the registered {@link LambdaEventSerializers} path + * (Jackson + mixins + DateModule + DateTimeModule + naming strategies). + * + *

+ * The check performs two consecutive round-trips + * (JSON → POJO → JSON → POJO → JSON) and compares the + * original JSON tree against the final output tree. A single structural + * comparison catches both: + *

+ *
    + *
  • Fields silently dropped during deserialization
  • + *
  • Non-idempotent serialization (output changes across round-trips)
  • + *
+ * + * @param fileName classpath resource name (must end with {@code .json}) + * @param targetClass the event class to deserialize into + * @throws AssertionError if the original and final JSON trees differ + */ + public static void assertSerializationRoundTrip(String fileName, Class targetClass) { + PojoSerializer serializer = LambdaEventSerializers.serializerFor(targetClass, + ClassLoader.getSystemClassLoader()); + + if (!fileName.endsWith(".json")) { + throw new IllegalArgumentException("File " + fileName + " must have json extension"); + } + + byte[] originalBytes; + try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)) { + if (stream == null) { + throw new IllegalArgumentException("Could not load resource '" + fileName + "' from classpath"); + } + originalBytes = toBytes(stream); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read resource " + fileName, e); + } + + // Two round-trips: original โ†’ POJO โ†’ JSON โ†’ POJO โ†’ JSON + // We are doing 2 passes so we can check instability problems + // like UnstablePojo in LambdaEventAssertTest + ByteArrayOutputStream firstOutput = roundTrip(new ByteArrayInputStream(originalBytes), serializer); + ByteArrayOutputStream secondOutput = roundTrip( + new ByteArrayInputStream(firstOutput.toByteArray()), serializer); + + // Compare original tree against final tree. + // Strip explicit nulls from the original because the serializer is + // configured with Include.NON_NULL โ€” null fields are intentionally + // omitted and that is not a data-loss bug. + try { + JsonNode originalTree = JsonNodeUtils.stripNulls(MAPPER.readTree(originalBytes)); + JsonNode finalTree = MAPPER.readTree(secondOutput.toByteArray()); + + if (!originalTree.equals(finalTree)) { + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", originalTree, finalTree, diffs); + + if (!diffs.isEmpty()) { + StringBuilder msg = new StringBuilder(); + msg.append("Serialization round-trip failure for ") + .append(targetClass.getSimpleName()) + .append(" (").append(diffs.size()).append(" difference(s)):\n"); + for (String diff : diffs) { + msg.append(" ").append(diff).append('\n'); + } + + String expected = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(originalTree); + String actual = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(finalTree); + throw buildAssertionError(msg.toString(), expected, actual); + } + } + } catch (IOException e) { + throw new UncheckedIOException("Failed to parse JSON for tree comparison", e); + } + } + + private static ByteArrayOutputStream roundTrip(InputStream stream, PojoSerializer serializer) { + T event = serializer.fromJson(stream); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + serializer.toJson(event, outputStream); + return outputStream; + } + + private static byte[] toBytes(InputStream stream) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] chunk = new byte[4096]; + int n; + while ((n = stream.read(chunk)) != -1) { + buffer.write(chunk, 0, n); + } + return buffer.toByteArray(); + } + + /** + * Tries to create an opentest4j AssertionFailedError for rich IDE diff + * support. Falls back to plain AssertionError if opentest4j is not on + * the classpath. + */ + private static AssertionError buildAssertionError(String message, String expected, String actual) { + try { + // opentest4j is provided by JUnit Platform (5.x) and enables + // IDE diff viewers to show expected vs actual side-by-side. + Class cls = Class.forName("org.opentest4j.AssertionFailedError"); + return (AssertionError) cls + .getConstructor(String.class, Object.class, Object.class) + .newInstance(message, expected, actual); + } catch (ReflectiveOperationException e) { + return new AssertionError(message + "\nExpected:\n" + expected + "\nActual:\n" + actual); + } + } +} diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java index 752b84e27..43030bbca 100644 --- a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java @@ -81,6 +81,12 @@ public void testLoadAPIGatewayV2CustomAuthorizerEvent() { assertThat(event).isNotNull(); assertThat(event.getRequestContext().getHttp().getMethod()).isEqualTo("POST"); + // getTime() converts the raw string "12/Mar/2020:19:03:58 +0000" into a DateTime object; + // Jackson then serializes it as ISO-8601 "2020-03-12T19:03:58.000Z" + assertThat(event.getRequestContext().getTime().toInstant().getMillis()) + .isEqualTo(DateTime.parse("2020-03-12T19:03:58.000Z").toInstant().getMillis()); + // getTimeEpoch() converts the raw long into an Instant; + // Jackson then serializes it as a decimal seconds value assertThat(event.getRequestContext().getTimeEpoch()).isEqualTo(Instant.ofEpochMilli(1583348638390L)); } @@ -136,6 +142,9 @@ public void testLoadLexEvent() { assertThat(event.getCurrentIntent().getName()).isEqualTo("BookHotel"); assertThat(event.getCurrentIntent().getSlots()).hasSize(4); assertThat(event.getBot().getName()).isEqualTo("BookTrip"); + // Jackson leniently coerces the JSON number for "Nights" into a String + // because slots is typed as Map + assertThat(event.getCurrentIntent().getSlots().get("Nights")).isInstanceOf(String.class); } @Test @@ -159,6 +168,10 @@ public void testLoadMSKFirehoseEvent() { assertThat(event.getRecords().get(0).getKafkaRecordValue().array()).asString().isEqualTo("{\"Name\":\"Hello World\"}"); assertThat(event.getRecords().get(0).getApproximateArrivalTimestamp()).asString().isEqualTo("1716369573887"); assertThat(event.getRecords().get(0).getMskRecordMetadata()).asString().isEqualTo("{offset=0, partitionId=1, approximateArrivalTimestamp=1716369573887}"); + // Jackson leniently coerces the JSON number in mskRecordMetadata into a String + // because the map is typed as Map + Map metadata = event.getRecords().get(0).getMskRecordMetadata(); + assertThat(metadata.get("approximateArrivalTimestamp")).isInstanceOf(String.class); } @Test @@ -408,6 +421,8 @@ public void testLoadRabbitMQEvent() { .returns("AIDACKCEVSQ6C2EXAMPLE", from(RabbitMQEvent.BasicProperties::getUserId)) .returns(80, from(RabbitMQEvent.BasicProperties::getBodySize)) .returns("Jan 1, 1970, 12:33:41 AM", from(RabbitMQEvent.BasicProperties::getTimestamp)); + // Jackson leniently coerces the JSON string "60000" for expiration into int + // because the model field is typed as int Map headers = basicProperties.getHeaders(); assertThat(headers).hasSize(3); diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtilsTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtilsTest.java new file mode 100644 index 000000000..ec5798dca --- /dev/null +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtilsTest.java @@ -0,0 +1,155 @@ +/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ +package com.amazonaws.services.lambda.runtime.tests; + +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonNode; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class JsonNodeUtilsTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + // --- stripNulls --- + + @Test + void stripNulls_removesTopLevelNulls() throws Exception { + JsonNode node = MAPPER.readTree("{\"a\":1,\"b\":null,\"c\":\"hello\"}"); + JsonNode result = JsonNodeUtils.stripNulls(node); + assertEquals(MAPPER.readTree("{\"a\":1,\"c\":\"hello\"}"), result); + } + + @Test + void stripNulls_removesNestedNulls() throws Exception { + JsonNode node = MAPPER.readTree("{\"outer\":{\"keep\":true,\"drop\":null}}"); + JsonNode result = JsonNodeUtils.stripNulls(node); + assertEquals(MAPPER.readTree("{\"outer\":{\"keep\":true}}"), result); + } + + @Test + void stripNulls_leavesArrayElementsIntact() throws Exception { + // Nulls inside arrays are kept (they're positional) + JsonNode node = MAPPER.readTree("{\"arr\":[1,null,3]}"); + JsonNode result = JsonNodeUtils.stripNulls(node); + assertEquals(MAPPER.readTree("{\"arr\":[1,null,3]}"), result); + } + + @Test + void stripNulls_removesNullsInsideArrayObjects() throws Exception { + JsonNode node = MAPPER.readTree("[{\"a\":1,\"b\":null},{\"c\":null}]"); + JsonNode result = JsonNodeUtils.stripNulls(node); + assertEquals(MAPPER.readTree("[{\"a\":1},{}]"), result); + } + + @Test + void stripNulls_noOpOnCleanTree() throws Exception { + JsonNode node = MAPPER.readTree("{\"a\":1,\"b\":\"two\"}"); + JsonNode result = JsonNodeUtils.stripNulls(node); + assertEquals(MAPPER.readTree("{\"a\":1,\"b\":\"two\"}"), result); + } + + // --- diffNodes --- + + @Test + void diffNodes_identicalTrees_noDiffs() throws Exception { + JsonNode a = MAPPER.readTree("{\"x\":1,\"y\":\"hello\"}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", a, a.deepCopy(), diffs); + assertTrue(diffs.isEmpty()); + } + + @Test + void diffNodes_changedValue() throws Exception { + JsonNode expected = MAPPER.readTree("{\"x\":1}"); + JsonNode actual = MAPPER.readTree("{\"x\":2}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + assertTrue(diffs.get(0).startsWith("CHANGED .x")); + } + + @Test + void diffNodes_missingField() throws Exception { + JsonNode expected = MAPPER.readTree("{\"a\":1,\"b\":2}"); + JsonNode actual = MAPPER.readTree("{\"a\":1}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + assertTrue(diffs.get(0).contains("MISSING") && diffs.get(0).contains(".b"), "got: " + diffs.get(0)); + } + + @Test + void diffNodes_addedField() throws Exception { + JsonNode expected = MAPPER.readTree("{\"a\":1}"); + JsonNode actual = MAPPER.readTree("{\"a\":1,\"b\":2}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size(), "diffs: " + diffs); + assertTrue(diffs.get(0).contains("ADDED") && diffs.get(0).contains(".b"), "got: " + diffs.get(0)); + } + + @Test + void diffNodes_nestedObjectDiff() throws Exception { + JsonNode expected = MAPPER.readTree("{\"outer\":{\"inner\":\"old\"}}"); + JsonNode actual = MAPPER.readTree("{\"outer\":{\"inner\":\"new\"}}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + assertTrue(diffs.get(0).contains(".outer.inner")); + } + + @Test + void diffNodes_arrayElementDiff() throws Exception { + JsonNode expected = MAPPER.readTree("{\"arr\":[1,2,3]}"); + JsonNode actual = MAPPER.readTree("{\"arr\":[1,99,3]}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + assertTrue(diffs.get(0).contains("[1]")); + } + + @Test + void diffNodes_arrayLengthMismatch() throws Exception { + JsonNode expected = MAPPER.readTree("{\"arr\":[1,2]}"); + JsonNode actual = MAPPER.readTree("{\"arr\":[1,2,3]}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + assertTrue(diffs.get(0).contains("ADDED"), "got: " + diffs.get(0)); + } + + // --- areSameDateTime (tested indirectly via diffNodes) --- + + @Test + void diffNodes_equivalentDateTimes_noDiff() throws Exception { + // "+0000" vs "Z" โ€” same instant, different format + JsonNode expected = MAPPER.readTree("{\"t\":\"2020-03-12T19:03:58.000+0000\"}"); + JsonNode actual = MAPPER.readTree("{\"t\":\"2020-03-12T19:03:58.000Z\"}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertTrue(diffs.isEmpty(), "Expected no diffs for equivalent datetimes, got: " + diffs); + } + + @Test + void diffNodes_differentDateTimes_hasDiff() throws Exception { + JsonNode expected = MAPPER.readTree("{\"t\":\"2020-03-12T19:03:58.000Z\"}"); + JsonNode actual = MAPPER.readTree("{\"t\":\"2021-01-01T00:00:00.000Z\"}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + } + + @Test + void diffNodes_nonDateStrings_notTreatedAsDates() throws Exception { + JsonNode expected = MAPPER.readTree("{\"s\":\"hello\"}"); + JsonNode actual = MAPPER.readTree("{\"s\":\"world\"}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + assertTrue(diffs.get(0).startsWith("CHANGED .s")); + } +} diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssertTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssertTest.java new file mode 100644 index 000000000..63067f8b2 --- /dev/null +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssertTest.java @@ -0,0 +1,71 @@ +package com.amazonaws.services.lambda.runtime.tests; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class LambdaEventAssertTest { + /** + * Demonstrates the completeness check: the fixture has a field + * ({@code unknownField}) that {@code PartialPojo} does not capture, + * so it gets silently dropped during deserialization. + */ + @Test + void shouldFailWhenFieldIsDropped() { + AssertionError error = assertThrows(AssertionError.class, + () -> LambdaEventAssert.assertSerializationRoundTrip( + "partial_pojo.json", PartialPojo.class)); + + assertTrue(error.getMessage().contains("PartialPojo"), + "Error message should name the failing class"); + } + + /** + * Demonstrates the stability check: the getter mutates state on each + * call, so the first and second round-trips produce different JSON. + */ + @Test + void shouldFailWhenSerializationIsUnstable() { + AssertionError error = assertThrows(AssertionError.class, + () -> LambdaEventAssert.assertSerializationRoundTrip( + "unstable_pojo.json", UnstablePojo.class)); + + assertTrue(error.getMessage().contains("UnstablePojo"), + "Error message should name the failing class"); + } + + /** POJO that only captures {@code name}, silently dropping any other fields. */ + public static class PartialPojo { + private String name; + + public PartialPojo() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + /** + * POJO with a getter that appends a suffix, making serialization + * non-idempotent. + */ + public static class UnstablePojo { + private String name; + + public UnstablePojo() { + } + + public String getName() { + return name == null ? null : name + "_x"; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/ResponseEventSerializationRoundTripTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/ResponseEventSerializationRoundTripTest.java new file mode 100644 index 000000000..e700b0d01 --- /dev/null +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/ResponseEventSerializationRoundTripTest.java @@ -0,0 +1,59 @@ +/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ +package com.amazonaws.services.lambda.runtime.tests; + +import com.amazonaws.services.lambda.runtime.events.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +/** + * Verifies serialization round-trip fidelity for Lambda response event types. + * + *

Response events are POJOs that Lambda functions return to the RIC, which + * serializes them to JSON via {@code EventHandlerLoader.getSerializer()}. + * None of these types are registered in {@code SUPPORTED_EVENTS}, so they + * go through the bare Jackson path ({@code JacksonFactory.getInstance() + * .getSerializer(type)}).

+ * + *

Although the RIC only calls {@code toJson()} on response types (never + * {@code fromJson()}), the round-trip test is a stricter check: if a response + * type survives JSON → POJO → JSON → POJO → JSON, it + * certainly survives the production POJO → JSON path.

+ * + * @see SerializationRoundTripTest for registered input events + * @see UnregisteredEventSerializationRoundTripTest for unregistered input events + */ +@SuppressWarnings("deprecation") // APIGatewayV2ProxyResponseEvent is deprecated +public class ResponseEventSerializationRoundTripTest { + + @ParameterizedTest(name = "{0}") + @MethodSource("passingCases") + void roundTrip(String displayName, String fixture, Class eventClass) { + LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass); + } + + private static Stream passingCases() { + return Stream.of( + // API Gateway responses + args(APIGatewayProxyResponseEvent.class, "response/apigw_proxy_response.json"), + args(APIGatewayV2HTTPResponse.class, "response/apigw_v2_http_response.json"), + args(APIGatewayV2WebSocketResponse.class, "response/apigw_v2_websocket_response.json"), + args(APIGatewayV2ProxyResponseEvent.class, "response/apigw_v2_websocket_response.json"), + // ALB response + args(ApplicationLoadBalancerResponseEvent.class, "response/alb_response.json"), + // S3 Batch response + args(S3BatchResponse.class, "response/s3_batch_response.json"), + // SQS Batch response + args(SQSBatchResponse.class, "response/sqs_batch_response.json"), + // Simple IAM Policy response (HTTP API) + args(SimpleIAMPolicyResponse.class, "response/simple_iam_policy_response.json"), + // MSK Firehose response + args(MSKFirehoseResponse.class, "response/msk_firehose_response.json")); + } + + private static Arguments args(Class clazz, String fixture) { + return Arguments.of(clazz.getSimpleName(), fixture, clazz); + } +} diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java new file mode 100644 index 000000000..3eab8fec0 --- /dev/null +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java @@ -0,0 +1,108 @@ +/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ +package com.amazonaws.services.lambda.runtime.tests; + +import com.amazonaws.services.lambda.runtime.events.*; +import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Verifies serialization round-trip fidelity for events that are registered in + * {@code LambdaEventSerializers.SUPPORTED_EVENTS}. + * + *

Registered events go through the full customized serialization path in the + * Runtime Interface Client (RIC): {@code EventHandlerLoader.getSerializer()} + * detects them via {@code isLambdaSupportedEvent()} and delegates to + * {@code LambdaEventSerializers.serializerFor()}, which applies Jackson mixins, + * {@code DateModule}/{@code DateTimeModule}, and naming strategies.

+ * + *

Each case feeds a JSON fixture through + * {@link LambdaEventAssert#assertSerializationRoundTrip} which performs two + * consecutive round-trips and compares the original JSON tree against the + * final output.

+ * + * @see UnregisteredEventSerializationRoundTripTest for events not in SUPPORTED_EVENTS + */ +public class SerializationRoundTripTest { + + @ParameterizedTest(name = "{0}") + @MethodSource("passingCases") + void roundTrip(String displayName, String fixture, Class eventClass) { + LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass); + } + + @ParameterizedTest(name = "{0} (known failure)") + @MethodSource("knownFailureCases") + void roundTripKnownFailures(String displayName, String fixture, Class eventClass) { + assertThrows(Throwable.class, + () -> LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass), + displayName + " was expected to fail but passed โ€” move it to passingCases()"); + } + + private static Stream passingCases() { + return Stream.of( + args(CloudFormationCustomResourceEvent.class, "cloudformation_event.json"), + args(CloudWatchLogsEvent.class, "cloudwatchlogs_event.json"), + args(CodeCommitEvent.class, "codecommit_event.json"), + args(ConfigEvent.class, "config_event.json"), + args(DynamodbEvent.class, "ddb/dynamo_event_roundtrip.json"), + args(KinesisEvent.class, "kinesis/kinesis_event_roundtrip.json"), + args(KinesisFirehoseEvent.class, "firehose_event.json"), + args(LambdaDestinationEvent.class, "lambda_destination_event.json"), + args(ScheduledEvent.class, "cloudwatch_event.json"), + args(SecretsManagerRotationEvent.class, "secrets_rotation_event.json"), + args(SNSEvent.class, "sns_event.json"), + args(LexEvent.class, "lex_event_roundtrip.json"), + args(ConnectEvent.class, "connect_event.json"), + args(SQSEvent.class, "sqs/sqs_event_nobody.json"), + args(APIGatewayProxyRequestEvent.class, "apigw_rest_event.json"), + args(CloudFrontEvent.class, "cloudfront_event.json"), + args(S3Event.class, "s3_event.json"), + args(S3EventNotification.class, "s3_event.json"), + args(APIGatewayV2HTTPEvent.class, "apigw_http_event.json"), + args(APIGatewayCustomAuthorizerEvent.class, "apigw_auth.json"), + args(ApplicationLoadBalancerRequestEvent.class, "elb_event.json"), + args(CloudWatchCompositeAlarmEvent.class, "cloudwatch_composite_alarm.json"), + args(CloudWatchMetricAlarmEvent.class, "cloudwatch_metric_alarm.json"), + args(CognitoUserPoolPreTokenGenerationEventV2.class, "cognito_user_pool_pre_token_generation_event_v2.json"), + args(KafkaEvent.class, "kafka_event_roundtrip.json"), + args(MSKFirehoseEvent.class, "msk_firehose_event_roundtrip.json"), + args(RabbitMQEvent.class, "rabbitmq_event_roundtrip.json"), + args(S3BatchEventV2.class, "s3_batch_event_v2.json"), + args(IoTButtonEvent.class, "iot_button_event.json"), + args(CognitoEvent.class, "cognito_sync_event.json"), + args(DynamodbTimeWindowEvent.class, "ddb/dynamo_time_window_event.json"), + args(KinesisTimeWindowEvent.class, "kinesis/kinesis_time_window_event.json")); + } + + private static Stream knownFailureCases() { + return Stream.of( + // APIGatewayV2CustomAuthorizerEvent has two serialization issues: + // 1. getTime() parses the raw string "12/Mar/2020:19:03:58 +0000" into a + // DateTime via dd/MMM/yyyy formatter. Jackson serializes as ISO-8601, but + // the formatter cannot parse ISO-8601 back on the second round-trip. + // The time field is effectively mandatory (getTime() throws NPE if null), + // and the date format change is inherent to how the serialization works. + // 2. getTimeEpoch() converts long to Instant; Jackson serializes as decimal + // seconds (e.g. 1583348638.390000000) instead of the original long. + // Both transformations are lossy; coercion captured in EventLoaderTest. + args(APIGatewayV2CustomAuthorizerEvent.class, "apigw_auth_v2.json"), + // ActiveMQEvent has one serialization issue: + // Destination.physicalName (camelCase) vs JSON "physicalname" (lowercase) โ€” + // ACCEPT_CASE_INSENSITIVE_PROPERTIES is disabled in JacksonFactory so the + // field is silently dropped during deserialization. + // Fix: create an ActiveMQEventMixin with a DestinationMixin that maps + // @JsonProperty("physicalname") to getPhysicalName()/setPhysicalName(), + // then register it in LambdaEventSerializers MIXIN_MAP and NESTED_CLASS_MAP. + args(ActiveMQEvent.class, "mq_event.json")); + } + + private static Arguments args(Class clazz, String fixture) { + return Arguments.of(clazz.getSimpleName(), fixture, clazz); + } +} diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/UnregisteredEventSerializationRoundTripTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/UnregisteredEventSerializationRoundTripTest.java new file mode 100644 index 000000000..12ad818e4 --- /dev/null +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/UnregisteredEventSerializationRoundTripTest.java @@ -0,0 +1,90 @@ +/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ +package com.amazonaws.services.lambda.runtime.tests; + +import com.amazonaws.services.lambda.runtime.events.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Verifies serialization round-trip fidelity for events that are NOT registered + * in {@code LambdaEventSerializers.SUPPORTED_EVENTS}. + * + *

In the Runtime Interface Client (RIC), when a handler's event type is not + * in {@code SUPPORTED_EVENTS}, {@code EventHandlerLoader.getSerializer()} falls + * through to {@code JacksonFactory.getInstance().getSerializer(type)} โ€” a bare + * {@code PojoSerializer} backed by Jackson without any mixins or naming + * strategies. However, {@code LambdaEventSerializers.serializerFor()} (used by + * this test) unconditionally registers {@code DateModule} and + * {@code DateTimeModule}, so Joda/java.time types are still handled. For most + * unregistered events this makes no practical difference because they don't + * contain Joda DateTime fields.

+ * + * @see SerializationRoundTripTest for events registered in SUPPORTED_EVENTS + */ +@SuppressWarnings("deprecation") // APIGatewayV2ProxyRequestEvent is deprecated +public class UnregisteredEventSerializationRoundTripTest { + + @ParameterizedTest(name = "{0}") + @MethodSource("passingCases") + void roundTrip(String displayName, String fixture, Class eventClass) { + LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass); + } + + @ParameterizedTest(name = "{0} (known failure)") + @MethodSource("knownFailureCases") + void roundTripKnownFailures(String displayName, String fixture, Class eventClass) { + assertThrows(Throwable.class, + () -> LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass), + displayName + " was expected to fail but passed โ€” move it to passingCases()"); + } + + private static Stream passingCases() { + return Stream.of( + // S3 Batch + args(S3BatchEvent.class, "s3_batch_event.json"), + // AppSync + args(AppSyncLambdaAuthorizerEvent.class, "appsync_authorizer_event.json"), + args(AppSyncLambdaAuthorizerResponse.class, "appsync_authorizer_response.json"), + // TimeWindow response + args(TimeWindowEventResponse.class, "time_window_event_response.json"), + // Cognito UserPool triggers + args(CognitoUserPoolPreSignUpEvent.class, "cognito/cognito_userpool_presignup.json"), + args(CognitoUserPoolPostConfirmationEvent.class, "cognito/cognito_userpool_postconfirmation.json"), + args(CognitoUserPoolPreAuthenticationEvent.class, "cognito/cognito_userpool_preauthentication.json"), + args(CognitoUserPoolPostAuthenticationEvent.class, "cognito/cognito_userpool_postauthentication.json"), + args(CognitoUserPoolDefineAuthChallengeEvent.class, "cognito/cognito_userpool_define_auth_challenge.json"), + args(CognitoUserPoolCreateAuthChallengeEvent.class, "cognito/cognito_userpool_create_auth_challenge.json"), + args(CognitoUserPoolVerifyAuthChallengeResponseEvent.class, "cognito/cognito_userpool_verify_auth_challenge.json"), + args(CognitoUserPoolMigrateUserEvent.class, "cognito/cognito_userpool_migrate_user.json"), + args(CognitoUserPoolCustomMessageEvent.class, "cognito/cognito_userpool_custom_message.json"), + args(CognitoUserPoolPreTokenGenerationEvent.class, "cognito/cognito_userpool_pre_token_generation.json"), + // Kinesis Analytics + args(KinesisAnalyticsFirehoseInputPreprocessingEvent.class, "kinesis/kinesis_analytics_firehose_input_preprocessing.json"), + args(KinesisAnalyticsStreamsInputPreprocessingEvent.class, "kinesis/kinesis_analytics_streams_input_preprocessing.json"), + args(KinesisAnalyticsInputPreprocessingResponse.class, "kinesis/kinesis_analytics_input_preprocessing_response.json"), + args(KinesisAnalyticsOutputDeliveryEvent.class, "kinesis/kinesis_analytics_output_delivery.json"), + args(KinesisAnalyticsOutputDeliveryResponse.class, "kinesis/kinesis_analytics_output_delivery_response.json"), + // API Gateway V2 WebSocket + args(APIGatewayV2WebSocketEvent.class, "apigw_websocket_event.json"), + args(APIGatewayV2ProxyRequestEvent.class, "apigw_websocket_event.json")); + } + + private static Stream knownFailureCases() { + return Stream.of( + // S3ObjectLambdaEvent: Lombok generates getXAmzRequestId() for field + // "xAmzRequestId". With USE_STD_BEAN_NAMING, Jackson derives the property + // name as "XAmzRequestId" (capital X), so the original "xAmzRequestId" key + // is silently dropped during deserialization. + // Fix: add @JsonProperty("xAmzRequestId") on the field or getter. + args(S3ObjectLambdaEvent.class, "s3_object_lambda_event.json")); + } + + private static Arguments args(Class clazz, String fixture) { + return Arguments.of(clazz.getSimpleName(), fixture, clazz); + } +} diff --git a/aws-lambda-java-tests/src/test/resources/apigw_http_event.json b/aws-lambda-java-tests/src/test/resources/apigw_http_event.json index 88f4e5b4b..91954656c 100644 --- a/aws-lambda-java-tests/src/test/resources/apigw_http_event.json +++ b/aws-lambda-java-tests/src/test/resources/apigw_http_event.json @@ -18,18 +18,6 @@ "requestContext": { "accountId": "123456789012", "apiId": "api-id", - "authentication": { - "clientCert": { - "clientCertPem": "CERT_CONTENT", - "subjectDN": "www.example.com", - "issuerDN": "Example issuer", - "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", - "validity": { - "notBefore": "May 28 12:30:02 2019 GMT", - "notAfter": "Aug 5 09:36:04 2021 GMT" - } - } - }, "authorizer": { "jwt": { "claims": { diff --git a/aws-lambda-java-tests/src/test/resources/apigw_rest_event.json b/aws-lambda-java-tests/src/test/resources/apigw_rest_event.json index 28f10c221..a139ccbe8 100644 --- a/aws-lambda-java-tests/src/test/resources/apigw_rest_event.json +++ b/aws-lambda-java-tests/src/test/resources/apigw_rest_event.json @@ -14,19 +14,22 @@ "Header2": [ "value1", "value2" + ], + "Header3": [ + "value1,value2" ] }, "queryStringParameters": { "parameter1": "value1", - "parameter2": "value" + "parameter2": "value2" }, "multiValueQueryStringParameters": { "parameter1": [ - "value1", - "value2" + "value1" ], "parameter2": [ - "value" + "value1", + "value2" ] }, "requestContext": { @@ -52,17 +55,7 @@ "sourceIp": "IP", "user": null, "userAgent": "user-agent", - "userArn": null, - "clientCert": { - "clientCertPem": "CERT_CONTENT", - "subjectDN": "www.example.com", - "issuerDN": "Example issuer", - "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", - "validity": { - "notBefore": "May 28 12:30:02 2019 GMT", - "notAfter": "Aug 5 09:36:04 2021 GMT" - } - } + "userArn": null }, "path": "/my/path", "protocol": "HTTP/1.1", @@ -76,5 +69,5 @@ "pathParameters": null, "stageVariables": null, "body": "Hello from Lambda!", - "isBase64Encoded": true + "isBase64Encoded": false } \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/apigw_websocket_event.json b/aws-lambda-java-tests/src/test/resources/apigw_websocket_event.json new file mode 100644 index 000000000..a47fc3a94 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/apigw_websocket_event.json @@ -0,0 +1,88 @@ +{ + "resource": "/", + "path": "/", + "httpMethod": "GET", + "headers": { + "Host": "abcdef1234.execute-api.us-east-1.amazonaws.com", + "Sec-WebSocket-Extensions": "permessage-deflate", + "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==", + "Sec-WebSocket-Version": "13", + "X-Forwarded-For": "192.0.2.1", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Host": [ + "abcdef1234.execute-api.us-east-1.amazonaws.com" + ], + "Sec-WebSocket-Extensions": [ + "permessage-deflate" + ], + "Sec-WebSocket-Key": [ + "dGhlIHNhbXBsZSBub25jZQ==" + ], + "Sec-WebSocket-Version": [ + "13" + ], + "X-Forwarded-For": [ + "192.0.2.1" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": { + "param1": "value1" + }, + "multiValueQueryStringParameters": { + "param1": [ + "value1" + ] + }, + "pathParameters": { + "proxy": "path/to/resource" + }, + "stageVariables": { + "stageVar1": "value1" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "abcdef", + "stage": "prod", + "requestId": "abc-def-ghi", + "identity": { + "cognitoIdentityPoolId": "us-east-1:id-pool", + "accountId": "123456789012", + "cognitoIdentityId": "us-east-1:identity-id", + "caller": "caller-id", + "apiKey": "api-key-id", + "sourceIp": "192.0.2.1", + "cognitoAuthenticationType": "authenticated", + "cognitoAuthenticationProvider": "provider", + "userArn": "arn:aws:iam::123456789012:user/testuser", + "userAgent": "Mozilla/5.0", + "user": "testuser", + "accessKey": "AKIAIOSFODNN7EXAMPLE" + }, + "resourcePath": "/", + "httpMethod": "GET", + "apiId": "abcdef1234", + "connectedAt": 1583348638390, + "connectionId": "abc123=", + "domainName": "abcdef1234.execute-api.us-east-1.amazonaws.com", + "eventType": "CONNECT", + "extendedRequestId": "abc123=", + "integrationLatency": "100", + "messageDirection": "IN", + "messageId": "msg-001", + "requestTime": "09/Apr/2020:18:03:58 +0000", + "requestTimeEpoch": 1583348638390, + "routeKey": "$connect", + "status": "200" + }, + "body": "request body", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/appsync_authorizer_event.json b/aws-lambda-java-tests/src/test/resources/appsync_authorizer_event.json new file mode 100644 index 000000000..494a500f3 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/appsync_authorizer_event.json @@ -0,0 +1,15 @@ +{ + "authorizationToken": "BE9DC5E3-D410-4733-AF76-70178092E681", + "requestContext": { + "apiId": "giy7kumfmvcqvbedntjwjvagii", + "accountId": "254688921111", + "requestId": "b80ed838-14c6-4500-b4c3-b694c7bef086", + "queryDocument": "mutation MyNewTask($desc: String!) {\n createTask(description: $desc) {\n id\n }\n}\n", + "operationName": "MyNewTask", + "variables": {} + }, + "requestHeaders": { + "host": "giy7kumfmvcqvbedntjwjvagii.appsync-api.us-east-1.amazonaws.com", + "content-type": "application/json" + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/appsync_authorizer_response.json b/aws-lambda-java-tests/src/test/resources/appsync_authorizer_response.json new file mode 100644 index 000000000..1216ad51d --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/appsync_authorizer_response.json @@ -0,0 +1,11 @@ +{ + "isAuthorized": true, + "resolverContext": { + "name": "Foo Man", + "balance": "100" + }, + "deniedFields": [ + "Mutation.createEvent" + ], + "ttlOverride": 15 +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cloudfront_event.json b/aws-lambda-java-tests/src/test/resources/cloudfront_event.json index 7485310e9..bf4625d06 100644 --- a/aws-lambda-java-tests/src/test/resources/cloudfront_event.json +++ b/aws-lambda-java-tests/src/test/resources/cloudfront_event.json @@ -7,7 +7,6 @@ }, "request": { "uri": "/test", - "querystring": "auth=test&foo=bar", "method": "GET", "clientIp": "2001:cdba::3257:9652", "headers": { diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_create_auth_challenge.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_create_auth_challenge.json new file mode 100644 index 000000000..495b41475 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_create_auth_challenge.json @@ -0,0 +1,37 @@ +{ + "version": "1", + "triggerSource": "CreateAuthChallenge_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "clientMetadata": { + "meta1": "value1" + }, + "challengeName": "CUSTOM_CHALLENGE", + "session": [ + { + "challengeName": "PASSWORD_VERIFIER", + "challengeResult": true, + "challengeMetadata": "metadata1" + } + ], + "userNotFound": false + }, + "response": { + "publicChallengeParameters": { + "captchaUrl": "url/123.jpg" + }, + "privateChallengeParameters": { + "answer": "5" + }, + "challengeMetadata": "CAPTCHA_CHALLENGE" + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_custom_message.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_custom_message.json new file mode 100644 index 000000000..aa7a53a83 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_custom_message.json @@ -0,0 +1,28 @@ +{ + "version": "1", + "triggerSource": "CustomMessage_SignUp", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com", + "phone_number_verified": "true", + "email_verified": "true" + }, + "clientMetadata": { + "meta1": "value1" + }, + "codeParameter": "####", + "usernameParameter": "testuser" + }, + "response": { + "smsMessage": "Your code is ####", + "emailMessage": "Your code is ####", + "emailSubject": "Welcome" + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_define_auth_challenge.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_define_auth_challenge.json new file mode 100644 index 000000000..e320b71d1 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_define_auth_challenge.json @@ -0,0 +1,32 @@ +{ + "version": "1", + "triggerSource": "DefineAuthChallenge_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "clientMetadata": { + "meta1": "value1" + }, + "session": [ + { + "challengeName": "PASSWORD_VERIFIER", + "challengeResult": true, + "challengeMetadata": "metadata1" + } + ], + "userNotFound": false + }, + "response": { + "challengeName": "CUSTOM_CHALLENGE", + "issueTokens": false, + "failAuthentication": false + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_migrate_user.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_migrate_user.json new file mode 100644 index 000000000..2897ae063 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_migrate_user.json @@ -0,0 +1,35 @@ +{ + "version": "1", + "triggerSource": "UserMigration_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "validationData": { + "key1": "val1" + }, + "clientMetadata": { + "meta1": "value1" + }, + "userName": "testuser", + "password": "test-password" + }, + "response": { + "userAttributes": { + "email": "user@example.com" + }, + "finalUserStatus": "CONFIRMED", + "messageAction": "SUPPRESS", + "desiredDeliveryMediums": [ + "EMAIL" + ], + "forceAliasCreation": false + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postauthentication.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postauthentication.json new file mode 100644 index 000000000..f41084d54 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postauthentication.json @@ -0,0 +1,20 @@ +{ + "version": "1", + "triggerSource": "PostAuthentication_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "clientMetadata": { + "meta1": "value1" + }, + "newDeviceUsed": false + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postconfirmation.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postconfirmation.json new file mode 100644 index 000000000..ecf63c7d3 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postconfirmation.json @@ -0,0 +1,20 @@ +{ + "version": "1", + "triggerSource": "PostConfirmation_ConfirmSignUp", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com", + "email_verified": "true" + }, + "clientMetadata": { + "meta1": "value1" + } + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_pre_token_generation.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_pre_token_generation.json new file mode 100644 index 000000000..f81ffb902 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_pre_token_generation.json @@ -0,0 +1,48 @@ +{ + "version": "1", + "triggerSource": "TokenGeneration_HostedAuth", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "clientMetadata": { + "meta1": "value1" + }, + "groupConfiguration": { + "groupsToOverride": [ + "group1", + "group2" + ], + "iamRolesToOverride": [ + "arn:aws:iam::123456789012:role/role1" + ], + "preferredRole": "arn:aws:iam::123456789012:role/role1" + } + }, + "response": { + "claimsOverrideDetails": { + "claimsToAddOrOverride": { + "custom:myattr": "myvalue" + }, + "claimsToSuppress": [ + "email" + ], + "groupOverrideDetails": { + "groupsToOverride": [ + "group1" + ], + "iamRolesToOverride": [ + "arn:aws:iam::123456789012:role/role1" + ], + "preferredRole": "arn:aws:iam::123456789012:role/role1" + } + } + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_preauthentication.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_preauthentication.json new file mode 100644 index 000000000..1402c4684 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_preauthentication.json @@ -0,0 +1,20 @@ +{ + "version": "1", + "triggerSource": "PreAuthentication_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "validationData": { + "key1": "val1" + }, + "userNotFound": false + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_presignup.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_presignup.json new file mode 100644 index 000000000..0d1f0936a --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_presignup.json @@ -0,0 +1,27 @@ +{ + "version": "1", + "triggerSource": "PreSignUp_SignUp", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "validationData": { + "key1": "val1" + }, + "clientMetadata": { + "meta1": "value1" + } + }, + "response": { + "autoConfirmUser": false, + "autoVerifyPhone": false, + "autoVerifyEmail": false + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_verify_auth_challenge.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_verify_auth_challenge.json new file mode 100644 index 000000000..ef14c4ddf --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_verify_auth_challenge.json @@ -0,0 +1,27 @@ +{ + "version": "1", + "triggerSource": "VerifyAuthChallengeResponse_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "clientMetadata": { + "meta1": "value1" + }, + "privateChallengeParameters": { + "answer": "5" + }, + "challengeAnswer": "5", + "userNotFound": false + }, + "response": { + "answerCorrect": true + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito_sync_event.json b/aws-lambda-java-tests/src/test/resources/cognito_sync_event.json new file mode 100644 index 000000000..6edf1c246 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito_sync_event.json @@ -0,0 +1,20 @@ +{ + "version": 2, + "eventType": "SyncTrigger", + "region": "us-east-1", + "identityPoolId": "us-east-1:example-identity-pool-id", + "identityId": "us-east-1:example-identity-id", + "datasetName": "SampleDataset", + "datasetRecords": { + "SampleKey1": { + "oldValue": "oldValue1", + "newValue": "newValue1", + "op": "replace" + }, + "SampleKey2": { + "oldValue": "oldValue2", + "newValue": "newValue2", + "op": "replace" + } + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/connect_event.json b/aws-lambda-java-tests/src/test/resources/connect_event.json index b71bf6692..4ce17a657 100644 --- a/aws-lambda-java-tests/src/test/resources/connect_event.json +++ b/aws-lambda-java-tests/src/test/resources/connect_event.json @@ -12,15 +12,6 @@ "InitialContactId": "6ca32fbd-8f92-46af-92a5-6b0f970f0efe", "InitiationMethod": "API", "InstanceARN": "arn:aws:connect:eu-central-1:123456789012:instance/9308c2a1-9bc6-4cea-8290-6c0b4a6d38fa", - "MediaStreams": { - "Customer": { - "Audio": { - "StartFragmentNumber": "91343852333181432392682062622220590765191907586", - "StartTimestamp": "1565781909613", - "StreamARN": "arn:aws:kinesisvideo:eu-central-1:123456789012:stream/connect-contact-a3d73b84-ce0e-479a-a9dc-5637c9d30ac9/1565272947806" - } - } - }, "PreviousContactId": "4ca32fbd-8f92-46af-92a5-6b0f970f0efe", "Queue": { "Name": "SampleQueue", diff --git a/aws-lambda-java-tests/src/test/resources/ddb/dynamo_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_event_roundtrip.json new file mode 100644 index 000000000..10d963c3c --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_event_roundtrip.json @@ -0,0 +1,97 @@ +{ + "Records": [ + { + "eventID": "c4ca4238a0b923820dcc509a6f75849b", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1.4285376E9, + "SequenceNumber": "4421584500000000017450439091", + "SizeBytes": 26, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", + "userIdentity": { + "principalId": "dynamodb.amazonaws.com", + "type": "Service" + } + }, + { + "eventID": "c81e728d9d4c2f636f067f89cc14862c", + "eventName": "MODIFY", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1.635734407123E9, + "SequenceNumber": "4421584500000000017450439092", + "SizeBytes": 59, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "eccbc87e4b5ce2fe28308fd9f2a7baf3", + "eventName": "REMOVE", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1.4285376E9, + "SequenceNumber": "4421584500000000017450439093", + "SizeBytes": 38, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/ddb/dynamo_time_window_event.json b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_time_window_event.json new file mode 100644 index 000000000..d931acb80 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_time_window_event.json @@ -0,0 +1,75 @@ +{ + "Records": [ + { + "eventID": "1", + "eventName": "INSERT", + "eventVersion": "1.0", + "eventSource": "aws:dynamodb", + "awsRegion": "us-east-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "SequenceNumber": "111", + "SizeBytes": 26, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "2", + "eventName": "MODIFY", + "eventVersion": "1.0", + "eventSource": "aws:dynamodb", + "awsRegion": "us-east-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "SequenceNumber": "222", + "SizeBytes": 59, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + } + ], + "window": { + "start": "2020-07-30T17:00:00Z", + "end": "2020-07-30T17:05:00Z" + }, + "state": { + "1": "state1" + }, + "shardId": "shard123456789", + "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", + "isFinalInvokeForWindow": false, + "isWindowTerminatedEarly": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/iot_button_event.json b/aws-lambda-java-tests/src/test/resources/iot_button_event.json new file mode 100644 index 000000000..8dc82826b --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/iot_button_event.json @@ -0,0 +1,5 @@ +{ + "serialNumber": "G030JF055364XVRB", + "clickType": "SINGLE", + "batteryVoltage": "2000mV" +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kafka_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/kafka_event_roundtrip.json new file mode 100644 index 000000000..d9f682e5f --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kafka_event_roundtrip.json @@ -0,0 +1,22 @@ +{ + "eventSource": "aws:kafka", + "eventSourceArn": "arn:aws:kafka:us-east-1:123456789012:cluster/vpc-3432434/4834-3547-3455-9872-7929", + "bootstrapServers": "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092", + "records": { + "mytopic-01": [ + { + "topic": "mytopic", + "partition": 0, + "offset": 15, + "timestamp": 1596480920837, + "timestampType": "CREATE_TIME", + "value": "SGVsbG8gZnJvbSBLYWZrYSAhIQ==", + "headers": [ + { + "headerKey": "aGVhZGVyVmFsdWU=" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_firehose_input_preprocessing.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_firehose_input_preprocessing.json new file mode 100644 index 000000000..8c6cfe514 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_firehose_input_preprocessing.json @@ -0,0 +1,14 @@ +{ + "invocationId": "invocationIdExample", + "applicationArn": "arn:aws:kinesisanalytics:us-east-1:123456789012:application/my-app", + "streamArn": "arn:aws:firehose:us-east-1:123456789012:deliverystream/my-stream", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "kinesisFirehoseRecordMetadata": { + "approximateArrivalTimestamp": 1583348638390 + }, + "data": "SGVsbG8gV29ybGQ=" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_input_preprocessing_response.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_input_preprocessing_response.json new file mode 100644 index 000000000..f5be190ec --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_input_preprocessing_response.json @@ -0,0 +1,9 @@ +{ + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "result": "Ok", + "data": "SGVsbG8gV29ybGQ=" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery.json new file mode 100644 index 000000000..573b6baba --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery.json @@ -0,0 +1,13 @@ +{ + "invocationId": "invocationIdExample", + "applicationArn": "arn:aws:kinesisanalytics:us-east-1:123456789012:application/my-app", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "lambdaDeliveryRecordMetadata": { + "retryHint": 0 + }, + "data": "SGVsbG8gV29ybGQ=" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery_response.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery_response.json new file mode 100644 index 000000000..56ddaa194 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery_response.json @@ -0,0 +1,8 @@ +{ + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "result": "Ok" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_streams_input_preprocessing.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_streams_input_preprocessing.json new file mode 100644 index 000000000..4ae8f3705 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_streams_input_preprocessing.json @@ -0,0 +1,17 @@ +{ + "invocationId": "invocationIdExample", + "applicationArn": "arn:aws:kinesisanalytics:us-east-1:123456789012:application/my-app", + "streamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/my-stream", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "kinesisStreamRecordMetadata": { + "sequenceNumber": "49546986683135544286507457936321625675700192471156785154", + "partitionKey": "partKey", + "shardId": "shardId-000000000000", + "approximateArrivalTimestamp": 1583348638390 + }, + "data": "SGVsbG8gV29ybGQ=" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_event_roundtrip.json new file mode 100644 index 000000000..e2081ef2b --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_event_roundtrip.json @@ -0,0 +1,21 @@ +{ + "Records": [ + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", + "approximateArrivalTimestamp": 1.4285376E9, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_time_window_event.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_time_window_event.json new file mode 100644 index 000000000..2d6283c58 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_time_window_event.json @@ -0,0 +1,32 @@ +{ + "Records": [ + { + "kinesis": { + "kinesisSchemaVersion": "1.0", + "partitionKey": "1", + "sequenceNumber": "49590338271490256608559692538361571095921575989136588898", + "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0Lg==", + "approximateArrivalTimestamp": 1.607497475E9 + }, + "eventSource": "aws:kinesis", + "eventVersion": "1.0", + "eventID": "shardId-000000000006:49590338271490256608559692538361571095921575989136588898", + "eventName": "aws:kinesis:record", + "invokeIdentityArn": "arn:aws:iam::123456789012:role/lambda-kinesis-role", + "awsRegion": "us-east-1", + "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream" + } + ], + "window": { + "start": "2020-12-09T07:04:00Z", + "end": "2020-12-09T07:06:00Z" + }, + "state": { + "1": "282", + "2": "715" + }, + "shardId": "shardId-000000000006", + "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream", + "isFinalInvokeForWindow": false, + "isWindowTerminatedEarly": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/lex_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/lex_event_roundtrip.json new file mode 100644 index 000000000..be065eb3f --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/lex_event_roundtrip.json @@ -0,0 +1,24 @@ +{ + "messageVersion": "1.0", + "invocationSource": "DialogCodeHook", + "userId": "John", + "sessionAttributes": { + "key": "value" + }, + "bot": { + "name": "BookTrip", + "alias": "$LATEST", + "version": "$LATEST" + }, + "outputDialogMode": "Text", + "currentIntent": { + "name": "BookHotel", + "slots": { + "Location": "Chicago", + "CheckInDate": "2030-11-08", + "Nights": "4", + "RoomType": "queen" + }, + "confirmationStatus": "None" + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/mq_event.json b/aws-lambda-java-tests/src/test/resources/mq_event.json index 6505a22d4..8b12af72e 100644 --- a/aws-lambda-java-tests/src/test/resources/mq_event.json +++ b/aws-lambda-java-tests/src/test/resources/mq_event.json @@ -5,15 +5,17 @@ { "messageID": "ID:b-9bcfa592-423a-4942-879d-eb284b418fc8-1.mq.us-west-2.amazonaws.com-37557-1234520418293-4:1:1:1:1", "messageType": "jms/text-message", - "data": "QUJDOkFBQUE=", - "connectionId": "myJMSCoID", + "timestamp": 1598827811958, + "deliveryMode": 0, "redelivered": false, + "expiration": 0, + "priority": 0, + "data": "QUJDOkFBQUE=", + "brokerInTime": 1598827811958, + "brokerOutTime": 1598827811959, "destination": { "physicalname": "testQueue" }, - "timestamp": 1598827811958, - "brokerInTime": 1598827811958, - "brokerOutTime": 1598827811959, "properties": { "testKey": "testValue" } @@ -21,15 +23,17 @@ { "messageID": "ID:b-8bcfa572-428a-4642-879d-eb284b418fc8-1.mq.us-west-2.amazonaws.com-37557-1234520418293-4:1:1:1:1", "messageType": "jms/bytes-message", + "timestamp": 1598827811958, + "deliveryMode": 0, + "redelivered": false, + "expiration": 0, + "priority": 0, "data": "3DTOOW7crj51prgVLQaGQ82S48k=", - "connectionId": "myJMSCoID1", - "persistent": false, + "brokerInTime": 1598827811958, + "brokerOutTime": 1598827811959, "destination": { "physicalname": "testQueue" }, - "timestamp": 1598827811958, - "brokerInTime": 1598827811958, - "brokerOutTime": 1598827811959, "properties": { "testKey": "testValue" } diff --git a/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json b/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json index 6b839912d..140908250 100644 --- a/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json +++ b/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json @@ -15,4 +15,4 @@ "kafkaRecordValue": "eyJOYW1lIjoiSGVsbG8gV29ybGQifQ==" } ] -} +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/msk_firehose_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/msk_firehose_event_roundtrip.json new file mode 100644 index 000000000..81b0a9c81 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/msk_firehose_event_roundtrip.json @@ -0,0 +1,18 @@ +{ + "invocationId": "12345621-4787-0000-a418-36e56Example", + "sourceMSKArn": "arn:aws:kafka:EXAMPLE", + "deliveryStreamArn": "arn:aws:firehose:EXAMPLE", + "region": "us-east-1", + "records": [ + { + "recordId": "00000000000000000000000000000000000000000000000000000000000000", + "approximateArrivalTimestamp": 1716369573887, + "mskRecordMetadata": { + "offset": "0", + "partitionId": "1", + "approximateArrivalTimestamp": "1716369573887" + }, + "kafkaRecordValue": "eyJOYW1lIjoiSGVsbG8gV29ybGQifQ==" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/partial_pojo.json b/aws-lambda-java-tests/src/test/resources/partial_pojo.json new file mode 100644 index 000000000..398218039 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/partial_pojo.json @@ -0,0 +1,4 @@ +{ + "name": "test", + "unknownField": "this will be dropped" +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/rabbitmq_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/rabbitmq_event_roundtrip.json new file mode 100644 index 000000000..44edf2f0a --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/rabbitmq_event_roundtrip.json @@ -0,0 +1,51 @@ +{ + "eventSource": "aws:rmq", + "eventSourceArn": "arn:aws:mq:us-west-2:112556298976:broker:test:b-9bcfa592-423a-4942-879d-eb284b418fc8", + "rmqMessagesByQueue": { + "test::/": [ + { + "basicProperties": { + "contentType": "text/plain", + "contentEncoding": null, + "headers": { + "header1": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 49 + ] + }, + "header2": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 50 + ] + }, + "numberInHeader": 10 + }, + "deliveryMode": 1, + "priority": 34, + "correlationId": null, + "replyTo": null, + "expiration": 60000, + "messageId": null, + "timestamp": "Jan 1, 1970, 12:33:41 AM", + "type": null, + "userId": "AIDACKCEVSQ6C2EXAMPLE", + "appId": null, + "clusterId": null, + "bodySize": 80 + }, + "redelivered": false, + "data": "eyJ0aW1lb3V0IjowLCJkYXRhIjoiQ1pybWYwR3c4T3Y0YnFMUXhENEUifQ==" + } + ] + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/alb_response.json b/aws-lambda-java-tests/src/test/resources/response/alb_response.json new file mode 100644 index 000000000..355eb193a --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/alb_response.json @@ -0,0 +1,15 @@ +{ + "statusCode": 200, + "statusDescription": "200 OK", + "headers": { + "Content-Type": "text/html" + }, + "multiValueHeaders": { + "Set-Cookie": [ + "cookie1=value1", + "cookie2=value2" + ] + }, + "body": "Hello", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/apigw_proxy_response.json b/aws-lambda-java-tests/src/test/resources/response/apigw_proxy_response.json new file mode 100644 index 000000000..640ccdc5c --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/apigw_proxy_response.json @@ -0,0 +1,15 @@ +{ + "statusCode": 200, + "headers": { + "Content-Type": "application/json", + "X-Custom-Header": "custom-value" + }, + "multiValueHeaders": { + "Set-Cookie": [ + "cookie1=value1", + "cookie2=value2" + ] + }, + "body": "{\"message\":\"Hello from Lambda\"}", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/apigw_v2_http_response.json b/aws-lambda-java-tests/src/test/resources/response/apigw_v2_http_response.json new file mode 100644 index 000000000..c39236650 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/apigw_v2_http_response.json @@ -0,0 +1,16 @@ +{ + "statusCode": 200, + "headers": { + "Content-Type": "application/json" + }, + "multiValueHeaders": { + "Set-Cookie": [ + "cookie1=value1" + ] + }, + "cookies": [ + "session=abc123; Secure; HttpOnly" + ], + "body": "{\"message\":\"OK\"}", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/apigw_v2_websocket_response.json b/aws-lambda-java-tests/src/test/resources/response/apigw_v2_websocket_response.json new file mode 100644 index 000000000..08392e890 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/apigw_v2_websocket_response.json @@ -0,0 +1,14 @@ +{ + "statusCode": 200, + "headers": { + "Content-Type": "application/json" + }, + "multiValueHeaders": { + "X-Custom": [ + "val1", + "val2" + ] + }, + "body": "{\"action\":\"sendmessage\",\"data\":\"hello\"}", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/msk_firehose_response.json b/aws-lambda-java-tests/src/test/resources/response/msk_firehose_response.json new file mode 100644 index 000000000..9ac497624 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/msk_firehose_response.json @@ -0,0 +1,9 @@ +{ + "records": [ + { + "recordId": "record-1", + "result": "Ok", + "kafkaRecordValue": "dHJhbnNmb3JtZWQgZGF0YQ==" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/s3_batch_response.json b/aws-lambda-java-tests/src/test/resources/response/s3_batch_response.json new file mode 100644 index 000000000..e63439d84 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/s3_batch_response.json @@ -0,0 +1,12 @@ +{ + "invocationSchemaVersion": "1.0", + "treatMissingKeysAs": "PermanentFailure", + "invocationId": "YXNkbGZqYWRmaiBhc2RmdW9hZHNmZGpmaGFzbGtkaGZza2RmaAo", + "results": [ + { + "taskId": "dGFza2lkZ29lc2hlcmUK", + "resultCode": "Succeeded", + "resultString": "Successfully processed" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/simple_iam_policy_response.json b/aws-lambda-java-tests/src/test/resources/response/simple_iam_policy_response.json new file mode 100644 index 000000000..5f23b6405 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/simple_iam_policy_response.json @@ -0,0 +1,7 @@ +{ + "isAuthorized": true, + "context": { + "userId": "user-123", + "scope": "read:all" + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/sqs_batch_response.json b/aws-lambda-java-tests/src/test/resources/response/sqs_batch_response.json new file mode 100644 index 000000000..5ef2de697 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/sqs_batch_response.json @@ -0,0 +1,7 @@ +{ + "batchItemFailures": [ + { + "itemIdentifier": "059f36b4-87a3-44ab-83d2-661975830a7d" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/s3_batch_event.json b/aws-lambda-java-tests/src/test/resources/s3_batch_event.json new file mode 100644 index 000000000..a70af0fdd --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/s3_batch_event.json @@ -0,0 +1,15 @@ +{ + "invocationSchemaVersion": "1.0", + "invocationId": "YXNkbGZqYWRmaiBhc2RmdW9hZHNmZGpmaGFzbGtkaGZza2RmaAo", + "job": { + "id": "f3cc4f60-61f6-4a2b-8a21-d07600c373ce" + }, + "tasks": [ + { + "taskId": "dGFza2lkZ29lc2hlcmUK", + "s3Key": "customerImage1.jpg", + "s3VersionId": "1", + "s3BucketArn": "arn:aws:s3:::amzn-s3-demo-bucket" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/s3_event.json b/aws-lambda-java-tests/src/test/resources/s3_event.json index 89e0bd312..73f59d072 100644 --- a/aws-lambda-java-tests/src/test/resources/s3_event.json +++ b/aws-lambda-java-tests/src/test/resources/s3_event.json @@ -28,8 +28,10 @@ }, "object": { "key": "test/key", + "urlDecodedKey": "test/key", "size": 1024, "eTag": "0123456789abcdef0123456789abcdef", + "versionId": "", "sequencer": "0A1B2C3D4E5F678901" } } diff --git a/aws-lambda-java-tests/src/test/resources/s3_object_lambda_event.json b/aws-lambda-java-tests/src/test/resources/s3_object_lambda_event.json new file mode 100644 index 000000000..db996e71c --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/s3_object_lambda_event.json @@ -0,0 +1,29 @@ +{ + "xAmzRequestId": "requestId", + "getObjectContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=snip", + "outputRoute": "io-use1-001", + "outputToken": "OutputToken" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "AssumedRole", + "principalId": "principalId", + "arn": "arn:aws:sts::111122223333:assumed-role/Admin/example", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.00" +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/time_window_event_response.json b/aws-lambda-java-tests/src/test/resources/time_window_event_response.json new file mode 100644 index 000000000..3c77b3784 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/time_window_event_response.json @@ -0,0 +1,10 @@ +{ + "state": { + "totalAmount": "500" + }, + "batchItemFailures": [ + { + "itemIdentifier": "49590338271490256608559692538361571095921575989136588898" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/unstable_pojo.json b/aws-lambda-java-tests/src/test/resources/unstable_pojo.json new file mode 100644 index 000000000..19db9a1cc --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/unstable_pojo.json @@ -0,0 +1,3 @@ +{ + "name": "test" +} \ No newline at end of file From 3d9661ee89c92f10a52352ad41a5fe3343ddd832 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:32:43 +0100 Subject: [PATCH 66/69] Bump actions/setup-java from 4 to 5 (#561) Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Davide Melfi --- .github/workflows/aws-lambda-java-core.yml | 2 +- .github/workflows/aws-lambda-java-events-sdk-transformer.yml | 2 +- .github/workflows/aws-lambda-java-events.yml | 2 +- .github/workflows/aws-lambda-java-log4j2.yml | 2 +- .github/workflows/aws-lambda-java-profiler.yml | 2 +- .github/workflows/aws-lambda-java-serialization.yml | 2 +- .github/workflows/aws-lambda-java-tests.yml | 2 +- .github/workflows/runtime-interface-client_merge_to_main.yml | 2 +- .github/workflows/runtime-interface-client_pr.yml | 4 ++-- .github/workflows/samples.yml | 4 ++-- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml index 373acf757..3e4364672 100644 --- a/.github/workflows/aws-lambda-java-core.yml +++ b/.github/workflows/aws-lambda-java-core.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml index 713f97211..144d52f86 100644 --- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml +++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml index 0a8725339..18be63cf9 100644 --- a/.github/workflows/aws-lambda-java-events.yml +++ b/.github/workflows/aws-lambda-java-events.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml index 315678339..945a1cb30 100644 --- a/.github/workflows/aws-lambda-java-log4j2.yml +++ b/.github/workflows/aws-lambda-java-log4j2.yml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index 1007e7124..485d93110 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 21 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index 78d23c37b..ccc805be7 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml index a52181dff..324c44514 100644 --- a/.github/workflows/aws-lambda-java-tests.yml +++ b/.github/workflows/aws-lambda-java-tests.yml @@ -30,7 +30,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index da2389d73..f66310755 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 59b1c8aed..645f1069c 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto @@ -51,7 +51,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index a76c3fb25..ef961c3e0 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -30,7 +30,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto @@ -54,7 +54,7 @@ jobs: - uses: actions/checkout@v6 # Set up both Java 8 and 21 - name: Set up Java 8 and 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: | 8 From 3239ddac451d3678a8f248143560809645321ed7 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Tue, 31 Mar 2026 10:01:20 +0100 Subject: [PATCH 67/69] Re-apply Jackson 2.18.6 upgrade with round-trip test coverage (#599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: ๐Ÿงช add roundtrip serialization test utility * test: fix false positives epoch format errors, added comment about this in the serialization package. * test: fixed false positves DateTime differences * test: fixing error in lex event fixture * test: fixing connect event * test: fixing api gateway proxy event false negative * test: fixing cloudfront and s3 event false negatives * chore: adding mise to gitignore * test: fix MSKFirehose, LexEvent, RabbitMQ, APIGatewayV2Auth and ActiveMQ serialization test fixtures * test: Add round-trip fixtures for 4 registered events Add JSON test fixtures and round-trip test cases for CognitoEvent, DynamodbTimeWindowEvent, IoTButtonEvent, and KinesisTimeWindowEvent. These were the only events registered in LambdaEventSerializers SUPPORTED_EVENTS that lacked test fixtures. Fixtures based on official AWS Lambda documentation examples. Time window event fixtures use round-trip-safe date formats to avoid coercion issues. Coverage: 32 passing + 2 known failures (34 total, up from 30). * test: Add round-trip fixtures for 21 unregistered events Add UnregisteredEventSerializationRoundTripTest covering events not in LambdaEventSerializers.SUPPORTED_EVENTS: 10 Cognito UserPool triggers, 5 Kinesis Analytics events, 2 API Gateway V2 WebSocket, 2 AppSync, 1 S3 Batch, and 1 TimeWindow response. S3ObjectLambdaEvent is a known failure (Lombok xAmzRequestId naming issue). Split SerializationRoundTripTest into registered (34 tests) and unregistered (22 tests) for clarity. Total: 56 tests, 53 passing, 3 known failures. * test: Add round-trip tests for 11 response event types Add ResponseEventSerializationRoundTripTest covering all 11 response event types in aws-lambda-java-events. 9 pass cleanly, 2 are known failures (IamPolicyResponse, IamPolicyResponseV1 โ€” getPolicyDocument() returns Map instead of the PolicyDocument POJO, making round-trip impossible by design since these are serialize-only types). Also update SerializationRoundTripTest comment for APIGatewayV2CustomAuthorizerEvent to clarify the date format change is a lossy transformation, not a bug. Total: 69 tests (34 registered + 22 unregistered + 11 response + 2 LambdaEventAssertTest), all green. Coverage now 66/68 event classes (97%). * chore: fixed comment * test: including IAM Policy Reponse roundtrip test * test: add test for JsonNodeUtils * chore: remove unwanted file on origin * docs: add tests 1.1.2 changelog entry * chore: remove entry in changelog * fix: ๐Ÿ”ง updating again jacjson 2.15.4 -> 2.18.6 * fix: fixing an error in github actions that was causing false positive errors when running aws-lambda-java-serialization * docs: update changelogs * chore: releasing aws-lambda-tests * chore: add space --------- Co-authored-by: Davide Melfi --- .../aws-lambda-java-serialization.yml | 2 +- .../RELEASE.CHANGELOG.md | 7 + aws-lambda-java-serialization/pom.xml | 4 +- .../events/LambdaEventSerializers.java | 285 +++++++++--------- aws-lambda-java-tests/RELEASE.CHANGELOG.md | 6 + aws-lambda-java-tests/pom.xml | 14 +- .../runtime/tests/LambdaEventAssert.java | 7 +- 7 files changed, 164 insertions(+), 161 deletions(-) diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index ccc805be7..f52c96fed 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -40,7 +40,7 @@ jobs: # Package and install target module - name: Package serialization with Maven - run: mvn -B package install --file aws-lambda-java-serialization/pom.xml + run: mvn -B install --file aws-lambda-java-serialization/pom.xml # Run tests - name: Run tests from aws-lambda-java-tests diff --git a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md index 4a0e8ca2a..d68d7b1fe 100644 --- a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md @@ -1,7 +1,14 @@ +### March 26, 2026 +`1.4.0`: +- Update `jackson-databind` dependency from 2.15.4 to 2.18.6 +- Replace deprecated `PropertyNamingStrategy.PascalCaseStrategy` with `PropertyNamingStrategies.UpperCamelCaseStrategy` +- The regression reported in 1.3.1 was a false positive caused by a CI workflow bug (`mvn package install` running the shade plugin twice, corrupting the artifact). Fixed by using `mvn install` instead. + ### March 19, 2026 `1.3.1`: - Revert `jackson-databind` dependency from 2.18.6 to 2.15.4 - Revert `PropertyNamingStrategies.UpperCamelCaseStrategy` to `PropertyNamingStrategy.PascalCaseStrategy` +- Note: reverted due to a suspected regression in Joda DateTime deserialization; later confirmed to be a false positive (see 1.4.0) ### March 11, 2026 `1.3.0`: diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index a71eb1f8d..d412fd765 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-lambda-java-serialization - 1.3.1 + 1.4.0 jar AWS Lambda Java Runtime Serialization @@ -32,7 +32,7 @@ 1.8 1.8 com.amazonaws.lambda.thirdparty - 2.15.4 + 2.18.6 2.10.1 20231013 7.3.2 diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java index 3f01d99b1..533bdcd49 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java @@ -19,6 +19,7 @@ import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.util.ReflectUtil; import com.amazonaws.services.lambda.runtime.serialization.util.SerializeUtil; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.amazonaws.services.lambda.runtime.serialization.events.modules.DateModule; import com.amazonaws.services.lambda.runtime.serialization.events.modules.DateTimeModule; @@ -86,156 +87,146 @@ public class LambdaEventSerializers { "com.amazonaws.services.lambda.runtime.events.SQSEvent") .collect(Collectors.toList()); - /** - * list of events incompatible with Jackson, with serializers explicitly defined - * Classes are incompatible with Jackson for any of the following reasons: - * 1. different constructor/setter types from getter types - * 2. various bugs within Jackson - */ - private static final Map SERIALIZER_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.s3.event.S3EventNotification", - new S3EventSerializer<>()), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", - new S3EventSerializer<>()), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.S3Event", - new S3EventSerializer<>())) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * list of events incompatible with Jackson, with serializers explicitly defined + * Classes are incompatible with Jackson for any of the following reasons: + * 1. different constructor/setter types from getter types + * 2. various bugs within Jackson + */ + private static final Map SERIALIZER_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.s3.event.S3EventNotification", new S3EventSerializer<>()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", new S3EventSerializer<>()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.S3Event", new S3EventSerializer<>())) + .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)); - /** - * Maps supported event classes to mixin classes with Jackson annotations. - * Jackson annotations are not loaded through the ClassLoader so if a Java field - * is serialized or deserialized from a - * json field that does not match the Jave field name, then a Mixin is required. - */ - @SuppressWarnings("rawtypes") - private static final Map MIXIN_MAP = Stream.of( - new SimpleEntry<>( - "com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", - CloudFormationCustomResourceEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFrontEvent", - CloudFrontEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", - CloudWatchLogsEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", - CodeCommitEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record", - CodeCommitEventMixin.RecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", - ConnectEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details", - ConnectEventMixin.DetailsMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData", - ConnectEventMixin.ContactDataMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint", - ConnectEventMixin.CustomerEndpointMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", - ConnectEventMixin.QueueMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint", - ConnectEventMixin.SystemEndpointMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", - DynamodbEventMixin.class), - new SimpleEntry<>( - "com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", - DynamodbEventMixin.DynamodbStreamRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.StreamRecord", - DynamodbEventMixin.StreamRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - DynamodbEventMixin.StreamRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.AttributeValue", - DynamodbEventMixin.AttributeValueMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - DynamodbEventMixin.AttributeValueMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", - DynamodbTimeWindowEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", - KinesisEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record", - KinesisEventMixin.RecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent", - KinesisTimeWindowEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ScheduledEvent", - ScheduledEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent", - SecretsManagerRotationEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - SNSEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord", - SNSEventMixin.SNSRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", - SQSEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage", - SQSEventMixin.SQSMessageMixin.class)) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * Maps supported event classes to mixin classes with Jackson annotations. + * Jackson annotations are not loaded through the ClassLoader so if a Java field is serialized or deserialized from a + * json field that does not match the Jave field name, then a Mixin is required. + */ + @SuppressWarnings("rawtypes") + private static final Map MIXIN_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", + CloudFormationCustomResourceEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFrontEvent", + CloudFrontEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", + CloudWatchLogsEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", + CodeCommitEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record", + CodeCommitEventMixin.RecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", + ConnectEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details", + ConnectEventMixin.DetailsMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData", + ConnectEventMixin.ContactDataMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint", + ConnectEventMixin.CustomerEndpointMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", ConnectEventMixin.QueueMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint", + ConnectEventMixin.SystemEndpointMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", + DynamodbEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", + DynamodbEventMixin.DynamodbStreamRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.StreamRecord", + DynamodbEventMixin.StreamRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + DynamodbEventMixin.StreamRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.AttributeValue", + DynamodbEventMixin.AttributeValueMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + DynamodbEventMixin.AttributeValueMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", + DynamodbTimeWindowEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", + KinesisEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record", + KinesisEventMixin.RecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent", + KinesisTimeWindowEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ScheduledEvent", + ScheduledEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent", + SecretsManagerRotationEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", + SNSEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord", + SNSEventMixin.SNSRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", + SQSEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage", + SQSEventMixin.SQSMessageMixin.class)) + .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)); - /** - * If mixins are required for inner classes of an event, then those nested - * classes must be specified here. - */ - @SuppressWarnings("rawtypes") - private static final Map> NESTED_CLASS_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CognitoEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.CognitoEvent$DatasetRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", - Arrays.asList( - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - "com.amazonaws.services.dynamodbv2.model.AttributeValue"), - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - "com.amazonaws.services.dynamodbv2.model.StreamRecord"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), - new SimpleEntry<>( - "com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", - Arrays.asList( - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - "com.amazonaws.services.dynamodbv2.model.AttributeValue"), - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - "com.amazonaws.services.dynamodbv2.model.StreamRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", - Arrays.asList( - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - "com.amazonaws.services.dynamodbv2.model.AttributeValue"), - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - "com.amazonaws.services.dynamodbv2.model.StreamRecord"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage")))) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * If mixins are required for inner classes of an event, then those nested classes must be specified here. + */ + @SuppressWarnings("rawtypes") + private static final Map> NESTED_CLASS_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CognitoEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.CognitoEvent$DatasetRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", + Arrays.asList( + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + "com.amazonaws.services.dynamodbv2.model.AttributeValue"), + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + "com.amazonaws.services.dynamodbv2.model.StreamRecord"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", + Arrays.asList( + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + "com.amazonaws.services.dynamodbv2.model.AttributeValue"), + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + "com.amazonaws.services.dynamodbv2.model.StreamRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", + Arrays.asList( + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + "com.amazonaws.services.dynamodbv2.model.AttributeValue"), + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + "com.amazonaws.services.dynamodbv2.model.StreamRecord"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage")))) + .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)); - /** - * If event requires a naming strategy. For example, when someone names the - * getter method getSNS and the setter - * method setSns, for some magical reasons, using both mixins and a naming - * strategy works - */ - private static final Map NAMING_STRATEGY_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - new PropertyNamingStrategy.PascalCaseStrategy()), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", - new PropertyNamingStrategy.PascalCaseStrategy())) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * If event requires a naming strategy. For example, when someone names the getter method getSNS and the setter + * method setSns, for some magical reasons, using both mixins and a naming strategy works + */ + private static final Map NAMING_STRATEGY_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", + new PropertyNamingStrategies.UpperCamelCaseStrategy()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", + new PropertyNamingStrategies.UpperCamelCaseStrategy()) + ) + .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)); /** * Returns whether the class name is a Lambda supported event model. diff --git a/aws-lambda-java-tests/RELEASE.CHANGELOG.md b/aws-lambda-java-tests/RELEASE.CHANGELOG.md index 76965b8fd..0b4bd2510 100644 --- a/aws-lambda-java-tests/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-tests/RELEASE.CHANGELOG.md @@ -1,3 +1,9 @@ +### March 27, 2026 +`1.1.3`: +- Add serialization round-trip tests covering 66 event classes +- Bumped `aws-lambda-java-serialization` to version `1.4.0` (Jackson `2.15.x` โ†’ `2.18.6`) +- Bumped `aws-lambda-java-events` to version `3.16.1` + ### August 26, 2021 `1.1.1`: - Bumped `aws-lambda-java-events` to version `3.11.0` diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 69a112cba..e63e529a2 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-tests - 1.1.2 + 1.1.3 jar AWS Lambda Java Tests @@ -40,18 +40,22 @@ --> 5.9.2 0.8.7 + 1.4.0 + 3.16.1 + 3.18.0 + 3.27.7 com.amazonaws aws-lambda-java-serialization - 1.2.0 + ${aws-lambda-java-serialization.version} com.amazonaws aws-lambda-java-events - 3.16.1 + ${aws-lambda-java-events.version} org.junit.jupiter @@ -71,13 +75,13 @@ org.apache.commons commons-lang3 - 3.18.0 + ${commons-lang3.version} org.assertj assertj-core - 3.27.7 + ${assertj-core.version} test diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java index e189b5e2b..f8d7e106d 100644 --- a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java @@ -25,13 +25,8 @@ * {@link AssertionError}. *

* - *

- * This class is intentionally package-private to support updates to - * the aws-lambda-java-events and aws-lambda-java-serialization packages. - * Consider making it public if there's a real request for it. - *

*/ -class LambdaEventAssert { +public class LambdaEventAssert { private static final ObjectMapper MAPPER = new ObjectMapper(); From 7230727de124386f9e9e49c04f2586a384b1bb02 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Tue, 31 Mar 2026 13:40:49 -0400 Subject: [PATCH 68/69] fix: pin GitHub Actions (#601) --- .github/workflows/repo-sync.yml | 4 ++-- .../runtime-interface-client_merge_to_main.yml | 12 ++++++------ .github/workflows/runtime-interface-client_pr.yml | 4 ++-- .github/workflows/samples.yml | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml index 6a918fde3..4934754d8 100644 --- a/.github/workflows/repo-sync.yml +++ b/.github/workflows/repo-sync.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v6 if: ${{ env.IS_CONFIGURED == 'true' }} - - uses: repo-sync/github-sync@v2 + - uses: repo-sync/github-sync@3832fe8e2be32372e1b3970bbae8e7079edeec88 # v2.3.0 name: Sync repo to branch if: ${{ env.IS_CONFIGURED == 'true' }} with: @@ -30,7 +30,7 @@ jobs: source_branch: main destination_branch: ${{ secrets.INTERMEDIATE_BRANCH }} github_token: ${{ secrets.GITHUB_TOKEN }} - - uses: repo-sync/pull-request@v2 + - uses: repo-sync/pull-request@7e79a9f5dc3ad0ce53138f01df2fad14a04831c5 # v2.12.1 name: Create pull request if: ${{ env.IS_CONFIGURED == 'true' }} with: diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index f66310755..d0d479111 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -28,20 +28,20 @@ jobs: contents: read steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: 8 distribution: corretto cache: maven - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 with: install: true @@ -62,7 +62,7 @@ jobs: if: env.ENABLE_SNAPSHOT != null env: ENABLE_SNAPSHOT: ${{ secrets.ENABLE_SNAPSHOT }} - uses: aws-actions/configure-aws-credentials@v4 + uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 with: aws-region: ${{ secrets.AWS_REGION }} role-to-assume: ${{ secrets.AWS_ROLE }} @@ -91,6 +91,6 @@ jobs: - name: Upload coverage to Codecov if: env.CODECOV_TOKEN != null - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 645f1069c..e0522005b 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -58,7 +58,7 @@ jobs: cache: maven - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -90,6 +90,6 @@ jobs: - name: Upload coverage to Codecov if: env.CODECOV_TOKEN != null - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index ef961c3e0..68e25827d 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -73,7 +73,7 @@ jobs: # Build custom-serialization samples - name: install sam - uses: aws-actions/setup-sam@v2 + uses: aws-actions/setup-sam@d78e1a4a9656d3b223e59b80676a797f20093133 # v2 - name: test fastJson run: cd samples/custom-serialization/fastJson && sam build && sam local invoke -e events/event.json | grep 200 - name: test gson From c4dcbab4ffeda26ec9dff997d6ef02a9ee2ab013 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 1 Apr 2026 08:43:15 -0400 Subject: [PATCH 69/69] fix: pinning actions (#602) --- .github/workflows/aws-lambda-java-profiler.yml | 8 ++++---- .github/workflows/runtime-interface-client_pr.yml | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index 485d93110..a098bfd14 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -22,17 +22,17 @@ jobs: contents: read steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: 21 distribution: corretto cache: maven - name: Issue AWS credentials - uses: aws-actions/configure-aws-credentials@v4 + uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 with: aws-region: ${{ secrets.AWS_REGION_PROFILER_EXTENSION_INTEGRATION_TEST }} role-to-assume: ${{ secrets.AWS_ROLE_PROFILER_EXTENSION_INTEGRATION_TEST }} @@ -68,7 +68,7 @@ jobs: run: ./integration_tests/download_from_s3.sh - name: Upload profiles - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: profiles path: /tmp/s3-artifacts diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index e0522005b..a0d8c6cc8 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -22,10 +22,10 @@ jobs: smoke-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: 8 distribution: corretto @@ -48,10 +48,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: 8 distribution: corretto @@ -61,7 +61,7 @@ jobs: uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 with: install: true @@ -83,7 +83,7 @@ jobs: IS_JAVA_8: true - name: Save the built jar - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: aws-lambda-java-runtime-interface-client path: ./aws-lambda-java-runtime-interface-client/target/aws-lambda-java-runtime-interface-client-*.jar