Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions docs/utilities/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,3 +472,49 @@ to powertools.You can then use it to do your validation or in idempotency module
}
}
```

## Advanced

### Lambda SnapStart priming

The Serialization utility integrates with AWS Lambda SnapStart to improve restore durations. To make sure the SnapStart priming logic of this utility runs correctly, you need an explicit reference to `EventDeserializer` in your code to allow the library to register before SnapStart takes a memory snapshot. Learn more about what priming is in this [blog post](https://aws.amazon.com/blogs/compute/optimizing-cold-start-performance-of-aws-lambda-using-advanced-priming-strategies-with-snapstart/){target="_blank"}.

If you don't use `EventDeserializer` during initialization yet, reference it in your Lambda handler. This can be done by adding one of the following lines to your handler class:

=== "Constructor"

```java hl_lines="6"
import software.amazon.lambda.powertools.utilities.EventDeserializer;

public class MyFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

public MyFunctionHandler() {
EventDeserializer.init(); // Ensure EventDeserializer is loaded for SnapStart
}

@Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
// ...
return something;
}
}
```

=== "Static Initializer"

```java hl_lines="6"
import software.amazon.lambda.powertools.utilities.EventDeserializer;

public class MyFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

static {
EventDeserializer.init(); // Ensure EventDeserializer is loaded for SnapStart
}

@Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
// ...
return something;
}
}
```
26 changes: 26 additions & 0 deletions powertools-serialization/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
<description>Utilities for JSON serialization used across the project.</description>

<dependencies>
<dependency>
<groupId>org.crac</groupId>
<artifactId>crac</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.lambda</groupId>
<artifactId>powertools-common</artifactId>
</dependency>
<dependency>
<groupId>io.burt</groupId>
<artifactId>jmespath-jackson</artifactId>
Expand Down Expand Up @@ -100,6 +108,24 @@
</build>

<profiles>
<profile>
<id>generate-classesloaded-file</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
-Xlog:class+load=info:classesloaded.txt
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>native</id>
<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,51 @@
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.crac.Context;
import org.crac.Core;
import org.crac.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.lambda.powertools.common.internal.ClassPreLoader;

/**
* Class that can be used to extract the meaningful part of an event and deserialize it into a Java object.<br/>
* For example, extract the body of an API Gateway event, or messages from an SQS event.
*/
public class EventDeserializer {
public class EventDeserializer implements Resource {

private static final Logger LOG = LoggerFactory.getLogger(EventDeserializer.class);
private static final EventDeserializer INSTANCE = new EventDeserializer();

static {
Core.getGlobalContext().register(INSTANCE);
}

public EventDeserializer() {

Check failure on line 64 in powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializer.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a nested comment explaining why this method is empty, throw an UnsupportedOperationException or complete the implementation.

See more on https://sonarcloud.io/project/issues?id=aws-powertools_powertools-lambda-java&issues=AaAwDbe2daycPjuqsIdK&open=AaAwDbe2daycPjuqsIdK&pullRequest=2614
}

/**
* Ensures this class is loaded so CRaC hooks register before SnapStart takes a snapshot.
*/
public static void init() {
// Referencing this method loads the class and runs the static CRaC registration.
}

@Override
public void beforeCheckpoint(Context<? extends Resource> context) {
try {
int primed = EventDeserializerPriming.prime().size();
LOG.debug("SnapStart invoke priming completed for {} event types", primed);
} catch (RuntimeException e) {
LOG.debug("SnapStart invoke priming failed", e);
}
ClassPreLoader.preloadClasses();
}

@Override
public void afterRestore(Context<? extends Resource> context) {
// No action needed after restore
}

/**
* Extract the meaningful part of a Lambda Event object. Main events are built-in:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* Copyright 2023 Amazon.com, Inc. or its affiliates.
* Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package software.amazon.lambda.powertools.utilities;

import static java.nio.charset.StandardCharsets.UTF_8;
import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
import com.amazonaws.services.lambda.runtime.events.ActiveMQEvent;
import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent;
import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
import com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent;
import com.amazonaws.services.lambda.runtime.events.KafkaEvent;
import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent;
import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent;
import com.amazonaws.services.lambda.runtime.events.KinesisEvent;
import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent;
import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent;
import com.amazonaws.services.lambda.runtime.events.SNSEvent;
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
import com.amazonaws.services.lambda.runtime.events.ScheduledEvent;
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
* Invoke-primes {@link EventDeserializer} by running {@code extractDataFrom} plus Jackson
* {@code as}/{@code asListOf} for every built-in event type.
*/
final class EventDeserializerPriming {

static final String SAMPLE_JSON = "{\"id\":1234,\"name\":\"product\",\"price\":42}";

private EventDeserializerPriming() {
}

static Set<Class<?>> prime() {
Objects.requireNonNull(JsonConfig.get().getObjectMapper());
Objects.requireNonNull(JsonConfig.get().getJmesPath());

String sampleBase64 = Base64.getEncoder().encodeToString(SAMPLE_JSON.getBytes(UTF_8));
Map<String, Object> sampleMap = Map.of("id", 1234, "name", "product", "price", 42);

Set<Class<?>> primed = new LinkedHashSet<>();
primeAs(primed, String.class, SAMPLE_JSON);
primeAs(primed, Map.class, sampleMap);
// Warm the JSON-array asListOf path (same source type as a raw String event)
Objects.requireNonNull(extractDataFrom("[" + SAMPLE_JSON + "]").asListOf(Map.class));

APIGatewayProxyRequestEvent apiV1 = new APIGatewayProxyRequestEvent();
apiV1.setBody(SAMPLE_JSON);
primeAs(primed, APIGatewayProxyRequestEvent.class, apiV1);

APIGatewayV2HTTPEvent apiV2 = new APIGatewayV2HTTPEvent();
apiV2.setBody(SAMPLE_JSON);
primeAs(primed, APIGatewayV2HTTPEvent.class, apiV2);

SNSEvent.SNS sns = new SNSEvent.SNS();
sns.setMessage(SAMPLE_JSON);
SNSEvent.SNSRecord snsRecord = new SNSEvent.SNSRecord();
snsRecord.setSns(sns);
SNSEvent snsEvent = new SNSEvent();
snsEvent.setRecords(List.of(snsRecord));
primeAs(primed, SNSEvent.class, snsEvent);

SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage();
sqsMessage.setBody(SAMPLE_JSON);
SQSEvent sqsEvent = new SQSEvent();
sqsEvent.setRecords(List.of(sqsMessage));
primeAsList(primed, SQSEvent.class, sqsEvent);
primeAs(primed, SQSEvent.SQSMessage.class, sqsMessage);

ScheduledEvent scheduledEvent = new ScheduledEvent();
scheduledEvent.setDetail(sampleMap);
primeAs(primed, ScheduledEvent.class, scheduledEvent);

ApplicationLoadBalancerRequestEvent albEvent = new ApplicationLoadBalancerRequestEvent();
albEvent.setBody(SAMPLE_JSON);
primeAs(primed, ApplicationLoadBalancerRequestEvent.class, albEvent);

CloudWatchLogsEvent.AWSLogs awsLogs = new CloudWatchLogsEvent.AWSLogs();
awsLogs.setData(sampleBase64);
CloudWatchLogsEvent cloudWatchLogsEvent = new CloudWatchLogsEvent();
cloudWatchLogsEvent.setAwsLogs(awsLogs);
primeAs(primed, CloudWatchLogsEvent.class, cloudWatchLogsEvent);

CloudFormationCustomResourceEvent cloudFormationEvent = new CloudFormationCustomResourceEvent();
cloudFormationEvent.setResourceProperties(sampleMap);
primeAs(primed, CloudFormationCustomResourceEvent.class, cloudFormationEvent);

KinesisEvent.KinesisEventRecord kinesisEventRecord = kinesisEventRecord();
KinesisEvent kinesisEvent = new KinesisEvent();
kinesisEvent.setRecords(List.of(kinesisEventRecord));
primeAsList(primed, KinesisEvent.class, kinesisEvent);
// Use a fresh record: decode(ByteBuffer) consumes the buffer position
primeAs(primed, KinesisEvent.KinesisEventRecord.class, kinesisEventRecord());

KinesisFirehoseEvent.Record firehoseRecord = new KinesisFirehoseEvent.Record();
firehoseRecord.setData(jsonBuffer());
KinesisFirehoseEvent firehoseEvent = new KinesisFirehoseEvent();
firehoseEvent.setRecords(List.of(firehoseRecord));
primeAsList(primed, KinesisFirehoseEvent.class, firehoseEvent);

KafkaEvent.KafkaEventRecord kafkaRecord = new KafkaEvent.KafkaEventRecord();
kafkaRecord.setValue(sampleBase64);
KafkaEvent kafkaEvent = new KafkaEvent();
kafkaEvent.setRecords(Map.of("topic", List.of(kafkaRecord)));
primeAsList(primed, KafkaEvent.class, kafkaEvent);

ActiveMQEvent.ActiveMQMessage activeMqMessage = new ActiveMQEvent.ActiveMQMessage();
activeMqMessage.setData(sampleBase64);
ActiveMQEvent activeMqEvent = new ActiveMQEvent();
activeMqEvent.setMessages(List.of(activeMqMessage));
primeAsList(primed, ActiveMQEvent.class, activeMqEvent);

RabbitMQEvent.RabbitMessage rabbitMessage = new RabbitMQEvent.RabbitMessage();
rabbitMessage.setData(sampleBase64);
RabbitMQEvent rabbitMqEvent = new RabbitMQEvent();
rabbitMqEvent.setRmqMessagesByQueue(Map.of("queue", List.of(rabbitMessage)));
primeAsList(primed, RabbitMQEvent.class, rabbitMqEvent);

KinesisAnalyticsFirehoseInputPreprocessingEvent.Record kaFirehoseRecord =
new KinesisAnalyticsFirehoseInputPreprocessingEvent.Record();
kaFirehoseRecord.setData(jsonBuffer());
KinesisAnalyticsFirehoseInputPreprocessingEvent kaFirehoseEvent =
new KinesisAnalyticsFirehoseInputPreprocessingEvent();
kaFirehoseEvent.setRecords(List.of(kaFirehoseRecord));
primeAsList(primed, KinesisAnalyticsFirehoseInputPreprocessingEvent.class, kaFirehoseEvent);

KinesisAnalyticsStreamsInputPreprocessingEvent.Record kaStreamsRecord =
new KinesisAnalyticsStreamsInputPreprocessingEvent.Record();
kaStreamsRecord.setData(jsonBuffer());
KinesisAnalyticsStreamsInputPreprocessingEvent kaStreamsEvent =
new KinesisAnalyticsStreamsInputPreprocessingEvent();
kaStreamsEvent.setRecords(List.of(kaStreamsRecord));
primeAsList(primed, KinesisAnalyticsStreamsInputPreprocessingEvent.class, kaStreamsEvent);

return primed;
}

private static void primeAs(Set<Class<?>> primed, Class<?> eventType, Object event) {
Objects.requireNonNull(extractDataFrom(event).as(Map.class));
primed.add(eventType);
}

private static void primeAsList(Set<Class<?>> primed, Class<?> eventType, Object event) {
Objects.requireNonNull(extractDataFrom(event).asListOf(Map.class));
primed.add(eventType);
}

private static ByteBuffer jsonBuffer() {
return ByteBuffer.wrap(SAMPLE_JSON.getBytes(UTF_8));
}

private static KinesisEvent.KinesisEventRecord kinesisEventRecord() {
KinesisEvent.Record kinesisRecord = new KinesisEvent.Record();
kinesisRecord.setData(jsonBuffer());
KinesisEvent.KinesisEventRecord kinesisEventRecord = new KinesisEvent.KinesisEventRecord();
kinesisEventRecord.setKinesis(kinesisRecord);
return kinesisEventRecord;
}
}
Loading