diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ab40d21d7..d9fdc8b1d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,5 +2,7 @@ *Description of changes:* +*Target (OCI, Managed Runtime, both):* + By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index 2febf33a4..9d71cbabf 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -29,6 +29,11 @@ jobs: # Install base module - name: Install events with Maven run: mvn -B install --file aws-lambda-java-events/pom.xml - # Package target module + + # Package and install target module - name: Package serialization with Maven - run: mvn -B package --file aws-lambda-java-serialization/pom.xml + run: mvn -B package install --file aws-lambda-java-serialization/pom.xml + + # Run tests + - name: Run tests from aws-lambda-java-tests + run: mvn test --file aws-lambda-java-tests/pom.xml diff --git a/README.md b/README.md index 044f00a63..5d8fad4c3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,16 @@ +# AWS Lambda Libs on CRaC + +This is a version of the original AWS Lambda Libraries with CRaC support added. + +To use, add next to the pom.xml: +``` + + io.github.crac.com.amazonaws + aws-lambda-java-runtime-interface-client + 2.4.1.CRAC.0 + +``` + # AWS Lambda Java Support Libraries Key libraries for running Java on the AWS Lambda platform. @@ -21,7 +34,7 @@ Example request handler public class Handler implements RequestHandler, String>{ @Override public String handleRequest(Map event, Context context) { - + } } ``` @@ -32,7 +45,7 @@ Example request stream handler public class HandlerStream implements RequestStreamHandler { @Override public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { - + } } ``` @@ -41,7 +54,7 @@ public class HandlerStream implements RequestStreamHandler { com.amazonaws aws-lambda-java-core - 1.2.1 + 1.2.3 ``` @@ -71,7 +84,7 @@ public class SqsHandler implements RequestHandler { com.amazonaws aws-lambda-java-events - 3.11.0 + 3.11.4 ``` @@ -101,7 +114,7 @@ public void testInjectSQSEvent(SQSEvent event) { ## aws-lambda-java-events-sdk-transformer This package provides helper classes/methods to use alongside `aws-lambda-java-events` in order to transform -Lambda input event model objects into SDK-compatible output model objects. +Lambda input event model objects into SDK-compatible output model objects. See the [documentation](aws-lambda-java-events-sdk-transformer/README.md) for more information. - [Release Notes](aws-lambda-java-events-sdk-transformer/RELEASE.CHANGELOG.md) @@ -125,7 +138,7 @@ See the [README](aws-lambda-java-log4j2/README.md) or the [official documentatio com.amazonaws aws-lambda-java-log4j2 - 1.5.1 + 1.6.0 ``` @@ -140,7 +153,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.1.1 + 2.4.0 ``` @@ -154,7 +167,7 @@ This package defines the Lambda serialization logic using in the `aws-lambda-jav com.amazonaws aws-lambda-java-serialization - 1.0.1 + 1.1.5 ``` @@ -162,4 +175,4 @@ This package defines the Lambda serialization logic using in the `aws-lambda-jav Each of the supplied packages should be used without modification. Removing dependencies, adding conflicting dependencies, or selectively including classes -from the packages can result in unexpected behavior. \ No newline at end of file +from the packages can result in unexpected behavior. diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md index fd52389ba..ebd0566ff 100644 --- a/aws-lambda-java-core/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### August 17, 2023 +`1.2.3`: +- Extended logger interface with level-aware logging backend functions + ### November 09, 2022 `1.2.2`: - Added new `CustomPojoSerializer` interface diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index 1438a2e7a..0dd848a96 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.2 + 1.2.3 jar AWS Lambda Java Core Library diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/LambdaLogger.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/LambdaLogger.java index ec51c4ab1..e068abe8a 100644 --- a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/LambdaLogger.java +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/LambdaLogger.java @@ -2,6 +2,8 @@ package com.amazonaws.services.lambda.runtime; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + /** * A low level Lambda runtime logger * @@ -10,7 +12,7 @@ public interface LambdaLogger { /** * Logs a string to AWS CloudWatch Logs - * + * *

* Logging will not be done: *

    @@ -22,7 +24,7 @@ public interface LambdaLogger { * *
*

- * + * * @param message A string containing the event to log. */ void log(String message); @@ -32,5 +34,27 @@ public interface LambdaLogger { * @param message byte array containing logs */ void log(byte[] message); + + /** + * LogLevel aware logging backend function. + * + * @param message in String format + * @param logLevel + */ + default void log(String message, LogLevel logLevel) { + log(message); + } + + /** + * LogLevel aware logging backend function. + * + * @param message in byte[] format + * @param logLevel + */ + default void log(byte[] message, LogLevel logLevel) { + log(message); + } + + } diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/logging/LogFormat.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/logging/LogFormat.java new file mode 100644 index 000000000..0d65860d7 --- /dev/null +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/logging/LogFormat.java @@ -0,0 +1,17 @@ +/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.logging; + + +public enum LogFormat { + JSON, + TEXT; + + public static LogFormat fromString(String logFormat) { + try { + return LogFormat.valueOf(logFormat.toUpperCase()); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid log format: '" + logFormat + "' expected one of [JSON, TEXT]"); + } + } +} diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/logging/LogLevel.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/logging/LogLevel.java new file mode 100644 index 000000000..4f48a3e41 --- /dev/null +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/logging/LogLevel.java @@ -0,0 +1,25 @@ +/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.logging; + + +public enum LogLevel { + // UNDEFINED log level is used when the legacy LambdaLogger::log(String) function is called + // where the loglevel is not defined. In this case we're not filtering the message in the runtime + UNDEFINED, + TRACE, + DEBUG, + INFO, + WARN, + ERROR, + FATAL; + + public static LogLevel fromString(String logLevel) { + try { + return LogLevel.valueOf(logLevel.toUpperCase()); + } catch (Exception e) { + throw new IllegalArgumentException( + "Invalid log level: '" + logLevel + "' expected one of [TRACE, DEBUG, INFO, WARN, ERROR, FATAL]"); + } + } +} diff --git a/aws-lambda-java-events-sdk-transformer/README.md b/aws-lambda-java-events-sdk-transformer/README.md index 9b98899cb..02f2dc11f 100644 --- a/aws-lambda-java-events-sdk-transformer/README.md +++ b/aws-lambda-java-events-sdk-transformer/README.md @@ -21,7 +21,7 @@ Add the following Apache Maven dependencies to your `pom.xml` file: com.amazonaws aws-lambda-java-events - 3.11.0 + 3.11.2 ``` diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index 6b25b46d7..6a2b1735c 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.0 + 3.11.2 provided diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 1d741469a..1a589d6f9 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -55,12 +55,8 @@ * `SQSBatchResponse` * `SQSEvent` -*As of version `3.0.0`, users are no longer required to pull in SDK dependencies in order to use this library.* - -### Getting Started - -[Maven](https://maven.apache.org) +### Usage ```xml @@ -68,34 +64,13 @@ com.amazonaws aws-lambda-java-core - 1.2.1 + 1.2.3 com.amazonaws aws-lambda-java-events - 3.11.0 + 3.11.4 ... ``` - -[Gradle](https://gradle.org) - -```groovy -'com.amazonaws:aws-lambda-java-core:1.2.1' -'com.amazonaws:aws-lambda-java-events:3.11.0' -``` - -[Leiningen](http://leiningen.org) and [Boot](http://boot-clj.com) - -```clojure -[com.amazonaws/aws-lambda-java-core "1.2.1"] -[com.amazonaws/aws-lambda-java-events "3.11.0"] -``` - -[sbt](http://www.scala-sbt.org) - -```scala -"com.amazonaws" % "aws-lambda-java-core" % "1.2.1" -"com.amazonaws" % "aws-lambda-java-events" % "3.11.0" -``` diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index 6451e5b96..ffb982941 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,3 +1,25 @@ +### December 1, 2023 +`3.11.4`: +- Improve `toString` in Cognito events by calling `super` +- Added missing `version` field to ScheduledEvent from CloudWatch + +### September 1, 2023 +`3.11.3`: +- Update challengeAnswer field format in CognitoUserPoolEvent + +### May 18, 2023 +`3.11.2`: +- Add missing fields to API Gateway request context + +### March 10, 2023 +`3.11.1`: +- Extended ActiveMQEvent with custom properties ([#408](https://github.com/aws/aws-lambda-java-libs/pull/408)) +- Updated dependencies([#410](https://github.com/aws/aws-lambda-java-libs/pull/410)): + - `joda-time` from 2.6 to 2.10.8 + - `jackson-databind` from 2.13.4.1 to 2.14.2 + - `junit-jupiter-engine` from 5.7.0 to 5.9.2 + - `json-unit-assertj` from 2.22.0 to 2.36.1 + ### November 24, 2021 `3.11.0`: - Added support for SQSaaES Partial Batch Feature ([#279](https://github.com/aws/aws-lambda-java-libs/pull/279)) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index af786d910..f550a37ec 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -1,190 +1,190 @@ - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 - com.amazonaws - aws-lambda-java-events - 3.11.0 - jar + com.amazonaws + aws-lambda-java-events + 3.11.4 + jar - AWS Lambda Java Events Library - - Event interface definitions AWS services supported by AWS Lambda. - - https://aws.amazon.com/lambda/ - - - Apache License, Version 2.0 - https://aws.amazon.com/apache2.0 - repo - - - - https://github.com/aws/aws-lambda-java-libs.git - - - - AWS Lambda team - Amazon Web Services - https://aws.amazon.com/ - - + AWS Lambda Java Events Library + + Event interface definitions AWS services supported by AWS Lambda. + + https://aws.amazon.com/lambda/ + + + Apache License, Version 2.0 + https://aws.amazon.com/apache2.0 + repo + + + + https://github.com/aws/aws-lambda-java-libs.git + + + + AWS Lambda team + Amazon Web Services + https://aws.amazon.com/ + + - - 1.8 - 1.8 - 1.18.22 - + + 1.8 + 1.8 + 1.18.22 + - - - sonatype-nexus-staging - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - + + + sonatype-nexus-staging + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + - - - joda-time - joda-time - 2.6 - + + + joda-time + joda-time + 2.10.8 + - - org.junit.jupiter - junit-jupiter-engine - 5.7.0 - test - - - com.fasterxml.jackson.core - jackson-databind - 2.13.4.1 - test - - - net.javacrumbs.json-unit - json-unit-assertj - 2.22.0 - test - + + org.junit.jupiter + junit-jupiter-engine + 5.9.2 + test + + + com.fasterxml.jackson.core + jackson-databind + 2.14.2 + test + + + net.javacrumbs.json-unit + json-unit-assertj + 2.36.1 + test + - - org.projectlombok - lombok - ${lombok.version} - provided - - + + org.projectlombok + lombok + ${lombok.version} + provided + + - - - dev - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - -Xdoclint:none - - - - attach-javadocs - - jar - - - - - - - - - release - - - - org.apache.maven.plugins - maven-source-plugin - 3.2.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - -Xdoclint:none - - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 - true - - sonatype-nexus-staging - https://aws.oss.sonatype.org/ - false - - - - org.apache.maven.plugins - maven-resources-plugin - 3.2.0 - - UTF-8 - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - - - org.projectlombok - lombok - ${lombok.version} - - - UTF-8 - - - - - - + + + dev + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + -Xdoclint:none + + + + attach-javadocs + + jar + + + + + + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + -Xdoclint:none + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.3 + true + + sonatype-nexus-staging + https://aws.oss.sonatype.org/ + false + + + + org.apache.maven.plugins + maven-resources-plugin + 3.2.0 + + UTF-8 + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + + org.projectlombok + lombok + ${lombok.version} + + + UTF-8 + + + + + + diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/APIGatewayProxyRequestEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/APIGatewayProxyRequestEvent.java index 042a481a9..8ff8ccb8b 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/APIGatewayProxyRequestEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/APIGatewayProxyRequestEvent.java @@ -66,6 +66,18 @@ public static class ProxyRequestContext implements Serializable, Cloneable { private Map authorizer; + private String extendedRequestId; + + private String requestTime; + + private Long requestTimeEpoch; + + private String domainName; + + private String domainPrefix; + + private String protocol; + /** * default constructor */ @@ -305,6 +317,145 @@ public ProxyRequestContext withOperationName(String operationName) { return this; } + /** + * @return The API Gateway Extended Request Id + */ + public String getExtendedRequestId() { + return extendedRequestId; + } + + /** + * @param extendedRequestId The API Gateway Extended Request Id + */ + public void setExtendedRequestId(String extendedRequestId) { + this.extendedRequestId = extendedRequestId; + } + + /** + * @param extendedRequestId The API Gateway Extended Request Id + * @return ProxyRequestContext object + */ + public ProxyRequestContext withExtendedRequestId(String extendedRequestId) { + this.setExtendedRequestId(extendedRequestId); + return this; + } + + /** + * @return The CLF-formatted request time (dd/MMM/yyyy:HH:mm:ss +-hhmm). + */ + public String getRequestTime() { + return requestTime; + } + + /** + * @param requestTime The CLF-formatted request time (dd/MMM/yyyy:HH:mm:ss +-hhmm). + */ + public void setRequestTime(String requestTime) { + this.requestTime = requestTime; + } + + /** + * @param requestTime The CLF-formatted request time (dd/MMM/yyyy:HH:mm:ss +-hhmm). + * @return ProxyRequestContext object + */ + public ProxyRequestContext withRequestTime(String requestTime) { + this.setRequestTime(requestTime); + return this; + } + + /** + * @return The Epoch-formatted request time (in millis) + */ + public Long getRequestTimeEpoch() { + return requestTimeEpoch; + } + + /** + * @param requestTimeEpoch The Epoch-formatted request time (in millis) + */ + public void setRequestTimeEpoch(Long requestTimeEpoch) { + this.requestTimeEpoch = requestTimeEpoch; + } + + /** + * @param requestTimeEpoch The Epoch-formatted request time (in millis) + * @return ProxyRequestContext object + */ + public ProxyRequestContext withRequestTimeEpoch(Long requestTimeEpoch) { + this.setRequestTimeEpoch(requestTimeEpoch); + return this; + } + + /** + * @return The full domain name used to invoke the API. This should be the same as the incoming Host header. + */ + public String getDomainName() { + return domainName; + } + + /** + * @param domainName The full domain name used to invoke the API. + * This should be the same as the incoming Host header. + */ + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + /** + * @param domainName The full domain name used to invoke the API. + * This should be the same as the incoming Host header. + * @return ProxyRequestContext object + */ + public ProxyRequestContext withDomainName(String domainName) { + this.setDomainName(domainName); + return this; + } + + /** + * @return The first label of the domainName. This is often used as a caller/customer identifier. + */ + public String getDomainPrefix() { + return domainPrefix; + } + + /** + * @param domainPrefix The first label of the domainName. This is often used as a caller/customer identifier. + */ + public void setDomainPrefix(String domainPrefix) { + this.domainPrefix = domainPrefix; + } + + /** + * @param domainPrefix The first label of the domainName. This is often used as a caller/customer identifier. + * @return + */ + public ProxyRequestContext withDomainPrefix(String domainPrefix) { + this.setDomainPrefix(domainPrefix); + return this; + } + /** + * @return The request protocol, for example, HTTP/1.1. + */ + public String getProtocol() { + return protocol; + } + + /** + * @param protocol The request protocol, for example, HTTP/1.1. + */ + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + /** + * @param protocol The request protocol, for example, HTTP/1.1. + * @return ProxyRequestContext object + */ + public ProxyRequestContext withProtocol(String protocol) { + this.setProtocol(protocol); + return this; + } + /** * Returns a string representation of this object; useful for testing and debugging. * @@ -338,6 +489,18 @@ public String toString() { sb.append("authorizer: ").append(getAuthorizer().toString()); if (getOperationName() != null) sb.append("operationName: ").append(getOperationName().toString()); + if (getExtendedRequestId() != null) + sb.append("extendedRequestId: ").append(getExtendedRequestId()).append(","); + if (getRequestTime() != null) + sb.append("requestTime: ").append(getRequestTime()).append(","); + if (getProtocol() != null) + sb.append("protocol: ").append(getProtocol()).append(","); + if (getRequestTimeEpoch() != null) + sb.append("requestTimeEpoch: ").append(getRequestTimeEpoch()).append(","); + if (getDomainPrefix() != null) + sb.append("domainPrefix: ").append(getDomainPrefix()).append(","); + if (getDomainName() != null) + sb.append("domainName: ").append(getDomainName()); sb.append("}"); return sb.toString(); } @@ -396,6 +559,30 @@ public boolean equals(Object obj) { return false; if (other.getOperationName() != null && !other.getOperationName().equals(this.getOperationName())) return false; + if (other.getExtendedRequestId() == null ^ this.getExtendedRequestId() == null) + return false; + if (other.getExtendedRequestId() != null && other.getExtendedRequestId().equals(this.getExtendedRequestId()) == false) + return false; + if (other.getRequestTime() == null ^ this.getRequestTime() == null) + return false; + if (other.getRequestTime() != null && other.getRequestTime().equals(this.getRequestTime()) == false) + return false; + if (other.getRequestTimeEpoch() == null ^ this.getRequestTimeEpoch() == null) + return false; + if (other.getRequestTimeEpoch() != null && other.getRequestTimeEpoch().equals(this.getRequestTimeEpoch()) == false) + return false; + if (other.getDomainName() == null ^ this.getDomainName() == null) + return false; + if (other.getDomainName() != null && other.getDomainName().equals(this.getDomainName()) == false) + return false; + if (other.getDomainPrefix() == null ^ this.getDomainPrefix() == null) + return false; + if (other.getDomainPrefix() != null && other.getDomainPrefix().equals(this.getDomainPrefix()) == false) + return false; + if (other.getProtocol() == null ^ this.getProtocol() == null) + return false; + if (other.getProtocol() != null && other.getProtocol().equals(this.getProtocol()) == false) + return false; return true; } @@ -415,6 +602,12 @@ public int hashCode() { hashCode = prime * hashCode + ((getPath() == null) ? 0 : getPath().hashCode()); hashCode = prime * hashCode + ((getAuthorizer() == null) ? 0 : getAuthorizer().hashCode()); hashCode = prime * hashCode + ((getOperationName() == null) ? 0: getOperationName().hashCode()); + hashCode = prime * hashCode + ((getExtendedRequestId() == null) ? 0 : getExtendedRequestId().hashCode()); + hashCode = prime * hashCode + ((getRequestTime() == null) ? 0 : getRequestTime().hashCode()); + hashCode = prime * hashCode + ((getRequestTimeEpoch() == null) ? 0 : getRequestTimeEpoch().hashCode()); + hashCode = prime * hashCode + ((getDomainName() == null) ? 0 : getDomainName().hashCode()); + hashCode = prime * hashCode + ((getDomainPrefix() == null) ? 0 : getDomainPrefix().hashCode()); + hashCode = prime * hashCode + ((getProtocol() == null) ? 0 : getProtocol().hashCode()); return hashCode; } diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ActiveMQEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ActiveMQEvent.java index 46791980e..e896a223e 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ActiveMQEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ActiveMQEvent.java @@ -18,6 +18,7 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import java.util.List; +import java.util.Map; /** * Represents an Active MQ event sent to Lambda @@ -52,6 +53,7 @@ public static class ActiveMQMessage { private String data; private long brokerInTime; private long brokerOutTime; + private Map properties; } @Data diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolCreateAuthChallengeEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolCreateAuthChallengeEvent.java index 6739d27f4..6074ca9b5 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolCreateAuthChallengeEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolCreateAuthChallengeEvent.java @@ -26,6 +26,7 @@ @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor +@ToString(callSuper = true) public class CognitoUserPoolCreateAuthChallengeEvent extends CognitoUserPoolEvent { /** @@ -56,6 +57,7 @@ public CognitoUserPoolCreateAuthChallengeEvent( @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor + @ToString(callSuper = true) public static class Request extends CognitoUserPoolEvent.Request { /** * One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the create auth challenge trigger. diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolCustomMessageEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolCustomMessageEvent.java index f8642c75c..403f85393 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolCustomMessageEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolCustomMessageEvent.java @@ -26,6 +26,7 @@ @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor +@ToString(callSuper = true) public class CognitoUserPoolCustomMessageEvent extends CognitoUserPoolEvent { /** * The request from the Amazon Cognito service. @@ -55,6 +56,7 @@ public CognitoUserPoolCustomMessageEvent( @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor + @ToString(callSuper = true) public static class Request extends CognitoUserPoolEvent.Request { /** * One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the custom message trigger. diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolDefineAuthChallengeEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolDefineAuthChallengeEvent.java index 33fcf53ad..8577c9f7a 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolDefineAuthChallengeEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolDefineAuthChallengeEvent.java @@ -26,6 +26,7 @@ @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor +@ToString(callSuper = true) public class CognitoUserPoolDefineAuthChallengeEvent extends CognitoUserPoolEvent { /** @@ -56,6 +57,7 @@ public CognitoUserPoolDefineAuthChallengeEvent( @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor + @ToString(callSuper = true) public static class Request extends CognitoUserPoolEvent.Request { /** * One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the define auth challenge trigger. diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolMigrateUserEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolMigrateUserEvent.java index ee47c360e..381010a76 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolMigrateUserEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolMigrateUserEvent.java @@ -26,6 +26,7 @@ @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor +@ToString(callSuper = true) public class CognitoUserPoolMigrateUserEvent extends CognitoUserPoolEvent { /** * The request from the Amazon Cognito service. @@ -55,6 +56,7 @@ public CognitoUserPoolMigrateUserEvent( @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor + @ToString(callSuper = true) public static class Request extends CognitoUserPoolEvent.Request { /** * The username entered by the user. diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPostAuthenticationEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPostAuthenticationEvent.java index 5d2f5089f..de1af6565 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPostAuthenticationEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPostAuthenticationEvent.java @@ -16,6 +16,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.ToString; import java.util.Map; @@ -29,6 +30,7 @@ @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor +@ToString(callSuper = true) public class CognitoUserPoolPostAuthenticationEvent extends CognitoUserPoolEvent { /** @@ -52,6 +54,7 @@ public CognitoUserPoolPostAuthenticationEvent( @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor + @ToString(callSuper = true) public static class Request extends CognitoUserPoolEvent.Request { /** * One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the post authentication trigger. diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPostConfirmationEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPostConfirmationEvent.java index 340b2a885..4a835489d 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPostConfirmationEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPostConfirmationEvent.java @@ -16,6 +16,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.ToString; import java.util.Map; @@ -29,6 +30,7 @@ @EqualsAndHashCode(callSuper = true) @Data @NoArgsConstructor +@ToString(callSuper = true) public class CognitoUserPoolPostConfirmationEvent extends CognitoUserPoolEvent { /** @@ -52,6 +54,7 @@ public CognitoUserPoolPostConfirmationEvent( @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor + @ToString(callSuper = true) public static class Request extends CognitoUserPoolEvent.Request { /** * One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the post confirmation trigger. diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreAuthenticationEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreAuthenticationEvent.java index 26f45bb99..110160415 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreAuthenticationEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreAuthenticationEvent.java @@ -16,6 +16,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.ToString; import java.util.Map; @@ -29,6 +30,7 @@ @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor +@ToString(callSuper = true) public class CognitoUserPoolPreAuthenticationEvent extends CognitoUserPoolEvent { /** @@ -52,6 +54,7 @@ public CognitoUserPoolPreAuthenticationEvent( @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor + @ToString(callSuper = true) public static class Request extends CognitoUserPoolEvent.Request { /** * One or more name-value pairs containing the validation data in the request to register a user. diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreSignUpEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreSignUpEvent.java index 2a7f0e3fc..da7a848e5 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreSignUpEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreSignUpEvent.java @@ -26,6 +26,7 @@ @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor +@ToString(callSuper = true) public class CognitoUserPoolPreSignUpEvent extends CognitoUserPoolEvent { /** @@ -56,6 +57,7 @@ public CognitoUserPoolPreSignUpEvent( @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor + @ToString(callSuper = true) public static class Request extends CognitoUserPoolEvent.Request { /** * One or more name-value pairs containing the validation data in the request to register a user. diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreTokenGenerationEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreTokenGenerationEvent.java index db73bbd99..e49ce3c40 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreTokenGenerationEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreTokenGenerationEvent.java @@ -26,6 +26,7 @@ @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor +@ToString(callSuper = true) public class CognitoUserPoolPreTokenGenerationEvent extends CognitoUserPoolEvent { /** * The request from the Amazon Cognito service. @@ -55,6 +56,7 @@ public CognitoUserPoolPreTokenGenerationEvent( @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor + @ToString(callSuper = true) public static class Request extends CognitoUserPoolEvent.Request { /** * One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the pre token generation trigger. diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolVerifyAuthChallengeResponseEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolVerifyAuthChallengeResponseEvent.java index 13aafb554..982ff72fd 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolVerifyAuthChallengeResponseEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolVerifyAuthChallengeResponseEvent.java @@ -26,6 +26,7 @@ @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor +@ToString(callSuper = true) public class CognitoUserPoolVerifyAuthChallengeResponseEvent extends CognitoUserPoolEvent { /** * The request from the Amazon Cognito service. @@ -55,6 +56,7 @@ public CognitoUserPoolVerifyAuthChallengeResponseEvent( @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor + @ToString(callSuper = true) public static class Request extends CognitoUserPoolEvent.Request { /** * One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the verify auth challenge trigger. @@ -67,7 +69,7 @@ public static class Request extends CognitoUserPoolEvent.Request { /** * The answer from the user's response to the challenge. */ - private Map challengeAnswer; + private String challengeAnswer; /** * This boolean is populated when PreventUserExistenceErrors is set to ENABLED for your User Pool client */ @@ -76,7 +78,7 @@ public static class Request extends CognitoUserPoolEvent.Request { @Builder(setterPrefix = "with") public Request(Map userAttributes, Map clientMetadata, - Map challengeAnswer, + String challengeAnswer, Map privateChallengeParameters, boolean userNotFound) { super(userAttributes); diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ScheduledEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ScheduledEvent.java index 5908c39c3..405ede583 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ScheduledEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ScheduledEvent.java @@ -26,6 +26,8 @@ public class ScheduledEvent implements Serializable, Cloneable { private static final long serialVersionUID = -5810383198587331146L; + private String version; + private String account; private String region; @@ -47,6 +49,29 @@ public class ScheduledEvent implements Serializable, Cloneable { */ public ScheduledEvent() {} + /** + * @return the version number + */ + public String getVersion() { + return version; + } + + /** + * @param version the version number + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * @param version version number + * @return ScheduledEvent + */ + public ScheduledEvent withVersion(String version) { + setVersion(version); + return this; + } + /** * @return the account id */ @@ -69,7 +94,7 @@ public ScheduledEvent withAccount(String account) { setAccount(account); return this; } - + /** * @return the aws region */ @@ -92,7 +117,7 @@ public ScheduledEvent withRegion(String region) { setRegion(region); return this; } - + /** * @return The details of the events (usually left blank) */ @@ -115,7 +140,7 @@ public ScheduledEvent withDetail(Map detail) { setDetail(detail); return this; } - + /** * @return The details type - see cloud watch events for more info */ @@ -138,19 +163,19 @@ public ScheduledEvent withDetailType(String detailType) { setDetailType(detailType); return this; } - + /** - * @return the soruce of the event + * @return the source of the event */ public String getSource() { return source; } /** - * @param soruce the soruce of the event + * @param source the source of the event */ - public void setSource(String soruce) { - this.source = soruce; + public void setSource(String source) { + this.source = source; } /** @@ -161,7 +186,7 @@ public ScheduledEvent withSource(String source) { setSource(source); return this; } - + /** * @return the timestamp for when the event is scheduled */ @@ -184,7 +209,7 @@ public ScheduledEvent withTime(DateTime time) { setTime(time); return this; } - + /** * @return the id of the event */ @@ -207,7 +232,7 @@ public ScheduledEvent withId(String id) { setId(id); return this; } - + /** * @return the resources used by event */ @@ -242,6 +267,8 @@ public ScheduledEvent withResources(List resources) { public String toString() { StringBuilder sb = new StringBuilder(); sb.append("{"); + if (getVersion() != null) + sb.append("version: ").append(getVersion()).append(","); if (getAccount() != null) sb.append("account: ").append(getAccount()).append(","); if (getRegion() != null) @@ -272,6 +299,10 @@ public boolean equals(Object obj) { if (obj instanceof ScheduledEvent == false) return false; ScheduledEvent other = (ScheduledEvent) obj; + if (other.getVersion() == null ^ this.getVersion() == null) + return false; + if (other.getVersion() != null && other.getVersion().equals(this.getVersion()) == false) + return false; if (other.getAccount() == null ^ this.getAccount() == null) return false; if (other.getAccount() != null && other.getAccount().equals(this.getAccount()) == false) @@ -312,6 +343,7 @@ public int hashCode() { final int prime = 31; int hashCode = 1; + hashCode = prime * hashCode + ((getVersion() == null) ? 0 : getVersion().hashCode()); hashCode = prime * hashCode + ((getAccount() == null) ? 0 : getAccount().hashCode()); hashCode = prime * hashCode + ((getRegion() == null) ? 0 : getRegion().hashCode()); hashCode = prime * hashCode + ((getDetail() == null) ? 0 : getDetail().hashCode()); @@ -331,5 +363,5 @@ public ScheduledEvent clone() { throw new IllegalStateException("Got a CloneNotSupportedException from Object.clone()", e); } } - + } diff --git a/aws-lambda-java-log4j2/README.md b/aws-lambda-java-log4j2/README.md index 3ccb07698..b1b739b69 100644 --- a/aws-lambda-java-log4j2/README.md +++ b/aws-lambda-java-log4j2/README.md @@ -10,7 +10,7 @@ Example for Maven pom.xml com.amazonaws aws-lambda-java-log4j2 - 1.5.1 + 1.6.0 org.apache.logging.log4j @@ -22,6 +22,11 @@ Example for Maven pom.xml log4j-api 2.17.1 + + org.apache.logging.log4j + log4j-layout-template-json + 2.17.1 + .... ``` @@ -65,10 +70,10 @@ If using maven shade plugin, set the plugin configuration as follows If you are using the [John Rengelman](https://github.com/johnrengelman/shadow) Gradle shadow plugin, then the plugin configuration is as follows: ```groovy - + dependencies{ ... - implementation group: 'com.amazonaws', name: 'aws-lambda-java-log4j2', version: '1.5.1' + implementation group: 'com.amazonaws', name: 'aws-lambda-java-log4j2', version: '1.6.0' implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4jVersion implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4jVersion } @@ -83,8 +88,8 @@ shadowJar { build.dependsOn(shadowJar) ``` - -If you are using the `sam build` and `sam deploy` commands to deploy your lambda function, then you don't + +If you are using the `sam build` and `sam deploy` commands to deploy your lambda function, then you don't need to use the shadow jar plugin. The `sam` cli-tool merges itself the `Log4j2Plugins.dat` files. @@ -94,22 +99,29 @@ Add the following file `/src/main/resources/log4j2.xml` ```xml - + - - + + + %d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1}:%L - %m%n - + + + + + - + ``` +If the `AWS_LAMBDA_LOG_FORMAT` is set to `JSON`, the `LambdaJSONFormat` formatter will be applied, otherwise the `LambdaTextFormat`. + ### 3. Example code ```java @@ -117,6 +129,8 @@ package example; import com.amazonaws.services.lambda.runtime.Context; +import static org.apache.logging.log4j.CloseableThreadContext.put; +import org.apache.logging.log4j.CloseableThreadContext.Instance; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -136,6 +150,12 @@ public class Hello { logger.error("log data from log4j err. \n this is a continuation of log4j.err"); + // When logging in JSON, you can also add custom fields + // In java11+ you can use the `try (var ctx = put("name", name)) {}` structure + Instance ctx = put("name", name); + logger.info("log line with input name"); + ctx.close(); + // Return will include the log stream name so you can look // up the log later. return String.format("Hello %s. log stream = %s", name, context.getLogStreamName()); diff --git a/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md b/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md index 2277b7921..49535d388 100644 --- a/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### October 24, 2023 +`1.6.0`: +- Log level and log format support + ### January 04, 2022 `1.5.1`: - Updated `log4j-core` and `log4j-api` dependencies to `2.17.1` diff --git a/aws-lambda-java-log4j2/pom.xml b/aws-lambda-java-log4j2/pom.xml index 9b048456d..b33300ef2 100644 --- a/aws-lambda-java-log4j2/pom.xml +++ b/aws-lambda-java-log4j2/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-log4j2 - 1.5.1 + 1.6.0 jar AWS Lambda Java Log4j 2.x Libraries @@ -48,7 +48,7 @@ com.amazonaws aws-lambda-java-core - 1.2.1 + 1.2.3 org.apache.logging.log4j diff --git a/aws-lambda-java-log4j2/src/main/java/com/amazonaws/services/lambda/runtime/log4j2/LambdaAppender.java b/aws-lambda-java-log4j2/src/main/java/com/amazonaws/services/lambda/runtime/log4j2/LambdaAppender.java index 5c1dd3158..a511c8dea 100755 --- a/aws-lambda-java-log4j2/src/main/java/com/amazonaws/services/lambda/runtime/log4j2/LambdaAppender.java +++ b/aws-lambda-java-log4j2/src/main/java/com/amazonaws/services/lambda/runtime/log4j2/LambdaAppender.java @@ -2,16 +2,24 @@ import com.amazonaws.services.lambda.runtime.LambdaRuntime; import com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal; + import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; +import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.core.config.plugins.PluginElement; import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; /** * Class to append log4j2 logs from AWS Lambda function to CloudWatch @@ -20,6 +28,9 @@ @Plugin(name = LambdaAppender.PLUGIN_NAME, category = LambdaAppender.PLUGIN_CATEGORY, elementType = LambdaAppender.PLUGIN_TYPE, printObject = true) public class LambdaAppender extends AbstractAppender { + static { + LambdaRuntimeInternal.setUseLog4jAppender(true); + } public static final String PLUGIN_NAME = "Lambda"; public static final String PLUGIN_CATEGORY = "Core"; @@ -27,6 +38,17 @@ public class LambdaAppender extends AbstractAppender { private LambdaLogger logger = LambdaRuntime.getLogger(); + private static LogFormat logFormat = LogFormat.TEXT; + + private static final Map logLevelMapper = new HashMap() {{ + put(Level.TRACE, LogLevel.TRACE); + put(Level.DEBUG, LogLevel.DEBUG); + put(Level.INFO, LogLevel.INFO); + put(Level.WARN, LogLevel.WARN); + put(Level.ERROR, LogLevel.ERROR); + put(Level.FATAL, LogLevel.FATAL); + }}; + /** * Builder class that follows log4j2 plugin convention * @param Generic Builder class @@ -34,13 +56,25 @@ public class LambdaAppender extends AbstractAppender { public static class Builder> extends AbstractAppender.Builder implements org.apache.logging.log4j.core.util.Builder { + @PluginAttribute(value = "format", defaultString = "TEXT") + LogFormat logFormat; + @PluginElement("LambdaTextFormat") + private LambdaTextFormat lambdaTextFormat; + @PluginElement("LambdaJsonFormat") + private LambdaJsonFormat lambdaJsonFormat; + /** * creates a new LambdaAppender * @return a new LambdaAppender */ public LambdaAppender build() { - return new LambdaAppender(super.getName(), super.getFilter(), super.getOrCreateLayout(), - super.isIgnoreExceptions()); + Layout layout; + if (logFormat == LogFormat.TEXT) { + layout = lambdaTextFormat != null ? lambdaTextFormat.getLayout() : super.getOrCreateLayout(); + } else { + layout = lambdaJsonFormat != null ? lambdaJsonFormat.getLayout() : super.getOrCreateLayout(); + } + return new LambdaAppender(super.getName(), super.getFilter(), layout, super.isIgnoreExceptions()); } } @@ -63,7 +97,15 @@ public static > B newBuilder() { */ private LambdaAppender(String name, Filter filter, Layout layout, boolean ignoreExceptions) { super(name, filter, layout, ignoreExceptions); - LambdaRuntimeInternal.setUseLog4jAppender(true); + } + + /** + * Converts log4j Level into Lambda LogLevel + * @param level the log4j log level + * @return Lambda log leve + */ + private LogLevel toLambdaLogLevel(Level level) { + return logLevelMapper.getOrDefault(level, LogLevel.UNDEFINED); } /** @@ -71,6 +113,6 @@ private LambdaAppender(String name, Filter filter, Layout layout) { + return new LambdaJsonFormat(layout); + } + + private LambdaJsonFormat(Layout layout) { + this.layout = layout; + } + + public Layout getLayout() { + return layout; + } +} diff --git a/aws-lambda-java-log4j2/src/main/java/com/amazonaws/services/lambda/runtime/log4j2/LambdaTextFormat.java b/aws-lambda-java-log4j2/src/main/java/com/amazonaws/services/lambda/runtime/log4j2/LambdaTextFormat.java new file mode 100644 index 000000000..0bd0304a0 --- /dev/null +++ b/aws-lambda-java-log4j2/src/main/java/com/amazonaws/services/lambda/runtime/log4j2/LambdaTextFormat.java @@ -0,0 +1,29 @@ +/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.log4j2; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +import java.io.Serializable; + +@Plugin(name = "LambdaTextFormat", category = "core", printObject = true) +public class LambdaTextFormat { + + private Layout layout; + + @PluginFactory + public static LambdaTextFormat createNode(@PluginElement("Layout") Layout layout) { + return new LambdaTextFormat(layout); + } + + private LambdaTextFormat(Layout layout) { + this.layout = layout; + } + + public Layout getLayout() { + return layout; + } +} diff --git a/aws-lambda-java-log4j2/src/main/resources/LambdaLayout.json b/aws-lambda-java-log4j2/src/main/resources/LambdaLayout.json new file mode 100644 index 000000000..975f4b529 --- /dev/null +++ b/aws-lambda-java-log4j2/src/main/resources/LambdaLayout.json @@ -0,0 +1,39 @@ +{ + "timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "timeZone": "UTC" + } + }, + "level": { + "$resolver": "level", + "field": "name" + }, + "message": { + "$resolver": "message" + }, + "logger": { + "$resolver": "logger", + "field": "name" + }, + + "errorType": { + "$resolver": "exception", + "field": "className" + }, + "errorMessage": { + "$resolver": "exception", + "field": "message" + }, + "stackTrace": { + "$resolver": "exception", + "field": "stackTrace" + }, + + "labels": { + "$resolver": "mdc", + "flatten": true, + "stringified": true + } +} \ 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 4977fb6c4..e57daf3a8 100644 --- a/aws-lambda-java-runtime-interface-client/Makefile +++ b/aws-lambda-java-runtime-interface-client/Makefile @@ -45,11 +45,19 @@ pr: test test-smoke .PHONY: build build: mvn clean install + mvn install -P linux-x86_64 + mvn install -P linux_musl-x86_64 + mvn install -P linux-aarch64 + mvn install -P linux_musl-aarch64 .PHONY: publish publish: ./ric-dev-environment/publish_snapshot.sh +.PHONY: publish +test-publish: + ./ric-dev-environment/test-platform-specific-jar-snapshot.sh + define HELP_MESSAGE Usage: $ make [TARGETS] diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 25cec202f..c9194c0c9 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.2.0 + 2.4.1 @@ -151,6 +151,33 @@ DOCKERHUB_PASSWORD= ``` Recommended way is to set the Docker Hub credentials in CodeBuild job by retrieving them from AWS Secrets Manager. +## Configuration +The `aws-lambda-java-runtime-interface-client` JAR is a large uber jar, which contains compiled C libraries +for x86_64 and aarch_64 for glibc and musl LIBC implementations. If the size is an issue, you can pick a smaller +platform-specific JAR by setting the ``. +``` + + + com.amazonaws + aws-lambda-java-runtime-interface-client + 2.4.1 + linux-x86_64 + +``` + +Available platform classifiers: `linux-x86_64`, `linux-aarch_64`, `linux_musl-aarch_64`, `linux_musl-x86_64` + +The Lambda runtime interface client tries to load compatible library during execution, by unpacking it to a temporary +location `/tmp/.libaws-lambda-jni.so`. +If this behaviour is not desirable, it is possible to extract the `.so` files during build time and specify the location via +`com.amazonaws.services.lambda.runtime.api.client.runtimeapi.NativeClient.JNI` system property, like +``` +ENTRYPOINT [ "/usr/bin/java", +"-Dcom.amazonaws.services.lambda.runtime.api.client.runtimeapi.NativeClient.JNI=/function/libaws-lambda-jni.linux_x86_64.so" +"-cp", "./*", +"com.amazonaws.services.lambda.runtime.api.client.AWSLambda" ] +``` + ## Security If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index c0c93e1ee..09a85363b 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,4 +1,33 @@ -### February 3, 2022 +### September 4, 2023 +`2.4.1` +- Null pointer bugfix ([#439](https://github.com/aws/aws-lambda-java-libs/pull/439)) + +### August 29, 2023 +`2.4.0` +- Logging improvements ([#436](https://github.com/aws/aws-lambda-java-libs/pull/436)) + +### July 17, 2023 +`2.3.3` +- Build platform specific JAR files +- NativeClient optimisations + +### April 14, 2023 +`2.3.2` +- Add curl patch + +### March 16, 2023 +`2.3.1` +- ignore module-info for CDS preparation purposes +- clear thread interrupted flag instead of exiting Lambda Runtime + +### March 14, 2023 +`2.3.0` +- added CRaC context implementation +- added runtime hooks execution logic +- updated serialisation dependency +- reduced Reflection API usage + +### February 3, 2023 `2.2.0` - Added timestamps to TLV - Removed legacy `init` method support diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 8de738de6..787ab5c19 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -1,279 +1,345 @@ - 4.0.0 + 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 + io.github.crac.com.amazonaws + aws-lambda-java-runtime-interface-client + 2.4.1.CRAC.0 + jar - com.amazonaws - aws-lambda-java-runtime-interface-client - 2.2.0 - jar + AWS Lambda Java Runtime Interface Client + + The AWS Lambda Java Runtime Interface Client implements the Lambda programming model for Java + + https://aws.amazon.com/lambda/ + + + Apache License, Version 2.0 + https://aws.amazon.com/apache2.0 + repo + + + + http://github.com/CRaC/aws-lambda-java-libs/tree/master + + + + antonkozlov + Anton Kozlov + https://github.com/AntonKozlov + akozlov@azul.com + + + AWS Lambda team + Amazon Web Services + https://aws.amazon.com/ + + - AWS Lambda Java Runtime Interface Client - - The AWS Lambda Java Runtime Interface Client implements the Lambda programming model for Java - - https://aws.amazon.com/lambda/ - - - Apache License, Version 2.0 - https://aws.amazon.com/apache2.0 - repo - - - - https://github.com/aws/aws-lambda-java-libs.git - - - - AWS Lambda team - Amazon Web Services - https://aws.amazon.com/ - - + + UTF-8 + UTF-8 + 0.8.8 + 5.9.2 + + true + + + + - - UTF-8 - UTF-8 - 1.8 - 1.8 - 0.8.8 - - true - + + + com.amazonaws + aws-lambda-java-core + 1.2.3 + + + com.amazonaws + aws-lambda-java-serialization + 1.1.2 + + + org.crac + crac + 1.4.0 + - - - com.amazonaws - aws-lambda-java-core - 1.2.2 - - - com.amazonaws - aws-lambda-java-serialization - 1.0.0 - + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + org.junit.jupiter + junit-jupiter + ${junit-jupiter.version} + test + + + org.mockito + mockito-core + 4.11.0 + test + + - - org.junit.jupiter - junit-jupiter-engine - 5.7.0 - test - - - org.junit.jupiter - junit-jupiter-api - 5.7.0 - test - - - - - - - com.allogy.maven.wagon - maven-s3-wagon - 1.2.0 - - - - - maven-surefire-plugin - 2.22.2 - - - maven-failsafe-plugin - 2.22.2 - - - org.apache.maven.plugins - maven-antrun-plugin - 1.7 - - - build-jni-lib - prepare-package - - run - - - - - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - UTF-8 - ${maven.compiler.source} - ${maven.compiler.target} - - - - org.apache.maven.plugins - maven-jar-plugin - 3.3.0 - - - - com.amazonaws.services.lambda.runtime.api.client.AWSLambda - true - true - - - - - - org.jacoco - jacoco-maven-plugin - ${jacoco.maven.plugin.version} - - - default-prepare-agent - - prepare-agent - - - - default-report - test - - report - - - - default-check - test - - check - - - - - PACKAGE - - - LINE - COVEREDRATIO - 0 - - - - - - - - - - - - - - - dev - + + + + com.allogy.maven.wagon + maven-s3-wagon + 1.2.0 + + - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - -Xdoclint:none - - - - attach-javadocs - - jar - - - - + + maven-surefire-plugin + 3.0.0-M9 + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + + + + + maven-failsafe-plugin + 2.22.2 + + + org.apache.maven.plugins + maven-antrun-plugin + 1.7 + + + build-jni-lib + prepare-package + + run + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 1.8 + 1.8 + UTF-8 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + com.amazonaws.services.lambda.runtime.api.client.AWSLambda + true + true + + + ${ric.classifier} + + com/ + jni/*${ric.classifier}.so + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.maven.plugin.version} + + + default-prepare-agent + + prepare-agent + + + + default-report + test + + report + + + + default-check + test + + check + + + + + PACKAGE + + + LINE + COVEREDRATIO + 0 + + + + + + + + - - - - release - - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - -Xdoclint:none - - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 - true - - sonatype-nexus-staging - https://aws.oss.sonatype.org/ - false - - - - - - - ci-repo - - - ci-repo - ${env.MAVEN_REPO_URL} - - - - + + + + + + dev + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + -Xdoclint:none + + + + attach-javadocs + + jar + + + + + + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + -Xdoclint:none + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.3 + true + + ossrh + https://s01.oss.sonatype.org/ + false + + + + + + + ci-repo + + + ci-repo + ${env.MAVEN_REPO_URL} + + + + + linux-x86_64 + + linux + x86_64 + linux-x86_64 + + + + linux_musl-x86_64 + + linux_musl + x86_64 + linux_musl-x86_64 + + + + linux-aarch64 + + linux + aarch_64 + linux-aarch_64 + + + + linux_musl-aarch64 + + linux_musl + aarch_64 + linux_musl-aarch_64 + + + diff --git a/aws-lambda-java-runtime-interface-client/ric-dev-environment/publish_snapshot.sh b/aws-lambda-java-runtime-interface-client/ric-dev-environment/publish_snapshot.sh index 58e521fba..cf5969e1f 100755 --- a/aws-lambda-java-runtime-interface-client/ric-dev-environment/publish_snapshot.sh +++ b/aws-lambda-java-runtime-interface-client/ric-dev-environment/publish_snapshot.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash -x set -e @@ -18,5 +18,33 @@ else echo "Already -SNAPSHOT version" fi -mvn -P ci-repo deploy --settings ric-dev-environment/settings.xml -mv pom.xml.versionsBackup pom.xml +CLASSIFIERS_ARRAY=("linux-x86_64" "linux_musl-x86_64" "linux-aarch_64" "linux_musl-aarch_64") + +for str in "${CLASSIFIERS_ARRAY[@]}"; do + FILES="${FILES}target/aws-lambda-java-runtime-interface-client-$projectVersion-$str.jar," + CLASSIFIERS="${CLASSIFIERS}${str}," + TYPES="${TYPES}jar," +done + +# remove the last "," +FILES=${FILES%?} +CLASSIFIERS=${CLASSIFIERS%?} +TYPES=${TYPES%?} + +mvn -B -X -P ci-repo \ + deploy:deploy-file \ + -DgroupId=com.amazonaws \ + -DartifactId=aws-lambda-java-runtime-interface-client \ + -Dpackaging=jar \ + -Dversion=$projectVersion \ + -Dfile=./target/aws-lambda-java-runtime-interface-client-$projectVersion.jar \ + -Dfiles=$FILES \ + -Dclassifiers=$CLASSIFIERS \ + -Dtypes=$TYPES \ + -DpomFile=pom.xml \ + -DrepositoryId=ci-repo -Durl=$MAVEN_REPO_URL \ + --settings ric-dev-environment/settings.xml + +if [ -f pom.xml.versionsBackup ]; then + mv pom.xml.versionsBackup pom.xml +fi diff --git a/aws-lambda-java-runtime-interface-client/ric-dev-environment/test-platform-specific-jar-snapshot.sh b/aws-lambda-java-runtime-interface-client/ric-dev-environment/test-platform-specific-jar-snapshot.sh new file mode 100755 index 000000000..c9eced5cb --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/ric-dev-environment/test-platform-specific-jar-snapshot.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -e + +projectVersion=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + + +# test uber jar +mvn -B -X -P ci-repo \ + dependency:get \ + -DremoteRepositories=ci-repo::::$MAVEN_REPO_URL \ + -Dartifact=com.amazonaws:aws-lambda-java-runtime-interface-client:${projectVersion}-SNAPSHOT \ + -Dtransitive=false \ + --settings ric-dev-environment/settings.xml + + +PLATFORM_ARRAY=("linux-x86_64" "linux_musl-x86_64" "linux-aarch_64" "linux_musl-aarch_64") + +for classifier in "${PLATFORM_ARRAY[@]}"; do + # Test platform specific jar + mvn -B -P ci-repo \ + dependency:get \ + -DremoteRepositories=ci-repo::::$MAVEN_REPO_URL \ + -Dartifact=com.amazonaws:aws-lambda-java-runtime-interface-client:${projectVersion}-SNAPSHOT:jar:${classifier} \ + -Dtransitive=false \ + --settings ric-dev-environment/settings.xml +done \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/CheckpointException.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/CheckpointException.java new file mode 100644 index 000000000..8793bfcba --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/CheckpointException.java @@ -0,0 +1,12 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + */ + +package com.amazonaws.services.lambda.crac; + +public class CheckpointException extends Exception { + private static final long serialVersionUID = -4956873658083157585L; + public CheckpointException() { + super(); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/Context.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/Context.java new file mode 100644 index 000000000..2fc22cdd6 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/Context.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + */ + +package com.amazonaws.services.lambda.crac; + +public abstract class Context implements Resource { + + protected Context() { + } + + @Override + public abstract void beforeCheckpoint(Context context) + throws CheckpointException; + + @Override + public abstract void afterRestore(Context context) + throws RestoreException; + + public abstract void register(R resource); +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/ContextImpl.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/ContextImpl.java new file mode 100644 index 000000000..ac4bc6480 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/ContextImpl.java @@ -0,0 +1,90 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + */ + +package com.amazonaws.services.lambda.crac; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.stream.Collectors; + + +/** + * Spec reference: https://crac.github.io/openjdk-builds/javadoc/api/java.base/jdk/crac/package-summary.html + */ + +public class ContextImpl extends Context { + + private volatile long order = -1L; + private final WeakHashMap checkpointQueue = new WeakHashMap<>(); + + @Override + public synchronized void beforeCheckpoint(Context context) throws CheckpointException { + + List exceptionsThrown = new ArrayList<>(); + for (Resource resource : getCheckpointQueueReverseOrderOfRegistration()) { + try { + resource.beforeCheckpoint(this); + } catch (CheckpointException e) { + Collections.addAll(exceptionsThrown, e.getSuppressed()); + } catch (Exception e) { + exceptionsThrown.add(e); + } + } + + if (!exceptionsThrown.isEmpty()) { + CheckpointException checkpointException = new CheckpointException(); + for (Throwable t : exceptionsThrown) { + checkpointException.addSuppressed(t); + } + throw checkpointException; + } + } + + @Override + public synchronized void afterRestore(Context context) throws RestoreException { + + List exceptionsThrown = new ArrayList<>(); + for (Resource resource : getCheckpointQueueForwardOrderOfRegistration()) { + try { + resource.afterRestore(this); + } catch (RestoreException e) { + Collections.addAll(exceptionsThrown, e.getSuppressed()); + } catch (Exception e) { + exceptionsThrown.add(e); + } + } + + if (!exceptionsThrown.isEmpty()) { + RestoreException restoreException = new RestoreException(); + for (Throwable t : exceptionsThrown) { + restoreException.addSuppressed(t); + } + throw restoreException; + } + } + + @Override + public synchronized void register(Resource resource) { + checkpointQueue.put(resource, ++order); + } + + private List getCheckpointQueueReverseOrderOfRegistration() { + return checkpointQueue.entrySet() + .stream() + .sorted((r1, r2) -> (int) (r2.getValue() - r1.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + + private List getCheckpointQueueForwardOrderOfRegistration() { + return checkpointQueue.entrySet() + .stream() + .sorted((r1, r2) -> (int) (r1.getValue() - r2.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/Core.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/Core.java new file mode 100644 index 000000000..b6a7d9e98 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/Core.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + */ + +package com.amazonaws.services.lambda.crac; + +/** + * Provides the global context for registering resources. + */ +public final class Core { + + private Core() { + } + + private static Context globalContext = new ContextImpl(); + + public static Context getGlobalContext() { + return globalContext; + } + + public static void checkpointRestore() { + throw new UnsupportedOperationException(); + } + + static void resetGlobalContext() { + globalContext = new ContextImpl(); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/Resource.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/Resource.java new file mode 100644 index 000000000..8c8d91c37 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/Resource.java @@ -0,0 +1,11 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + */ + +package com.amazonaws.services.lambda.crac; + +public interface Resource { + void afterRestore(Context context) throws Exception; + + void beforeCheckpoint(Context context) throws Exception; +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/RestoreException.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/RestoreException.java new file mode 100644 index 000000000..d045a0bad --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/RestoreException.java @@ -0,0 +1,13 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + */ + +package com.amazonaws.services.lambda.crac; + +public class RestoreException extends Exception { + private static final long serialVersionUID = -823900409868237860L; + + public RestoreException() { + super(); + } +} 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 5a0650f09..1424de0cc 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 @@ -5,6 +5,7 @@ // package com.amazonaws.services.lambda.runtime.api.client; +import com.amazonaws.services.lambda.crac.Core; import com.amazonaws.services.lambda.runtime.LambdaLogger; import com.amazonaws.services.lambda.runtime.api.client.LambdaRequestHandler.UserFaultHandler; import com.amazonaws.services.lambda.runtime.api.client.logging.FramedTelemetryLogSink; @@ -15,6 +16,8 @@ import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClient; 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; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.factories.GsonFactory; import com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory; @@ -32,6 +35,7 @@ import java.security.Security; import java.util.Properties; + /** * The entrypoint of this class is {@link AWSLambda#startRuntime}. It performs two main tasks: * @@ -59,6 +63,10 @@ public class AWSLambda { // https://github.com/aws/aws-xray-sdk-java/blob/2f467e50db61abb2ed2bd630efc21bddeabd64d9/aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/contexts/LambdaSegmentContext.java#L39-L40 private static final String LAMBDA_TRACE_HEADER_PROP = "com.amazonaws.xray.traceHeader"; + 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); + static { // Override the disabledAlgorithms setting to match configuration for openjdk8-u181. // This is to keep DES ciphers around while we deploying security updates. @@ -126,7 +134,7 @@ private static LambdaRequestHandler findRequestHandler(final String handlerStrin } public static void setupRuntimeLogger(LambdaLogger lambdaLogger) - throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException { + throws ClassNotFoundException { ReflectUtil.setStaticField( Class.forName("com.amazonaws.services.lambda.runtime.LambdaRuntime"), "logger", @@ -152,9 +160,9 @@ public static String getEnvOrExit(String envVariableName) { private static FileDescriptor intToFd(int fd) throws RuntimeException { try { Class clazz = FileDescriptor.class; - Constructor c = clazz.getDeclaredConstructor(new Class[]{Integer.TYPE}); + Constructor c = clazz.getDeclaredConstructor(Integer.TYPE); c.setAccessible(true); - return c.newInstance(new Integer(fd)); + return c.newInstance(fd); } catch (Exception e) { throw new RuntimeException(e); } @@ -176,13 +184,17 @@ private static LogSink createLogSink() { } public static void main(String[] args) { - // TODO validate arguments, show usage startRuntime(args[0]); } private static void startRuntime(String handler) { try (LogSink logSink = createLogSink()) { - startRuntime(handler, new LambdaContextLogger(logSink)); + LambdaLogger logger = new LambdaContextLogger( + logSink, + LogLevel.fromString(LambdaEnvironment.LAMBDA_LOG_LEVEL), + LogFormat.fromString(LambdaEnvironment.LAMBDA_LOG_FORMAT) + ); + startRuntime(handler, logger); } catch (Throwable t) { throw new Error(t); } @@ -211,14 +223,13 @@ private static void startRuntime(String handler, LambdaLogger lambdaLogger) thro requestHandler = findRequestHandler(handler, customerClassLoader); } catch (UserFault userFault) { lambdaLogger.log(userFault.reportableError()); - ByteArrayOutputStream payload = new ByteArrayOutputStream(1024); - Failure failure = new Failure(userFault); - GsonFactory.getInstance().getSerializer(Failure.class).toJson(failure, payload); - runtimeClient.postInitError(payload.toByteArray(), failure.getErrorType()); + reportInitError(new Failure(userFault), runtimeClient); System.exit(1); return; } - + if (INIT_TYPE_SNAP_START.equals(AWS_LAMBDA_INITIALIZATION_TYPE)) { + onInitComplete(runtimeClient, lambdaLogger); + } boolean shouldExit = false; while (!shouldExit) { UserFault userFault = null; @@ -232,8 +243,8 @@ private static void startRuntime(String handler, LambdaLogger lambdaLogger) thro ByteArrayOutputStream payload; try { payload = requestHandler.call(request); - // TODO calling payload.toByteArray() creates a new copy of the underlying buffer runtimeClient.postInvocationResponse(request.getId(), payload.toByteArray()); + boolean ignored = Thread.interrupted(); // clear interrupted flag in case if it was set by user's code } catch (UserFault f) { userFault = f; UserFault.filterStackTrace(f); @@ -260,6 +271,49 @@ private static void startRuntime(String handler, LambdaLogger lambdaLogger) thro } } + static void onInitComplete(final LambdaRuntimeClient runtimeClient, final LambdaLogger lambdaLogger) throws IOException { + try { + Core.getGlobalContext().beforeCheckpoint(null); + // Blocking call to RAPID /restore/next API, will return after taking snapshot. + // This will also be the 'entrypoint' when resuming from snapshots. + runtimeClient.getRestoreNext(); + } catch (Exception e1) { + logExceptionCloudWatch(lambdaLogger, e1); + reportInitError(new Failure(e1), runtimeClient); + System.exit(64); + } + try { + Core.getGlobalContext().afterRestore(null); + } catch (Exception restoreExc) { + logExceptionCloudWatch(lambdaLogger, restoreExc); + Failure errorPayload = new Failure(restoreExc); + reportRestoreError(errorPayload, runtimeClient); + System.exit(64); + } + } + + private static void logExceptionCloudWatch(LambdaLogger lambdaLogger, Exception exc) { + UserFault.filterStackTrace(exc); + UserFault userFault = UserFault.makeUserFault(exc, true); + lambdaLogger.log(userFault.reportableError()); + } + + static void reportInitError(final Failure failure, + final LambdaRuntimeClient runtimeClient) throws IOException { + + ByteArrayOutputStream payload = new ByteArrayOutputStream(1024); + JacksonFactory.getInstance().getSerializer(Failure.class).toJson(failure, payload); + runtimeClient.postInitError(payload.toByteArray(), failure.getErrorType()); + } + + static int reportRestoreError(final Failure failure, + final LambdaRuntimeClient runtimeClient) throws IOException { + + ByteArrayOutputStream payload = new ByteArrayOutputStream(1024); + JacksonFactory.getInstance().getSerializer(Failure.class).toJson(failure, payload); + return runtimeClient.postRestoreError(payload.toByteArray(), failure.getErrorType()); + } + private static PojoSerializer xRayErrorCauseSerializer; /** diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoader.java index 6d8534bf3..8e72ea4a9 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoader.java @@ -13,7 +13,7 @@ /** * This class loads all of the classes that are in jars on the classpath. - * + *

* It is used to generate a class list and Application CDS archive that includes all the possible classes that could be * loaded by the runtime. This simplifies the process of generating the Application CDS archive. */ @@ -24,8 +24,8 @@ public class ClasspathLoader { private static final int CLASS_SUFFIX_LEN = ".class".length(); static { - // NativeClient loads a native library and crashes if loaded here so just exclude it - BLOCKLIST.add("com.amazonaws.services.lambda.runtime.api.client.runtimeapi.NativeClient"); + // Ignore module info class for serialization lib + BLOCKLIST.add("META-INF.versions.9.module-info"); } private static String pathToClassName(final String path) { @@ -36,7 +36,7 @@ private static void loadClass(String name) { try { Class.forName(name, true, SYSTEM_CLASS_LOADER); } catch (ClassNotFoundException e) { - System.err.println("[WARN] Failed to load " + name + ": " + e.getMessage()); + System.err.println("[WARN] Failed to load " + name + ": " + e.getMessage()); } } @@ -46,13 +46,13 @@ private static void loadClassesInJar(File file) throws IOException { while (en.hasMoreElements()) { JarEntry entry = en.nextElement(); - if(!entry.getName().endsWith(".class")) { + if (!entry.getName().endsWith(".class")) { continue; } String name = pathToClassName(entry.getName()); - if(BLOCKLIST.contains(name)) { + if (BLOCKLIST.contains(name)) { continue; } @@ -63,11 +63,11 @@ private static void loadClassesInJar(File file) throws IOException { private static void loadClassesInClasspathEntry(String entry) throws IOException { File file = new File(entry); - if(!file.exists()) { + if (!file.exists()) { throw new FileNotFoundException("Classpath entry does not exist: " + file.getPath()); } - if(file.isDirectory() || !file.getPath().endsWith(".jar")) { + if (file.isDirectory() || !file.getPath().endsWith(".jar")) { System.err.println("[WARN] Only jar classpath entries are supported. Skipping " + file.getPath()); return; } @@ -77,10 +77,10 @@ private static void loadClassesInClasspathEntry(String entry) throws IOException private static void loadAllClasses() throws IOException { final String classPath = System.getProperty("java.class.path"); - if(classPath == null) { + if (classPath == null) { return; } - for(String classPathEntry : classPath.split(File.pathSeparator)) { + for (String classPathEntry : classPath.split(File.pathSeparator)) { loadClassesInClasspathEntry(classPathEntry); } } 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 fc16d43f3..9e3c48ebb 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 @@ -4,13 +4,16 @@ import com.amazonaws.services.lambda.runtime.ClientContext; import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal; - +import com.amazonaws.services.lambda.runtime.api.client.LambdaRequestHandler.UserFaultHandler; import com.amazonaws.services.lambda.runtime.api.client.api.LambdaClientContext; import com.amazonaws.services.lambda.runtime.api.client.api.LambdaCognitoIdentity; import com.amazonaws.services.lambda.runtime.api.client.api.LambdaContext; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.InvocationRequest; import com.amazonaws.services.lambda.runtime.api.client.util.UnsafeUtil; import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers; @@ -18,15 +21,11 @@ import com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory; import com.amazonaws.services.lambda.runtime.serialization.util.Functions; import com.amazonaws.services.lambda.runtime.serialization.util.ReflectUtil; -import com.amazonaws.services.lambda.runtime.api.client.LambdaRequestHandler.UserFaultHandler; -import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.InvocationRequest; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -40,11 +39,10 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.Map; +import java.util.Objects; import java.util.Optional; -import static com.amazonaws.services.lambda.runtime.api.client.UserFault.filterStackTrace; -import static com.amazonaws.services.lambda.runtime.api.client.UserFault.makeUserFault; -import static com.amazonaws.services.lambda.runtime.api.client.UserFault.trace; +import static com.amazonaws.services.lambda.runtime.api.client.UserFault.*; public final class EventHandlerLoader { private static final byte[] _JsonNull = new byte[]{'n', 'u', 'l', 'l'}; @@ -57,32 +55,37 @@ private enum Platform { private static final EnumMap>> typeCache = new EnumMap<>(Platform.class); - private EventHandlerLoader() { } + private EventHandlerLoader() { + } /** * returns the appropriate serializer for the class based on platform and whether the class is a supported event + * * @param platform enum platform - * @param type Type of object used + * @param type Type of object used * @return PojoSerializer * @see Platform for which platforms are used * @see LambdaEventSerializers for how mixins and modules are added to the serializer */ @SuppressWarnings({"unchecked", "rawtypes"}) private static PojoSerializer getSerializer(Platform platform, Type type) { + PojoSerializer customSerializer = PojoSerializerLoader.getCustomerSerializer(type); + if (customSerializer != null) { + return customSerializer; + } + // if serializing a Class that is a Lambda supported event, use Jackson with customizations if (type instanceof Class) { - Class clazz = ((Class)type); + Class clazz = ((Class) type); if (LambdaEventSerializers.isLambdaSupportedEvent(clazz.getName())) { return LambdaEventSerializers.serializerFor(clazz, AWSLambda.customerClassLoader); } } // else platform dependent (Android uses GSON but all other platforms use Jackson) - switch (platform) { - case ANDROID: - return GsonFactory.getInstance().getSerializer(type); - default: - return JacksonFactory.getInstance().getSerializer(type); + if (Objects.requireNonNull(platform) == Platform.ANDROID) { + return GsonFactory.getInstance().getSerializer(type); } + return JacksonFactory.getInstance().getSerializer(type); } private static PojoSerializer getSerializerCached(Platform platform, Type type) { @@ -145,7 +148,7 @@ private static Platform getPlatform(Context context) { } private static boolean isVoid(Type type) { - return Void.TYPE.equals(type) || (type instanceof Class) && Void.class.isAssignableFrom((Class)type); + return Void.TYPE.equals(type) || (type instanceof Class) && Void.class.isAssignableFrom((Class) type); } /** @@ -388,7 +391,7 @@ public void handleRequest(InputStream inputStream, OutputStream outputStream, Co } } - public static Constructor getConstructor(Class clazz) throws Exception { + private static Constructor getConstructor(Class clazz) throws Exception { final Constructor constructor; try { constructor = clazz.getConstructor(); @@ -404,7 +407,7 @@ public static Constructor getConstructor(Class clazz) throws Exception return constructor; } - public static T newInstance(Constructor constructor) { + private static T newInstance(Constructor constructor) { try { return constructor.newInstance(); } catch (UserFault e) { @@ -453,15 +456,15 @@ public ClassContext(ParameterizedType type, ClassContext curContext) { for (int i = 0; i < types.length; i++) { Type t = types[i]; if (t instanceof TypeVariable) { - types[i] = curContext.resolveTypeVariable((TypeVariable)t); + types[i] = curContext.resolveTypeVariable((TypeVariable) t); } } Type t = type.getRawType(); if (t instanceof Class) { - this.clazz = (Class)t; + this.clazz = (Class) t; } else if (t instanceof TypeVariable) { - this.clazz = (Class)((TypeVariable)t).getGenericDeclaration(); + this.clazz = (Class) ((TypeVariable) t).getGenericDeclaration(); } else { throw new RuntimeException("Type " + t + " is of unexpected type " + t.getClass()); } @@ -494,30 +497,30 @@ private TypeVariable[] getTypeParameters() { * * @return null of no type found. Otherwise the type found. */ - public static Type[] findInterfaceParameters(Class clazz, Class iface) { + private static Type[] findInterfaceParameters(Class clazz, Class iface) { LinkedList clazzes = new LinkedList<>(); - clazzes.addFirst(new ClassContext(clazz, (Type[])null)); + clazzes.addFirst(new ClassContext(clazz, (Type[]) null)); while (!clazzes.isEmpty()) { final ClassContext curContext = clazzes.removeLast(); Type[] interfaces = curContext.clazz.getGenericInterfaces(); for (Type type : interfaces) { if (type instanceof ParameterizedType) { - ParameterizedType candidate = (ParameterizedType)type; + ParameterizedType candidate = (ParameterizedType) type; Type rawType = candidate.getRawType(); if (!(rawType instanceof Class)) { //should be impossible System.err.println("raw type is not a class: " + rawType); continue; } - Class rawClass = (Class)rawType; + Class rawClass = (Class) rawType; if (iface.isAssignableFrom(rawClass)) { return new ClassContext(candidate, curContext).actualTypeArguments; } else { clazzes.addFirst(new ClassContext(candidate, curContext)); } } else if (type instanceof Class) { - clazzes.addFirst(new ClassContext((Class)type, curContext)); + clazzes.addFirst(new ClassContext((Class) type, curContext)); } else { //should never happen? System.err.println("Unexpected type class " + type.getClass().getName()); @@ -526,9 +529,9 @@ public static Type[] findInterfaceParameters(Class clazz, Class iface) { final Type superClass = curContext.clazz.getGenericSuperclass(); if (superClass instanceof ParameterizedType) { - clazzes.addFirst(new ClassContext((ParameterizedType)superClass, curContext)); + clazzes.addFirst(new ClassContext((ParameterizedType) superClass, curContext)); } else if (superClass != null) { - clazzes.addFirst(new ClassContext((Class)superClass, curContext)); + clazzes.addFirst(new ClassContext((Class) superClass, curContext)); } } return null; @@ -536,7 +539,7 @@ public static Type[] findInterfaceParameters(Class clazz, Class iface) { @SuppressWarnings({"rawtypes"}) - public static LambdaRequestHandler wrapRequestHandlerClass(final Class clazz) { + private static LambdaRequestHandler wrapRequestHandlerClass(final Class clazz) { Type[] ptypes = findInterfaceParameters(clazz, RequestHandler.class); if (ptypes == null) { return new UserFaultHandler(makeUserFault("Class " @@ -550,7 +553,7 @@ public static LambdaRequestHandler wrapRequestHandlerClass(final Class clazz) { + private static LambdaRequestHandler wrapRequestStreamHandlerClass(final Class clazz) { final Constructor constructor; try { constructor = getConstructor(clazz); @@ -595,7 +598,7 @@ public static LambdaRequestHandler wrapRequestStreamHandlerClass(final Class clazz) { + private static LambdaRequestHandler loadStreamingRequestHandler(Class clazz) { if (RequestStreamHandler.class.isAssignableFrom(clazz)) { return wrapRequestStreamHandlerClass(clazz.asSubclass(RequestStreamHandler.class)); } else if (RequestHandler.class.isAssignableFrom(clazz)) { @@ -706,14 +709,14 @@ private static Optional getHandlerFromOverload(Class cl } } - private static final boolean isContext(Type t) { + private static boolean isContext(Type t) { return Context.class.equals(t); } /** * Returns true if the last type in params is a lambda context object interface (Context). */ - private static final boolean lastParameterIsContext(Class[] params) { + private static boolean lastParameterIsContext(Class[] params) { return params.length != 0 && isContext(params[params.length - 1]); } @@ -725,10 +728,9 @@ private static final boolean lastParameterIsContext(Class[] params) { public int compare(Method lhs, Method rhs) { //1. Non bridge methods are preferred over bridge methods. - if (! lhs.isBridge() && rhs.isBridge()) { + if (!lhs.isBridge() && rhs.isBridge()) { return -1; - } - else if (!rhs.isBridge() && lhs.isBridge()) { + } else if (!rhs.isBridge() && lhs.isBridge()) { return 1; } @@ -823,28 +825,13 @@ private static LambdaRequestHandler loadEventPojoHandler(HandlerInfo handlerInfo } @SuppressWarnings({"rawtypes"}) - public static LambdaRequestHandler wrapPojoHandler(RequestHandler instance, Type pType, Type rType) { + private static LambdaRequestHandler wrapPojoHandler(RequestHandler instance, Type pType, Type rType) { return wrapRequestStreamHandler(new PojoHandlerAsStreamHandler(instance, Optional.ofNullable(pType), isVoid(rType) ? Optional.empty() : Optional.of(rType) )); } - public static String exceptionToString(Throwable t) { - StringWriter writer = new StringWriter(65536); - try (PrintWriter wrapped = new PrintWriter(writer)) { - t.printStackTrace(wrapped); - } - StringBuffer buffer = writer.getBuffer(); - if (buffer.length() > 262144) { - final String extra = " Truncated by Lambda"; - buffer.delete(262144, buffer.length()); - buffer.append(extra); - } - - return buffer.toString(); - } - - public static LambdaRequestHandler wrapRequestStreamHandler(final RequestStreamHandler handler) { + private static LambdaRequestHandler wrapRequestStreamHandler(final RequestStreamHandler handler) { return new LambdaRequestHandler() { private final ByteArrayOutputStream output = new ByteArrayOutputStream(1024); private Functions.V2 log4jContextPutMethod = null; @@ -855,14 +842,31 @@ private void safeAddRequestIdToLog4j(String log4jContextClassName, Class log4jContextClass = ReflectUtil.loadClass(AWSLambda.customerClassLoader, log4jContextClassName); log4jContextPutMethod = ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass); log4jContextPutMethod.call("AWSRequestId", request.getId()); - } catch (Exception e) {} + } catch (Exception e) { + } + } + + /** + * Passes the LambdaContext to the logger so that the JSON formatter can include the requestId. + * + * We do casting here because both the LambdaRuntime and the LambdaLogger is in the core package, + * and the setLambdaContext(context) is a method we don't want to publish for customers. That method is + * only implemented on the internal LambdaContextLogger, so we check and cast to be able to call it. + * @param context the LambdaContext + */ + private void safeAddContextToLambdaLogger(LambdaContext context) { + LambdaLogger logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger(); + if (logger instanceof LambdaContextLogger) { + LambdaContextLogger contextLogger = (LambdaContextLogger) logger; + contextLogger.setLambdaContext(context); + } } public ByteArrayOutputStream call(InvocationRequest request) throws Error, Exception { output.reset(); LambdaCognitoIdentity cognitoIdentity = null; - if(request.getCognitoIdentity() != null && !request.getCognitoIdentity().isEmpty()) { + if (request.getCognitoIdentity() != null && !request.getCognitoIdentity().isEmpty()) { cognitoIdentity = getCognitoSerializer().fromJson(request.getCognitoIdentity()); } @@ -885,6 +889,8 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep clientContext ); + safeAddContextToLambdaLogger(context); + if (LambdaRuntimeInternal.getUseLog4jAppender()) { safeAddRequestIdToLog4j("org.apache.log4j.MDC", request, Object.class); safeAddRequestIdToLog4j("org.apache.logging.log4j.ThreadContext", request, String.class); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/Failure.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/Failure.java index d568d9739..c445bafef 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/Failure.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/Failure.java @@ -36,7 +36,7 @@ public Failure(Throwable t) { this.errorType = t.getClass().getName(); StackTraceElement[] trace = t.getStackTrace(); this.stackTrace = new String[trace.length]; - for( int i = 0; i < trace.length; i++) { + for (int i = 0; i < trace.length; i++) { this.stackTrace[i] = trace[i].toString(); } Throwable cause = t.getCause(); @@ -81,4 +81,8 @@ public int compare(Class o1, Class o2) { public String getErrorType() { return errorType; } + + public String getErrorMessage() { + return errorMessage; + } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/HandlerInfo.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/HandlerInfo.java index eaca0fd0a..8f95ba857 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/HandlerInfo.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/HandlerInfo.java @@ -10,7 +10,7 @@ public static class InvalidHandlerException extends RuntimeException { public final Class clazz; public final String methodName; - public HandlerInfo (Class clazz, String methodName) { + public HandlerInfo(Class clazz, String methodName) { this.clazz = clazz; this.methodName = methodName; } @@ -19,7 +19,7 @@ public static HandlerInfo fromString(String handler, ClassLoader cl) throws Clas final int colonLoc = handler.lastIndexOf("::"); final String className; final String methodName; - if(colonLoc < 0) { + if (colonLoc < 0) { className = handler; methodName = null; } else { @@ -27,7 +27,7 @@ public static HandlerInfo fromString(String handler, ClassLoader cl) throws Clas methodName = handler.substring(colonLoc + 2); } - if(className.isEmpty() || (methodName != null && methodName.isEmpty())) { + if (className.isEmpty() || (methodName != null && methodName.isEmpty())) { throw new InvalidHandlerException(); } return new HandlerInfo(Class.forName(className, true, cl), methodName); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/LambdaEnvironment.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/LambdaEnvironment.java index b2d552049..af3578501 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/LambdaEnvironment.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/LambdaEnvironment.java @@ -4,13 +4,16 @@ import com.amazonaws.services.lambda.runtime.api.client.util.EnvReader; +import static com.amazonaws.services.lambda.runtime.api.client.ReservedRuntimeEnvironmentVariables.*; import static java.lang.Integer.parseInt; public class LambdaEnvironment { public static final EnvReader ENV_READER = new EnvReader(); - public static final int MEMORY_LIMIT = parseInt(ENV_READER.getEnvOrDefault(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_FUNCTION_MEMORY_SIZE, "128")); - public static final String LOG_GROUP_NAME = ENV_READER.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_LOG_GROUP_NAME); - public static final String LOG_STREAM_NAME = ENV_READER.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_LOG_STREAM_NAME); - public static final String FUNCTION_NAME = ENV_READER.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_FUNCTION_NAME); - public static final String FUNCTION_VERSION = ENV_READER.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_FUNCTION_VERSION); + public static final int MEMORY_LIMIT = parseInt(ENV_READER.getEnvOrDefault(AWS_LAMBDA_FUNCTION_MEMORY_SIZE, "128")); + public static final String LOG_GROUP_NAME = ENV_READER.getEnv(AWS_LAMBDA_LOG_GROUP_NAME); + public static final String LOG_STREAM_NAME = ENV_READER.getEnv(AWS_LAMBDA_LOG_STREAM_NAME); + public static final String LAMBDA_LOG_LEVEL = ENV_READER.getEnvOrDefault(AWS_LAMBDA_LOG_LEVEL, "UNDEFINED"); + public static final String LAMBDA_LOG_FORMAT = ENV_READER.getEnvOrDefault(AWS_LAMBDA_LOG_FORMAT, "TEXT"); + public static final String FUNCTION_NAME = ENV_READER.getEnv(AWS_LAMBDA_FUNCTION_NAME); + public static final String FUNCTION_VERSION = ENV_READER.getEnv(AWS_LAMBDA_FUNCTION_VERSION); } 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 new file mode 100644 index 000000000..819f9872d --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java @@ -0,0 +1,75 @@ +/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.api.client; + +import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; +import com.amazonaws.services.lambda.runtime.CustomPojoSerializer; + +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Type; +import java.util.Iterator; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; + +public class PojoSerializerLoader { + // The serializer obtained from the provider will always be the same so we can cache it as a filed. + private static CustomPojoSerializer customPojoSerializer; + // If Input and Output type are different, the runtime will try to search for a serializer twice due to + // the getSerializerCached method. Save the initialization state in order to search for the provider only once. + private static boolean initialized = false; + + private static CustomPojoSerializer loadSerializer() + throws ServiceConfigurationError, TooManyServiceProvidersFoundException { + + if (customPojoSerializer != null) { + return customPojoSerializer; + } + + ServiceLoader loader = ServiceLoader.load(CustomPojoSerializer.class, AWSLambda.customerClassLoader); + Iterator serializers = loader.iterator(); + + if (!serializers.hasNext()) { + initialized = true; + return null; + } + + customPojoSerializer = serializers.next(); + + if (serializers.hasNext()) { + throw new TooManyServiceProvidersFoundException( + "Too many serializers provided inside the META-INF/services folder, only one is allowed" + ); + } + + initialized = true; + return customPojoSerializer; + } + + public static PojoSerializer getCustomerSerializer(Type type) { + if (!initialized) { + customPojoSerializer = loadSerializer(); + } + + if (customPojoSerializer == null) { + return null; + } + + return new PojoSerializer() { + @Override + public Object fromJson(InputStream input) { + return customPojoSerializer.fromJson(input, type); + } + + @Override + public Object fromJson(String input) { + return customPojoSerializer.fromJson(input, type); + } + + @Override + public void toJson(Object value, OutputStream output) { + customPojoSerializer.toJson(value, output, type); + } + }; + } +} 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 fc5006c5d..eca5cef17 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 @@ -19,10 +19,10 @@ * Lambda runtimes set several environment variables during initialization. * Most of the environment variables provide information about the function or runtime. * The keys for these environment variables are reserved and cannot be set in your function configuration. - * @see Using AWS Lambda Environment Variables * + * @see Using AWS Lambda Environment Variables + *

* NOTICE: This class is forked from io.micronaut.function.aws.runtime.ReservedRuntimeEnvironments found at https://github.com/micronaut-projects/micronaut-aws - * */ public interface ReservedRuntimeEnvironmentVariables { @@ -66,6 +66,16 @@ public interface ReservedRuntimeEnvironmentVariables { */ String AWS_LAMBDA_LOG_STREAM_NAME = "AWS_LAMBDA_LOG_STREAM_NAME"; + /** + * The logging level set for the function. + */ + String AWS_LAMBDA_LOG_LEVEL = "AWS_LAMBDA_LOG_LEVEL"; + + /** + * The logging format set for the function. + */ + String AWS_LAMBDA_LOG_FORMAT = "AWS_LAMBDA_LOG_FORMAT"; + /** * Access key id obtained from the function's execution role. */ @@ -77,7 +87,6 @@ public interface ReservedRuntimeEnvironmentVariables { String AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"; /** - * * The access keys obtained from the function's execution role. */ String AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN"; @@ -87,6 +96,12 @@ public interface ReservedRuntimeEnvironmentVariables { */ String AWS_LAMBDA_RUNTIME_API = "AWS_LAMBDA_RUNTIME_API"; + + /** + * Initialization type + */ + String AWS_LAMBDA_INITIALIZATION_TYPE = "AWS_LAMBDA_INITIALIZATION_TYPE"; + /** * The path to your Lambda function code. */ diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/TooManyServiceProvidersFoundException.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/TooManyServiceProvidersFoundException.java new file mode 100644 index 000000000..bba400346 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/TooManyServiceProvidersFoundException.java @@ -0,0 +1,20 @@ +/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.api.client; + +public class TooManyServiceProvidersFoundException extends RuntimeException { + public TooManyServiceProvidersFoundException() { + } + + public TooManyServiceProvidersFoundException(String errorMessage) { + super(errorMessage); + } + + public TooManyServiceProvidersFoundException(Throwable cause) { + super(cause); + } + + public TooManyServiceProvidersFoundException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file 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 73066ad51..bc95af572 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 @@ -6,7 +6,7 @@ import java.io.StringWriter; public final class UserFault extends RuntimeException { - private static final long serialVersionUID = 0; + private static final long serialVersionUID = -479308856905162038L; public final String msg; public final String exception; @@ -66,8 +66,8 @@ public static String trace(Throwable t) { */ public static T filterStackTrace(T t) { StackTraceElement[] trace = t.getStackTrace(); - for(int i = 0; i < trace.length; i++) { - if(trace[i].getClassName().startsWith(packagePrefix)) { + for (int i = 0; i < trace.length; i++) { + if (trace[i].getClassName().startsWith(packagePrefix)) { StackTraceElement[] newTrace = new StackTraceElement[i]; System.arraycopy(trace, 0, newTrace, 0, i); t.setStackTrace(newTrace); @@ -77,9 +77,15 @@ public static T filterStackTrace(T t) { Throwable cause = t.getCause(); - if(cause != null) { + if (cause != null) { filterStackTrace(cause); } + + Throwable[] suppressedExceptions = t.getSuppressed(); + for (Throwable suppressed : suppressedExceptions) { + filterStackTrace(suppressed); + } + return t; } @@ -102,7 +108,7 @@ static UserFault makeClassNotFoundUserFault(Throwable e, String className) { } public String reportableError() { - if(this.exception != null || this.trace != null) { + if (this.exception != null || this.trace != null) { return String.format("%s: %s\n%s\n", this.msg, this.exception == null ? "" : this.exception, diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/XRayErrorCause.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/XRayErrorCause.java index 5a05810e5..73db5b940 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/XRayErrorCause.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/XRayErrorCause.java @@ -18,7 +18,7 @@ public class XRayErrorCause { public XRayErrorCause(Throwable throwable) { working_directory = System.getProperty("user.dir"); - exceptions = Collections.unmodifiableCollection(Collections.singletonList(new XRayException(throwable))); + exceptions = Collections.singletonList(new XRayException(throwable)); paths = Collections.unmodifiableCollection( Arrays.stream(throwable.getStackTrace()) .map(XRayErrorCause::determineFileName) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaClientContext.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaClientContext.java index 8887dfb24..3b8976b7b 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaClientContext.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaClientContext.java @@ -2,10 +2,10 @@ package com.amazonaws.services.lambda.runtime.api.client.api; -import java.util.Map; - -import com.amazonaws.services.lambda.runtime.ClientContext; import com.amazonaws.services.lambda.runtime.Client; +import com.amazonaws.services.lambda.runtime.ClientContext; + +import java.util.Map; public class LambdaClientContext implements 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 689c9110d..3d57ce4c2 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 @@ -22,16 +22,16 @@ public class LambdaContext implements Context { private final LambdaLogger logger; public LambdaContext( - int memoryLimit, - long deadlineTimeInMs, - String requestId, - String logGroupName, - String logStreamName, - String functionName, - CognitoIdentity identity, - String functionVersion, - String invokedFunctionArn, - ClientContext clientContext + int memoryLimit, + long deadlineTimeInMs, + String requestId, + String logGroupName, + String logStreamName, + String functionName, + CognitoIdentity identity, + String functionVersion, + String invokedFunctionArn, + ClientContext clientContext ) { this.memoryLimit = memoryLimit; this.deadlineTimeInMs = deadlineTimeInMs; diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLogger.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLogger.java new file mode 100644 index 000000000..8246a3ad2 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLogger.java @@ -0,0 +1,68 @@ +/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.api.client.logging; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.amazonaws.services.lambda.runtime.api.client.api.LambdaContext; +import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +/** + * Provides default implementation of the convenience logger functions. + * When extending AbstractLambdaLogger, only one function has to be overridden: + * void logMessage(byte[] message, LogLevel logLevel); + */ +public abstract class AbstractLambdaLogger implements LambdaLogger { + private final LogFiltering logFiltering; + private final LogFormatter logFormatter; + protected final LogFormat logFormat; + + public AbstractLambdaLogger(LogLevel logLevel, LogFormat logFormat) { + this.logFiltering = new LogFiltering(logLevel); + + this.logFormat = logFormat; + if (logFormat == LogFormat.JSON) { + logFormatter = new JsonLogFormatter(); + } else { + logFormatter = new TextLogFormatter(); + } + } + + protected abstract void logMessage(byte[] message, LogLevel logLevel); + + protected void logMessage(String message, LogLevel logLevel) { + byte[] messageBytes = message == null ? null : message.getBytes(UTF_8); + logMessage(messageBytes, logLevel); + } + + @Override + public void log(String message, LogLevel logLevel) { + if (logFiltering.isEnabled(logLevel)) { + this.logMessage(logFormatter.format(message, logLevel), logLevel); + } + } + + @Override + public void log(byte[] message, LogLevel logLevel) { + if (logFiltering.isEnabled(logLevel)) { + // there is no formatting for byte[] messages + this.logMessage(message, logLevel); + } + } + + @Override + public void log(String message) { + this.log(message, LogLevel.UNDEFINED); + } + + @Override + public void log(byte[] message) { + this.log(message, LogLevel.UNDEFINED); + } + + public void setLambdaContext(LambdaContext lambdaContext) { + this.logFormatter.setLambdaContext(lambdaContext); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/FrameType.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/FrameType.java index 351a39ede..6663cc1d9 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/FrameType.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/FrameType.java @@ -2,12 +2,40 @@ package com.amazonaws.services.lambda.runtime.api.client.logging; -public enum FrameType { +import com.amazonaws.services.lambda.runtime.logging.LogLevel; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; - LOG(0xa55a0003); +/** + * The first 4 bytes of the framing protocol is the Frame Type, that's made of a magic number (3 bytes) and 1 byte of flags. + * +-----------------------+ + * | Frame Type - 4 bytes | + * +-----------------------+ + * | a5 5a 00 | flgs | + * + - - - - - + - - - - - + + * \ bit | + * | view| + * +---------+ + + * | | + * v byte 3 v F - free + * +-+-+-+-+-+-+-+-+ J - { JsonLog = 0, PlainTextLog = 1 } + * |F|F|F|L|l|l|T|J| T - { NoTimeStamp = 0, TimeStampPresent = 1 } + * +-+-+-+-+-+-+-+-+ Lll -> Log Level in 3-bit binary (L-> most significant bit) + */ +public class FrameType { + private static final int LOG_MAGIC = 0xa55a0000; + private static final int OFFSET_LOG_FORMAT = 0; + private static final int OFFSET_TIMESTAMP_PRESENT = 1; + private static final int OFFSET_LOG_LEVEL = 2; private final int val; + public static int getValue(LogLevel logLevel, LogFormat logFormat) { + return LOG_MAGIC | + (logLevel.ordinal() << OFFSET_LOG_LEVEL) | + (1 << OFFSET_TIMESTAMP_PRESENT) | + (logFormat.ordinal() << OFFSET_LOG_FORMAT); + } + FrameType(int val) { this.val = val; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/FramedTelemetryLogSink.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/FramedTelemetryLogSink.java index bb436217b..f20e77221 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/FramedTelemetryLogSink.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/FramedTelemetryLogSink.java @@ -9,20 +9,23 @@ import java.nio.ByteOrder; import java.time.Instant; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; + /** * FramedTelemetryLogSink implements the logging contract between runtimes and the platform. It implements a simple * framing protocol so message boundaries can be determined. Each frame can be visualized as follows: * - *

+ * 
  * {@code
  * +----------------------+------------------------+---------------------+-----------------------+
  * | Frame Type - 4 bytes | Length (len) - 4 bytes | Timestamp - 8 bytes | Message - 'len' bytes |
  * +----------------------+------------------------+---------------------+-----------------------+
  * }
  * 
- * + *

* The first 4 bytes indicate the type of the frame - log frames have a type defined as the hex value 0xa55a0001. The - * second 4 bytes should indicate the message's length. The next 8 bytes contain UNIX timestamp of the message in + * second 4 bytes should indicate the message's length. The next 8 bytes contain UNIX timestamp of the message in * microsecond accuracy. The next 'len' bytes contain the message. The byte order is big-endian. */ public class FramedTelemetryLogSink implements LogSink { @@ -38,16 +41,21 @@ public FramedTelemetryLogSink(FileDescriptor fd) throws IOException { } @Override - public synchronized void log(byte[] message) { + public synchronized void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { try { - writeFrame(message); + writeFrame(logLevel, logFormat, message); } catch (IOException e) { e.printStackTrace(); } } - private void writeFrame(byte[] message) throws IOException { - updateHeader(message.length); + @Override + public void log(byte[] message) { + log(LogLevel.UNDEFINED, LogFormat.TEXT, message); + } + + private void writeFrame(LogLevel logLevel, LogFormat logFormat, byte[] message) throws IOException { + updateHeader(logLevel, logFormat, message.length); this.logOutputStream.write(this.headerBuf.array()); this.logOutputStream.write(message); } @@ -60,9 +68,9 @@ private long timestamp() { /** * Updates the header ByteBuffer with the provided length. The header comprises the frame type and message length. */ - private void updateHeader(int length) { + private void updateHeader(LogLevel logLevel, LogFormat logFormat, int length) { this.headerBuf.clear(); - this.headerBuf.putInt(FrameType.LOG.getValue()); + this.headerBuf.putInt(FrameType.getValue(logLevel, logFormat)); this.headerBuf.putInt(length); this.headerBuf.putLong(timestamp()); this.headerBuf.flip(); 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 new file mode 100644 index 000000000..ef9e1c410 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java @@ -0,0 +1,55 @@ +/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.api.client.logging; + +import com.amazonaws.services.lambda.runtime.api.client.api.LambdaContext; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; +import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; +import com.amazonaws.services.lambda.runtime.serialization.factories.GsonFactory; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +public class JsonLogFormatter implements LogFormatter { + private final PojoSerializer serializer = GsonFactory.getInstance().getSerializer(StructuredLogMessage.class); + private LambdaContext lambdaContext; + + private static final DateTimeFormatter dateFormatter = + DateTimeFormatter + .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + .withZone(ZoneId.of("UTC")); + + @Override + public String format(String message, LogLevel logLevel) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + StructuredLogMessage msg = createLogMessage(message, logLevel); + serializer.toJson(msg, stream); + stream.write('\n'); + return new String(stream.toByteArray(), StandardCharsets.UTF_8); + } + + private StructuredLogMessage createLogMessage(String message, LogLevel logLevel) { + StructuredLogMessage msg = new StructuredLogMessage(); + msg.timestamp = dateFormatter.format(LocalDateTime.now()); + msg.message = message; + msg.level = logLevel; + + if (lambdaContext != null) { + msg.AWSRequestId = lambdaContext.getAwsRequestId(); + } + return msg; + } + + + /** + * Function to set the context for every invocation. + * This way the logger will be able to attach additional information to the log packet. + */ + @Override + public void setLambdaContext(LambdaContext context) { + this.lambdaContext = context; + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LambdaContextLogger.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LambdaContextLogger.java index eaa2e3ea0..4800b356f 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LambdaContextLogger.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LambdaContextLogger.java @@ -2,33 +2,29 @@ package com.amazonaws.services.lambda.runtime.api.client.logging; -import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.api.client.api.LambdaContext; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; import static java.nio.charset.StandardCharsets.UTF_8; -public class LambdaContextLogger implements LambdaLogger { +public class LambdaContextLogger extends AbstractLambdaLogger { // If a null string is passed in, replace it with "null", // replicating the behavior of System.out.println(null); private static final byte[] NULL_BYTES_VALUE = "null".getBytes(UTF_8); private final transient LogSink sink; - public LambdaContextLogger(LogSink sink) { + public LambdaContextLogger(LogSink sink, LogLevel logLevel, LogFormat logFormat) { + super(logLevel, logFormat); this.sink = sink; } - public void log(byte[] message) { + @Override + protected void logMessage(byte[] message, LogLevel logLevel) { if (message == null) { message = NULL_BYTES_VALUE; } - sink.log(message); - } - - public void log(String message) { - if (message == null) { - this.log(NULL_BYTES_VALUE); - } else { - this.log(message.getBytes(UTF_8)); - } + sink.log(logLevel, this.logFormat, message); } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LogFiltering.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LogFiltering.java new file mode 100644 index 000000000..59038efa5 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LogFiltering.java @@ -0,0 +1,17 @@ +/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.api.client.logging; + +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +public class LogFiltering { + private final LogLevel minimumLogLevel; + + public LogFiltering(LogLevel minimumLogLevel) { + this.minimumLogLevel = minimumLogLevel; + } + + boolean isEnabled(LogLevel logLevel) { + return (logLevel == LogLevel.UNDEFINED || logLevel.ordinal() >= minimumLogLevel.ordinal()); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LogFormatter.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LogFormatter.java new file mode 100644 index 000000000..debe5af41 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LogFormatter.java @@ -0,0 +1,13 @@ +/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.api.client.logging; + +import com.amazonaws.services.lambda.runtime.api.client.api.LambdaContext; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +public interface LogFormatter { + String format(String message, LogLevel logLevel); + + default void setLambdaContext(LambdaContext context) { + }; +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LogSink.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LogSink.java index 11c3f92ce..77df08e25 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LogSink.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LogSink.java @@ -4,8 +4,13 @@ import java.io.Closeable; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; + public interface LogSink extends Closeable { void log(byte[] message); + void log(LogLevel logLevel, LogFormat logFormat, byte[] message); + } 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 5487c5dd0..6fd6b87c7 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 @@ -4,9 +4,16 @@ import java.io.IOException; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; + public class StdOutLogSink implements LogSink { @Override public void log(byte[] message) { + log(LogLevel.UNDEFINED, LogFormat.TEXT, message); + } + + public void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { try { System.out.write(message); } catch (IOException e) { @@ -15,5 +22,6 @@ public void log(byte[] message) { } @Override - public void close() {} + public void close() { + } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StructuredLogMessage.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StructuredLogMessage.java new file mode 100644 index 000000000..9c271adf7 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StructuredLogMessage.java @@ -0,0 +1,12 @@ +/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.api.client.logging; + +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +class StructuredLogMessage { + public String timestamp; + public String message; + public LogLevel level; + public String AWSRequestId; +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/TextLogFormatter.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/TextLogFormatter.java new file mode 100644 index 000000000..7c3454012 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/TextLogFormatter.java @@ -0,0 +1,28 @@ +/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.api.client.logging; + +import com.amazonaws.services.lambda.runtime.api.client.api.LambdaContext; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +import java.util.HashMap; + +public class TextLogFormatter implements LogFormatter { + private static final HashMap logLevelMapper = new HashMap() {{ + for (LogLevel logLevel: LogLevel.values()) { + put(logLevel, "[" + logLevel.toString() + "] "); + } + }}; + + @Override + public String format(String message, LogLevel logLevel) { + if (logLevel == LogLevel.UNDEFINED) { + return message; + } + + return new StringBuilder() + .append(logLevelMapper.get(logLevel)) + .append(message) + .toString(); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/InvocationRequest.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/InvocationRequest.java index c8df04afd..6a6e7b129 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/InvocationRequest.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/InvocationRequest.java @@ -5,7 +5,7 @@ /** * An invocation request represents the response of the runtime API's next invocation API. - * + *

* Copyright (c) 2019 Amazon. All rights reserved. */ public class InvocationRequest { @@ -41,39 +41,61 @@ public class InvocationRequest { */ private String cognitoIdentity; - /** - * An input stream of the invocation's request body. - */ - private InputStream stream; - private byte[] content; public String getId() { return id; } + public void setId(String id) { + this.id = id; + } + public String getXrayTraceId() { return xrayTraceId; } + public void setXrayTraceId(String xrayTraceId) { + this.xrayTraceId = xrayTraceId; + } + public String getInvokedFunctionArn() { return invokedFunctionArn; } + public void setInvokedFunctionArn(String invokedFunctionArn) { + this.invokedFunctionArn = invokedFunctionArn; + } + public long getDeadlineTimeInMs() { return deadlineTimeInMs; } + public void setDeadlineTimeInMs(long deadlineTimeInMs) { + this.deadlineTimeInMs = deadlineTimeInMs; + } + public String getClientContext() { return clientContext; } + public void setClientContext(String clientContext) { + this.clientContext = clientContext; + } + public String getCognitoIdentity() { return cognitoIdentity; } + public void setCognitoIdentity(String cognitoIdentity) { + this.cognitoIdentity = cognitoIdentity; + } + public InputStream getContentAsStream() { return new ByteArrayInputStream(content); } + public void setContent(byte[] content) { + this.content = content; + } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClient.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClient.java index 05aa50a1b..e79ba6142 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClient.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClient.java @@ -6,16 +6,19 @@ import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import static java.net.HttpURLConnection.HTTP_ACCEPTED; +import static java.net.HttpURLConnection.HTTP_OK; import static java.nio.charset.StandardCharsets.UTF_8; /** * LambdaRuntimeClient is a client of the AWS Lambda Runtime HTTP API for custom runtimes. - * + *

* API definition can be found at https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html - * + *

* Copyright (c) 2019 Amazon. All rights reserved. */ public class LambdaRuntimeClient { @@ -35,14 +38,15 @@ public LambdaRuntimeClient(String hostnamePort) { this.hostname = parts[0]; this.port = Integer.parseInt(parts[1]); this.invocationEndpoint = invocationEndpoint(); + NativeClient.init(); } public InvocationRequest waitForNextInvocation() { - return NativeClient.next(); + return NativeClient.nextWrapper(); } public void postInvocationResponse(String requestId, byte[] response) { - NativeClient.postInvocationResponse(requestId.getBytes(UTF_8), response); + NativeClient.postInvocationResponseWrapper(requestId.getBytes(UTF_8), response); } public void postInvocationError(String requestId, byte[] errorResponse, String errorType) throws IOException { @@ -55,6 +59,15 @@ public void postInvocationError(String requestId, byte[] errorResponse, String e post(endpoint, errorResponse, errorType, errorCause); } + public void getRestoreNext() throws IOException { + doGet(restoreNextEndpoint(), HTTP_OK); + } + + public int postRestoreError(byte[] errorResponse, String errorType) throws IOException { + String endpoint = restoreErrorEndpoint(); + return postError(endpoint, errorResponse, errorType, null); + } + public void postInitError(byte[] errorResponse, String errorType) throws IOException { String endpoint = initErrorEndpoint(); post(endpoint, errorResponse, errorType, null); @@ -65,10 +78,10 @@ private void post(String endpoint, byte[] errorResponse, String errorType, Strin HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", DEFAULT_CONTENT_TYPE); - if(errorType != null && !errorType.isEmpty()) { + if (errorType != null && !errorType.isEmpty()) { conn.setRequestProperty(ERROR_TYPE_HEADER, errorType); } - if(errorCause != null && errorCause.getBytes().length < XRAY_ERROR_CAUSE_MAX_HEADER_SIZE) { + if (errorCause != null && errorCause.getBytes().length < XRAY_ERROR_CAUSE_MAX_HEADER_SIZE) { conn.setRequestProperty(XRAY_ERROR_CAUSE_HEADER, errorCause); } conn.setFixedLengthStreamingMode(errorResponse.length); @@ -87,7 +100,7 @@ private void post(String endpoint, byte[] errorResponse, String errorType, Strin } private String invocationEndpoint() { - return "http://" + hostname + ":" + port + "/2018-06-01/runtime/invocation/"; + return getBaseUrl() + "/2018-06-01/runtime/invocation/"; } private String invocationErrorEndpoint(String requestId) { @@ -95,7 +108,80 @@ private String invocationErrorEndpoint(String requestId) { } private String initErrorEndpoint() { - return "http://" + hostname + ":" + port + "/2018-06-01/runtime/init/error"; + return getBaseUrl() + "/2018-06-01/runtime/init/error"; + } + + private String restoreErrorEndpoint() { + return getBaseUrl() + "/2018-06-01/runtime/restore/error"; + } + + private String restoreNextEndpoint() { + return getBaseUrl() + "/2018-06-01/runtime/restore/next"; + } + + private String getBaseUrl() { + return "http://" + hostname + ":" + port; + } + + private int postError(String endpoint, + byte[] errorResponse, + String errorType, + String errorCause) throws IOException { + + Map headers = new HashMap<>(); + if (errorType != null && !errorType.isEmpty()) { + headers.put(ERROR_TYPE_HEADER, errorType); + } + if (errorCause != null && errorCause.getBytes().length < XRAY_ERROR_CAUSE_MAX_HEADER_SIZE) { + headers.put(XRAY_ERROR_CAUSE_HEADER, errorCause); + } + + return doPost(endpoint, DEFAULT_CONTENT_TYPE, headers, errorResponse); + } + + private int doPost(String endpoint, + String contentType, + Map headers, + byte[] payload) throws IOException { + + URL url = createUrl(endpoint); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", contentType); + + for (Map.Entry header : headers.entrySet()) { + conn.setRequestProperty(header.getKey(), header.getValue()); + } + + conn.setFixedLengthStreamingMode(payload.length); + conn.setDoOutput(true); + + try (OutputStream outputStream = conn.getOutputStream()) { + outputStream.write(payload); + } + + // get response code before closing the stream + int responseCode = conn.getResponseCode(); + + // don't need to read the response, close stream to ensure connection re-use + closeInputStreamQuietly(conn); + + return responseCode; + } + + private void doGet(String endpoint, int expectedHttpResponseCode) throws IOException { + + URL url = createUrl(endpoint); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + + int responseCode = conn.getResponseCode(); + if (responseCode != expectedHttpResponseCode) { + throw new LambdaRuntimeClientException(endpoint, responseCode); + } + + closeInputStreamQuietly(conn); } private URL createUrl(String endpoint) { @@ -113,4 +199,23 @@ private void closeQuietly(InputStream inputStream) { } catch (IOException e) { } } + + private void closeInputStreamQuietly(HttpURLConnection conn) { + + InputStream inputStream; + try { + inputStream = conn.getInputStream(); + } catch (IOException e) { + return; + } + + if (inputStream == null) { + return; + } + try { + inputStream.close(); + } catch (IOException e) { + // ignore + } + } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java index a9592e1a8..785b95864 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java @@ -2,72 +2,147 @@ package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; +import org.crac.Context; +import org.crac.Resource; + +import java.io.FileNotFoundException; import java.io.InputStream; +import java.lang.annotation.Native; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; /** * This module defines the native Runtime Interface Client which is responsible for all HTTP * interactions with the Runtime API. */ class NativeClient { - private static final String nativeLibPath = "/tmp/.aws-lambda-runtime-interface-client"; - private static final String architecturePathSuffix = "/" + getArchIdentifier(); - // Implementation based on AWS CRT, but adopted to support 64-bit architectures only (ref. https://github.com/awslabs/aws-crt-java/blob/0e9c3db8b07258b57c2503cfc47c787ccef10670/src/main/java/software/amazon/awssdk/crt/CRT.java#L106-L134) - private static final String supported_arm_architectures = "^(aarch64.*|arm64.*)$"; - private static final String supported_x86_architectures = "^(x8664|amd64|ia32e|em64t|x64|x86_64)$"; - private static final String[] libsToTry = { - "aws-lambda-runtime-interface-client.glibc.so", - "aws-lambda-runtime-interface-client.musl.so", - }; - private static final Throwable[] exceptions = new Throwable[libsToTry.length]; - static { - boolean loaded = false; - for (int i = 0; !loaded && i < libsToTry.length; ++i) { - try (InputStream lib = NativeClient.class.getResourceAsStream( - Paths.get(architecturePathSuffix, libsToTry[i]).toString())) { - Files.copy(lib, Paths.get(nativeLibPath), StandardCopyOption.REPLACE_EXISTING); - System.load(nativeLibPath); - loaded = true; - } catch (UnsatisfiedLinkError e) { - exceptions[i] = e; - } catch (Exception e) { - exceptions[i] = e; + static class CheckpointState implements Resource { + enum State { + WORKING, + SYNCING, + SYNCED, + }; + + State state = State.WORKING; + + private void waitFor(State targetState) { + while (state != targetState) { + try { + this.wait(); + } catch (InterruptedException interruptedException) { } } - if (!loaded) { - for (int i = 0; i < libsToTry.length; ++i) { - System.err.printf("Failed to load the native runtime interface client library %s. Exception: %s\n", libsToTry[i], exceptions[i].getMessage()); - } - System.exit(-1); + } + + @Override + public synchronized void beforeCheckpoint(Context context) throws Exception { + state = State.SYNCING; + waitFor(State.SYNCED); + deinitializeClient(); + } + + @Override + public synchronized void afterRestore(Context context) throws Exception { + initUserAgent(); + state = State.WORKING; + this.notifyAll(); + } + + public synchronized void syncPoint() { + if (state == State.SYNCING) { + state = State.SYNCED; + this.notifyAll(); } - String userAgent = String.format( - "aws-lambda-java/%s-%s" , - System.getProperty("java.vendor.version"), - NativeClient.class.getPackage().getImplementationVersion()); - initializeClient(userAgent.getBytes()); + waitFor(State.WORKING); + } + } + + static CheckpointState checkpointState = new CheckpointState(); + + private static final String NATIVE_LIB_PATH = "/tmp/.libaws-lambda-jni.so"; + public static final String NATIVE_CLIENT_JNI_PROPERTY = "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.NativeClient.JNI"; + + static void init() { + loadJNILib(); + initUserAgent(); + org.crac.Core.getGlobalContext().register(checkpointState); } + private static void loadJNILib() { + String jniLib = System.getProperty(NATIVE_CLIENT_JNI_PROPERTY); + if (jniLib != null) { + System.load(jniLib); + } else { + String[] libsToTry = new String[]{ + "libaws-lambda-jni.linux-x86_64.so", + "libaws-lambda-jni.linux-aarch_64.so", + "libaws-lambda-jni.linux_musl-x86_64.so", + "libaws-lambda-jni.linux_musl-aarch_64.so" + }; + unpackAndLoadNativeLibrary(libsToTry); + } + } + + /** - * @return a string describing the detected architecture the RIC is executing on - * @throws UnknownPlatformException + * Unpacks JNI library from the JAR to a temporary location and tries to load it using System.load() + * Implementation based on AWS CRT + * (ref. ...) + * + * @param libsToTry - array of native libraries to try */ - static String getArchIdentifier() { - String arch = System.getProperty("os.arch"); + static void unpackAndLoadNativeLibrary(String[] libsToTry) { - if (arch.matches(supported_x86_architectures)) { - return "x86_64"; - } else if (arch.matches(supported_arm_architectures)) { - return "aarch64"; + List errorMessages = new ArrayList<>(); + for (String libToTry : libsToTry) { + try (InputStream inputStream = NativeClient.class.getResourceAsStream( + Paths.get("/jni", libToTry).toString())) { + if (inputStream == null) { + throw new FileNotFoundException("Specified file not in the JAR: " + libToTry); + } + Files.copy(inputStream, Paths.get(NATIVE_LIB_PATH), StandardCopyOption.REPLACE_EXISTING); + System.load(NATIVE_LIB_PATH); + return; + } catch (UnsatisfiedLinkError | Exception e) { + errorMessages.add(e.getMessage()); + } } - throw new UnknownPlatformException("architecture not supported: " + arch); + for (int i = 0; i < libsToTry.length; ++i) { + System.err.println("Failed to load the native runtime interface client library " + libsToTry[i] + + ". Exception: " + errorMessages.get(i)); + } + System.exit(-1); + } + + private static void initUserAgent() { + String userAgent = String.format( + "aws-lambda-java/%s-%s", + System.getProperty("java.vendor.version"), + NativeClient.class.getPackage().getImplementationVersion()); + + initializeClient(userAgent.getBytes()); + } static native void initializeClient(byte[] userAgent); - static native InvocationRequest next(); + private static native InvocationRequest next(); + + static InvocationRequest nextWrapper() { + return next(); + } + + private static native void postInvocationResponse(byte[] requestId, byte[] response); + + static void postInvocationResponseWrapper(byte[] requestId, byte[] response) { + postInvocationResponse(requestId, response); + checkpointState.syncPoint(); + } - static native void postInvocationResponse(byte[] requestId, byte[] response); + static native void deinitializeClient(); } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/LambdaOutputStream.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/LambdaOutputStream.java index 83ffcb9ac..556859a27 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/LambdaOutputStream.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/LambdaOutputStream.java @@ -14,7 +14,7 @@ public LambdaOutputStream(OutputStream inner) { @Override public void write(int b) throws IOException { - write(new byte[] {(byte)b}); + write(new byte[]{(byte) b}); } @Override @@ -25,6 +25,6 @@ public void write(byte[] bytes) throws IOException { @Override public void write(byte[] bytes, int offset, int length) throws IOException { - inner.write(bytes, offset, length); + inner.write(bytes, offset, length); } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc index 2f4a66553..dd4fdb22e 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc @@ -7,14 +7,19 @@ RUN yum install -y \ tar \ gzip \ make \ + patch \ gcc \ gcc-c++ \ java-11-amazon-corretto # Install curl dependency COPY ./deps/curl-$CURL_VERSION.tar.gz /src/deps/ +COPY ./deps/curl_001_disable_wakeup.patch /src/deps/ + RUN tar xzf /src/deps/curl-$CURL_VERSION.tar.gz -C /src/deps + WORKDIR /src/deps/curl-$CURL_VERSION +RUN patch lib/multihandle.h ../curl_001_disable_wakeup.patch RUN ./configure \ --prefix $(pwd)/../artifacts \ --disable-shared \ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl index fa91af809..e15f6adc5 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl @@ -10,12 +10,17 @@ RUN apk update && \ g++ \ gcc \ make \ + patch \ perl # Install curl dependency COPY ./deps/curl-$CURL_VERSION.tar.gz /src/deps/ +COPY ./deps/curl_001_disable_wakeup.patch /src/deps/ + RUN tar xzf /src/deps/curl-$CURL_VERSION.tar.gz -C /src/deps + WORKDIR /src/deps/curl-$CURL_VERSION +RUN patch lib/multihandle.h ../curl_001_disable_wakeup.patch RUN ./configure \ --prefix $(pwd)/../artifacts \ --disable-shared \ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh index e7122b205..3b505e74d 100755 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash -x # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. set -euo pipefail @@ -6,46 +6,114 @@ set -euo pipefail SRC_DIR=$(dirname "$0") DST_DIR=${1} MULTI_ARCH=${2} -CURL_VERSION=7.86.0 - -# Not using associative arrays to maintain bash 3 compatibility with building on MacOS -# MacOS ships with bash 3 and associative arrays require bash 4+ -# Declaring a map as an array with the column character as a separator : -declare -a ARCHITECTURES_TO_PLATFORM=( - "x86_64:linux/amd64" - "aarch64:linux/arm64/v8" -) - -declare -a TARGETS=("glibc" "musl") - -for pair in "${ARCHITECTURES_TO_PLATFORM[@]}"; do - arch=${pair%%:*} - platform=${pair#*:} - - if [[ "${MULTI_ARCH}" != "true" ]] && [[ "$(arch)" != "${arch}" ]]; then - echo "multi arch build not requested and host arch is $(arch), so skipping ${arch}:${platform} ..." - continue - fi - - mkdir -p "${DST_DIR}/classes/${arch}" - - for target in "${TARGETS[@]}"; do - echo "Compiling the native library for target ${target} on architecture ${arch} using Docker platform ${platform}" - artifact="${DST_DIR}/classes/${arch}/aws-lambda-runtime-interface-client.${target}.so" - - if [[ "${MULTI_ARCH}" == "true" ]]; then - docker build --platform="${platform}" -f "${SRC_DIR}/Dockerfile.${target}" --build-arg CURL_VERSION=${CURL_VERSION} "${SRC_DIR}" -o - | tar -xOf - src/aws-lambda-runtime-interface-client.so > "${artifact}" - else - echo "multi-arch not requestsed, assuming this is a workaround to goofyness when docker buildx is enabled on Linux CI environments." - echo "enabling docker buildx often updates the docker api version, so assuming that docker cli is also too old to use --output type=tar, so doing alternative build-tag-run approach" - docker build --platform="${platform}" -t "lambda-java-jni-lib-${target}-${arch}" -f "${SRC_DIR}/Dockerfile.${target}" --build-arg CURL_VERSION=${CURL_VERSION} "${SRC_DIR}" - docker run --rm --entrypoint /bin/cat "lambda-java-jni-lib-${target}-${arch}" /src/aws-lambda-runtime-interface-client.so > "${artifact}" - fi - - [ -f "${artifact}" ] - if ! file -b "${artifact}" | tr '-' '_' | tee /dev/stderr | grep -q "${arch}"; then - echo "${artifact} did not appear to be the correct architecture, check that Docker buildx is enabled" - exit 1 - fi - done -done +BUILD_OS=${3} +BUILD_ARCH=${4} +CURL_VERSION=7.83.1 + +function get_docker_platform() { + arch=$1 + + if [ "${arch}" == "x86_64" ]; then + echo "linux/amd64" + elif [ "${arch}" == "aarch_64" ]; then + echo "linux/arm64/v8" + else + echo "UNKNOWN_DOCKER_PLATFORM" + fi +} + +function get_target_os() { + libc_impl=$1 + + if [ "${libc_impl}" == "glibc" ]; then + echo "linux" + elif [ "${libc_impl}" == "musl" ]; then + echo "linux_musl" + else + echo "UNKNOWN_OS" + fi +} + +function build_for_libc_arch() { + libc_impl=$1 + arch=$2 + artifact=$3 + + docker_platform=$(get_docker_platform ${arch}) + + echo "Compiling the native library with libc implementation \`${libc_impl}\` on architecture \`${arch}\` using Docker platform \`${docker_platform}\`" + + if [[ "${MULTI_ARCH}" == "true" ]]; then + docker build --platform="${docker_platform}" -f "${SRC_DIR}/Dockerfile.${libc_impl}" \ + --build-arg CURL_VERSION=${CURL_VERSION} "${SRC_DIR}" -o - \ + | tar -xOf - src/aws-lambda-runtime-interface-client.so > "${artifact}" + else + echo "multi-arch not requested, assuming this is a workaround to goofyness when docker buildx is enabled on Linux CI environments." + echo "enabling docker buildx often updates the docker api version, so assuming that docker cli is also too old to use --output type=tar, so doing alternative build-tag-run approach" + image_name="lambda-java-jni-lib-${libc_impl}-${arch}" + docker build --platform="${docker_platform}" \ + -t "${image_name}" \ + -f "${SRC_DIR}/Dockerfile.${libc_impl}" \ + --build-arg CURL_VERSION=${CURL_VERSION} "${SRC_DIR}" + + docker run --rm --entrypoint /bin/cat "${image_name}" \ + /src/aws-lambda-runtime-interface-client.so > "${artifact}" + fi + + [ -f "${artifact}" ] + + # file -b ${artifact} produces lines like this: + # x86_64: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=582888b42da34895828e1281cbbae15d279175b7, not stripped + # aarch_64: ELF 64-bit LSB shared object, ARM aarch64, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=fa54218974fb2c17772b6acf22467a2c67a87011, not stripped + # we need to ensure it has the expected architecture in it + # + # cut -d "," -f2 will extract second field (' x86-64' or ' ARM aarch64') + # tr -d '-' removes '-', so we'll have (' x8664' or ' ARM aarch64') + # grep -q is for quiet mode, no output + # ${arch//_} removes '_' chars from the `aarch` variable, (aarch_64 => aarch64, x86_64 => x8664) + if ! file -b "${artifact}" | cut -d "," -f2 | tr -d '-' | grep -q "${arch//_}"; then + echo "${artifact} did not appear to be the correct architecture, check that Docker buildx is enabled" + exit 1 + fi +} + +function get_target_artifact() { + target_os=$1 + target_arch=$2 + + target_file="${DST_DIR}/classes/jni/libaws-lambda-jni.${target_os}-${target_arch}.so" + target_dir=$(dirname "$target_file") + mkdir -p "$target_dir" + echo "$target_file" +} + + + +if [ -n "$BUILD_OS" ] && [ -n "$BUILD_ARCH" ]; then + # build for the specified arch and libc implementation + libc_impl="glibc" + if [ "$BUILD_OS" == "linux_musl" ]; then + libc_impl="musl" + fi + target_artifact=$(get_target_artifact "$BUILD_OS" "$BUILD_ARCH") + build_for_libc_arch "$libc_impl" "$BUILD_ARCH" "$target_artifact" +else + # build for all architectures and libc implementations + declare -a ARCHITECTURES=("x86_64" "aarch_64") + declare -a LIBC_IMPLS=("glibc" "musl") + + for arch in "${ARCHITECTURES[@]}"; do + + if [[ "${MULTI_ARCH}" != "true" ]] && [[ "$(arch)" != "${arch}" ]]; then + echo "multi arch build not requested and host arch is $(arch), so skipping ${arch}..." + continue + fi + + for libc_impl in "${LIBC_IMPLS[@]}"; do + target_os=$(get_target_os $libc_impl) + target_artifact=$(get_target_artifact "$target_os" "$arch") + build_for_libc_arch "$libc_impl" "$arch" "$target_artifact" + done + + done +fi diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp index 87fa9f028..240a8a795 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp @@ -149,3 +149,9 @@ JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_run throwLambdaRuntimeClientException(env, errorMessage, outcome.get_failure()); } } + +JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_deinitializeClient(JNIEnv *env, jobject thisObject) { + delete CLIENT; + CLIENT = NULL; +} + diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h index 28a6f444a..47d1265df 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h @@ -15,6 +15,9 @@ JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_ JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_postInvocationResponse (JNIEnv *, jobject, jbyteArray, jbyteArray); +JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_deinitializeClient + (JNIEnv *, jobject); + #ifdef __cplusplus } #endif diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/curl-7.83.1.tar.gz b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/curl-7.83.1.tar.gz new file mode 100644 index 000000000..b71926a37 Binary files /dev/null and b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/curl-7.83.1.tar.gz differ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/curl-7.86.0.tar.gz b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/curl-7.86.0.tar.gz deleted file mode 100644 index 650bee5ba..000000000 Binary files a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/curl-7.86.0.tar.gz and /dev/null differ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/curl_001_disable_wakeup.patch b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/curl_001_disable_wakeup.patch new file mode 100644 index 000000000..1bb067054 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/curl_001_disable_wakeup.patch @@ -0,0 +1,14 @@ +diff --git a/multihandle.h b/multihandle.h +index a26fb619a..18080f1c3 100644 +--- a/multihandle.h ++++ b/multihandle.h +@@ -70,10 +70,6 @@ typedef enum { + + #define CURLPIPE_ANY (CURLPIPE_MULTIPLEX) + +-#if !defined(CURL_DISABLE_SOCKETPAIR) +-#define ENABLE_WAKEUP +-#endif +- + /* value for MAXIMUM CONCURRENT STREAMS upper limit */ + #define INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1) \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/ContextImplTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/ContextImplTest.java new file mode 100644 index 000000000..b81660730 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/ContextImplTest.java @@ -0,0 +1,300 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + */ + +package com.amazonaws.services.lambda.crac; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.doThrow; + +public class ContextImplTest { + + private Resource throwsWithSuppressedException, noop, noop2, throwsException, throwCustomException; + + @BeforeEach + public void setup() throws Exception { + + throwsWithSuppressedException = Mockito.mock(Resource.class); + CheckpointException checkpointException = new CheckpointException(); + checkpointException.addSuppressed(new NumberFormatException()); + + RestoreException restoreException = new RestoreException(); + restoreException.addSuppressed(new NumberFormatException()); + + doThrow(checkpointException).when(throwsWithSuppressedException).beforeCheckpoint(ArgumentMatchers.any()); + doThrow(restoreException).when(throwsWithSuppressedException).afterRestore(ArgumentMatchers.any()); + + noop = Mockito.mock(Resource.class); + Mockito.doNothing().when(noop).beforeCheckpoint(ArgumentMatchers.any()); + Mockito.doNothing().when(noop).afterRestore(ArgumentMatchers.any()); + + noop2 = Mockito.mock(Resource.class); + Mockito.doNothing().when(noop2).beforeCheckpoint(ArgumentMatchers.any()); + Mockito.doNothing().when(noop2).afterRestore(ArgumentMatchers.any()); + + throwsException = Mockito.mock(Resource.class); + doThrow(CheckpointException.class).when(throwsException).beforeCheckpoint(ArgumentMatchers.any()); + doThrow(RestoreException.class).when(throwsException).afterRestore(ArgumentMatchers.any()); + + throwCustomException = Mockito.mock(Resource.class); + doThrow(IndexOutOfBoundsException.class).when(throwCustomException).beforeCheckpoint(ArgumentMatchers.any()); + doThrow(UnsupportedOperationException.class).when(throwCustomException).afterRestore(ArgumentMatchers.any()); + + Core.resetGlobalContext(); + } + + static class StatefulResource implements Resource { + + int state = 0; + + @Override + public void afterRestore(Context context) { + state += 1; + } + + @Override + public void beforeCheckpoint(Context context) { + state += 2; + } + + int getValue() { + return state; + } + } + + static int GLOBAL_STATE; + + static class ChangeGlobalStateResource implements Resource { + + ChangeGlobalStateResource() { + GLOBAL_STATE = 0; + } + + @Override + public void afterRestore(Context context) { + GLOBAL_STATE += 1; + } + + @Override + public void beforeCheckpoint(Context context) { + GLOBAL_STATE += 2; + } + } + + /** + * Happy path test with real / not mocked resource + */ + @Test + public void verifyHooksWereExecuted() throws CheckpointException, RestoreException { + StatefulResource resource = new StatefulResource(); + Core.getGlobalContext().register(resource); + + Core.getGlobalContext().beforeCheckpoint(null); + Core.getGlobalContext().afterRestore(null); + + assertEquals(3, resource.getValue()); + } + + /** + * This test is to validate GC intervention + */ + @Test + public void verifyHooksWereExecutedWithGC() throws CheckpointException, RestoreException { + StatefulResource resource = new StatefulResource(); + Core.getGlobalContext().register(resource); + gcAndSleep(); + + Core.getGlobalContext().beforeCheckpoint(null); + Core.getGlobalContext().afterRestore(null); + + assertEquals(3, resource.getValue()); + } + + @Test + public void verifyHooksAreNotExecutedForGarbageCollectedResources() throws CheckpointException, RestoreException { + Core.getGlobalContext().register(new ChangeGlobalStateResource()); + gcAndSleep(); + + Core.getGlobalContext().beforeCheckpoint(null); + Core.getGlobalContext().afterRestore(null); + + + assertEquals(0, GLOBAL_STATE); + } + + private static void gcAndSleep() { + for (int i = 0; i < 10; i++) { + System.gc(); + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + System.out.println("thread was interrupted"); + throw new RuntimeException(e); + } + } + + @Test + public void Should_NotifyResourcesInReverseOrderOfRegistration_When_CheckpointNotification() throws Exception { + // Given + InOrder checkpointNotificationOrder = Mockito.inOrder(noop, noop2); + Core.getGlobalContext().register(noop); + Core.getGlobalContext().register(noop2); + + // When + Core.getGlobalContext().beforeCheckpoint(null); + + // Then + checkpointNotificationOrder.verify(noop2).beforeCheckpoint(ArgumentMatchers.any()); + checkpointNotificationOrder.verify(noop).beforeCheckpoint(ArgumentMatchers.any()); + } + + @Test + public void Should_NotifyResourcesInOrderOfRegistration_When_RestoreNotification() throws Exception { + // Given + InOrder restoreNotificationOrder = Mockito.inOrder(noop, noop2); + Core.getGlobalContext().register(noop); + Core.getGlobalContext().register(noop2); + + // When + Core.getGlobalContext().afterRestore(null); + + // Then + restoreNotificationOrder.verify(noop).afterRestore(ArgumentMatchers.any()); + restoreNotificationOrder.verify(noop2).afterRestore(ArgumentMatchers.any()); + } + + @Test + public void Should_ResourcesAreAlwaysNotified_When_AnyNotificationThrowsException() throws Exception { + + // Given + Core.getGlobalContext().register(throwsWithSuppressedException); + Core.getGlobalContext().register(noop); + Core.getGlobalContext().register(noop2); + Core.getGlobalContext().register(throwsException); + Core.getGlobalContext().register(throwCustomException); + + // When + try { + Core.getGlobalContext().beforeCheckpoint(null); + } catch (Exception ignored) { + } + + try { + Core.getGlobalContext().afterRestore(null); + } catch (Exception ignored) { + } + + // Then + Mockito.verify(throwsWithSuppressedException, Mockito.times(1)).beforeCheckpoint(ArgumentMatchers.any()); + Mockito.verify(noop, Mockito.times(1)).beforeCheckpoint(ArgumentMatchers.any()); + Mockito.verify(noop2, Mockito.times(1)).beforeCheckpoint(ArgumentMatchers.any()); + Mockito.verify(throwsException, Mockito.times(1)).beforeCheckpoint(ArgumentMatchers.any()); + Mockito.verify(throwCustomException, Mockito.times(1)).beforeCheckpoint(ArgumentMatchers.any()); + + Mockito.verify(throwsWithSuppressedException, Mockito.times(1)).afterRestore(ArgumentMatchers.any()); + Mockito.verify(noop, Mockito.times(1)).afterRestore(ArgumentMatchers.any()); + Mockito.verify(noop2, Mockito.times(1)).afterRestore(ArgumentMatchers.any()); + Mockito.verify(throwsException, Mockito.times(1)).afterRestore(ArgumentMatchers.any()); + Mockito.verify(throwCustomException, Mockito.times(1)).afterRestore(ArgumentMatchers.any()); + } + + @Test + public void Should_CatchAndSuppressAnyExceptionsAsCheckpointException_When_CheckpointNotification() { + // Given + Core.getGlobalContext().register(throwsWithSuppressedException); + Core.getGlobalContext().register(throwCustomException); + + // When + try { + Core.getGlobalContext().beforeCheckpoint(null); + } catch (CheckpointException e1) { + // Then + assertEquals(2, e1.getSuppressed().length); + } catch (Throwable e2) { + fail("All exceptions thrown during checkpoint notification should be reported as CheckpointException"); + } + } + + @Test + public void Should_CatchAndSuppressAnyExceptionsAsRestoreException_When_RestoreNotification() { + // Given + Core.getGlobalContext().register(throwsWithSuppressedException); + Core.getGlobalContext().register(throwCustomException); + + // When + try { + Core.getGlobalContext().afterRestore(null); + } catch (RestoreException e1) { + // Then + assertEquals(2, e1.getSuppressed().length); + } catch (Exception e2) { + fail("All exceptions thrown during restore notification should be reported as RestoreException"); + } + } + + @Test + public void Should_SuppressOriginalCheckpointExceptionUnderAnotherCheckpointException_When_ResourceIsAContext() throws Exception { + // Given + Context c0 = Mockito.mock(Context.class); + doThrow(CheckpointException.class).when(c0).beforeCheckpoint(ArgumentMatchers.any()); + + Core.getGlobalContext().register(c0); + + // When + try { + Core.getGlobalContext().beforeCheckpoint(null); + } catch (CheckpointException e1) { + // Then + assertEquals(1, e1.getSuppressed().length); + assertTrue(e1.getSuppressed()[0] instanceof CheckpointException, + "When the Resource is a Context and it throws CheckpointException it should be suppressed under another CheckpointException"); + + } catch (Exception e2) { + fail("All exceptions thrown during checkpoint notification should be reported as CheckpointException"); + } + } + + @Test + public void Should_SuppressOriginalRestoreExceptionUnderAnotherRestoreException_When_ResourceIsAContext() throws Exception { + // Given + Context c0 = Mockito.mock(Context.class); + doThrow(RestoreException.class).when(c0).afterRestore(ArgumentMatchers.any()); + + Core.getGlobalContext().register(c0); + + // When + try { + Core.getGlobalContext().afterRestore(null); + } catch (RestoreException e1) { + // Then + assertEquals(1, e1.getSuppressed().length); + assertTrue(e1.getSuppressed()[0] instanceof RestoreException, + "When the Resource is a Context and it throws RestoreException it should be suppressed under another RestoreException"); + } catch (Exception e2) { + fail("All exceptions thrown during restore notification should be reported as RestoreException"); + } + } + + @Test + public void Should_NotifyOnlyOnce_When_ResourceRegistersMultipleTimes() throws Exception { + // Given + Core.getGlobalContext().register(noop); + Core.getGlobalContext().register(noop); + + // When + Core.getGlobalContext().beforeCheckpoint(null); + Core.getGlobalContext().afterRestore(null); + + // Then + Mockito.verify(noop, Mockito.times(1)).beforeCheckpoint(ArgumentMatchers.any()); + Mockito.verify(noop, Mockito.times(1)).afterRestore(ArgumentMatchers.any()); + } +} 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 new file mode 100644 index 000000000..171985dea --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java @@ -0,0 +1,77 @@ +package com.amazonaws.services.lambda.runtime.api.client; + +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.InvocationRequest; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class EventHandlerLoaderTest { + + @Test + void RequestHandlerTest() throws Exception { + String handler = "test.lambda.handlers.RequestHandlerImpl"; + LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandler(handler); + assertSuccessfulInvocation(lambdaRequestHandler); + } + + @Test + void RequestStreamHandlerTest() throws Exception { + String handler = "test.lambda.handlers.RequestStreamHandlerImpl"; + LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandler(handler); + assertSuccessfulInvocation(lambdaRequestHandler); + } + + @Test + void PojoHandlerTest_noParams() throws Exception { + String handler = "test.lambda.handlers.POJOHanlderImpl::noParamsHandler"; + LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandler(handler); + assertSuccessfulInvocation(lambdaRequestHandler); + } + + @Test + void PojoHandlerTest_oneParamEvent() throws Exception { + String handler = "test.lambda.handlers.POJOHanlderImpl::oneParamHandler_event"; + LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandler(handler); + assertSuccessfulInvocation(lambdaRequestHandler); + } + + + @Test + void PojoHandlerTest_oneParamContext() throws Exception { + String handler = "test.lambda.handlers.POJOHanlderImpl::oneParamHandler_context"; + LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandler(handler); + assertSuccessfulInvocation(lambdaRequestHandler); + } + + @Test + void PojoHandlerTest_twoParams() throws Exception { + String handler = "test.lambda.handlers.POJOHanlderImpl::twoParamsHandler"; + LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandler(handler); + assertSuccessfulInvocation(lambdaRequestHandler); + } + + private LambdaRequestHandler getLambdaRequestHandler(String handler) throws ClassNotFoundException { + ClassLoader cl = this.getClass().getClassLoader(); + HandlerInfo handlerInfo = HandlerInfo.fromString(handler, cl); + return EventHandlerLoader.loadEventHandler(handlerInfo); + } + + private static void assertSuccessfulInvocation(LambdaRequestHandler lambdaRequestHandler) throws Exception { + InvocationRequest invocationRequest = getTestInvocationRequest(); + + ByteArrayOutputStream resultBytes = lambdaRequestHandler.call(invocationRequest); + String result = resultBytes.toString(); + + assertEquals("\"success\"", result); + } + + private static InvocationRequest getTestInvocationRequest() { + InvocationRequest invocationRequest = new InvocationRequest(); + invocationRequest.setContent("\"Hello\"".getBytes()); + invocationRequest.setId("id"); + invocationRequest.setXrayTraceId("traceId"); + return invocationRequest; + } +} \ 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/UserFaultTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/UserFaultTest.java index cdc7a9b1d..9e88024b0 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/UserFaultTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/UserFaultTest.java @@ -4,10 +4,9 @@ import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; import static testpkg.StackTraceHelper.callThenThrowRuntimeException; +import static testpkg.StackTraceHelper.throwCheckpointExceptionWithTwoSuppressedExceptions; import static testpkg.StackTraceHelper.throwRuntimeException; public class UserFaultTest { @@ -71,4 +70,18 @@ public void testReportableErrorOnlyMessage() { String actual = userFault.reportableError(); assertEquals(expected, actual); } + + @Test + public void testSuppressedExceptionsAreIncluded() { + try{ + throwCheckpointExceptionWithTwoSuppressedExceptions("error 1", "error 2"); + } catch(Exception e1) { + UserFault userFault = UserFault.makeUserFault(e1); + String reportableUserFault = userFault.reportableError(); + + assertTrue(reportableUserFault.contains("com.amazonaws.services.lambda.crac.CheckpointException"), "CheckpointException missing in reported UserFault"); + assertTrue(reportableUserFault.contains("Suppressed: java.lang.RuntimeException: error 1"), "Suppressed error 1 missing in reported UserFault"); + assertTrue(reportableUserFault.contains("Suppressed: java.lang.RuntimeException: error 2"), "Suppressed error 2 missing in reported UserFault"); + } + } } 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 new file mode 100644 index 000000000..baeb4c242 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java @@ -0,0 +1,143 @@ +package com.amazonaws.services.lambda.runtime.api.client.logging; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import org.junit.jupiter.api.Test; + +import java.util.LinkedList; +import java.util.List; + +import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + + +public class AbstractLambdaLoggerTest { + class TestSink implements LogSink { + private List messages = new LinkedList<>(); + + public TestSink() { + } + + @Override + public void log(byte[] message) { + messages.add(message); + } + + @Override + public void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { + messages.add(message); + } + + @Override + public void close() { + } + + List getMessages() { + return messages; + } + } + + private void logMessages(LambdaLogger logger) { + logger.log("trace", LogLevel.TRACE); + logger.log("debug", LogLevel.DEBUG); + logger.log("info", LogLevel.INFO); + logger.log("warn", LogLevel.WARN); + logger.log("error", LogLevel.ERROR); + logger.log("fatal", LogLevel.FATAL); + } + + @Test + public void testLoggingNullValuesWithoutLogLevelInText() { + TestSink sink = new TestSink(); + LambdaLogger logger = new LambdaContextLogger(sink, LogLevel.INFO, LogFormat.TEXT); + + String isNullString = null; + byte[] isNullBytes = null; + + logger.log(isNullString); + logger.log(isNullBytes); + + assertEquals("null", new String(sink.getMessages().get(0))); + assertEquals("null", new String(sink.getMessages().get(1))); + } + + @Test + public void testLoggingNullValuesWithoutLogLevelInJSON() { + TestSink sink = new TestSink(); + LambdaLogger logger = new LambdaContextLogger(sink, LogLevel.INFO, LogFormat.JSON); + + String isNullString = null; + byte[] isNullBytes = null; + + logger.log(isNullString); + logger.log(isNullBytes); + + assertEquals(2, sink.getMessages().size()); + } + + @Test + public void testLoggingNullValuesWithLogLevelInText() { + TestSink sink = new TestSink(); + LambdaLogger logger = new LambdaContextLogger(sink, LogLevel.INFO, LogFormat.TEXT); + + String isNullString = null; + byte[] isNullBytes = null; + + logger.log(isNullString, LogLevel.ERROR); + logger.log(isNullBytes, LogLevel.ERROR); + + assertEquals("[ERROR] null", new String(sink.getMessages().get(0))); + assertEquals("null", new String(sink.getMessages().get(1))); + } + + @Test + public void testLoggingNullValuesWithLogLevelInJSON() { + TestSink sink = new TestSink(); + LambdaLogger logger = new LambdaContextLogger(sink, LogLevel.INFO, LogFormat.JSON); + + String isNullString = null; + byte[] isNullBytes = null; + + logger.log(isNullString, LogLevel.ERROR); + logger.log(isNullBytes, LogLevel.ERROR); + + assertEquals(2, sink.getMessages().size()); + } + @Test + public void testWithoutFiltering() { + TestSink sink = new TestSink(); + LambdaLogger logger = new LambdaContextLogger(sink, LogLevel.UNDEFINED, LogFormat.TEXT); + logMessages(logger); + + assertEquals(6, sink.getMessages().size()); + } + + @Test + public void testWithFiltering() { + TestSink sink = new TestSink(); + LambdaLogger logger = new LambdaContextLogger(sink, LogLevel.WARN, LogFormat.TEXT); + logMessages(logger); + + assertEquals(3, sink.getMessages().size()); + } + + @Test + public void testUndefinedLogLevelWithFiltering() { + TestSink sink = new TestSink(); + LambdaLogger logger = new LambdaContextLogger(sink, LogLevel.WARN, LogFormat.TEXT); + logger.log("undefined"); + + assertEquals(1, sink.getMessages().size()); + } + + @Test + public void testFormattingLogMessages() { + TestSink sink = new TestSink(); + LambdaLogger logger = new LambdaContextLogger(sink, LogLevel.INFO, LogFormat.TEXT); + logger.log("test message", LogLevel.INFO); + + assertEquals(1, sink.getMessages().size()); + assertEquals("[INFO] test message", new String(sink.getMessages().get(0))); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/FrameTypeTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/FrameTypeTest.java new file mode 100644 index 000000000..65078790c --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/FrameTypeTest.java @@ -0,0 +1,39 @@ +package com.amazonaws.services.lambda.runtime.api.client.logging; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import com.amazonaws.services.lambda.runtime.logging.LogLevel; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; + +public class FrameTypeTest { + + @Test + public void logFrames() { + assertHexEquals( + 0xa55a0003, + FrameType.getValue(LogLevel.UNDEFINED, LogFormat.TEXT) + ); + + assertHexEquals( + 0xa55a001b, + FrameType.getValue(LogLevel.FATAL, LogFormat.TEXT) + ); + } + + + /** + * Helper function to make it easier to debug failing test. + * + * @param expected Expected value as int + * @param actual Actual value as int + */ + private void assertHexEquals(int expected, int actual) { + assertEquals( + Integer.toHexString(expected), + Integer.toHexString(actual) + ); + } + +} diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/FramedTelemetryLogSinkTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/FramedTelemetryLogSinkTest.java index e8dbb73bb..e3e68a693 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/FramedTelemetryLogSinkTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/FramedTelemetryLogSinkTest.java @@ -20,6 +20,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; + public class FramedTelemetryLogSinkTest { private static final int DEFAULT_BUFFER_SIZE = 256; @@ -35,13 +38,16 @@ private long timestamp() { @Test public void logSingleFrame() throws IOException { - byte[] message = "hello world\nsomething on a new line!\n".getBytes(); + byte[] message = "{\"message\": \"hello world\nsomething on a new line!\"}".getBytes(); + LogLevel logLevel = LogLevel.ERROR; + LogFormat logFormat = LogFormat.JSON; + File tmpFile = tmpFolder.resolve("pipe").toFile(); FileOutputStream fos = new FileOutputStream(tmpFile); FileDescriptor fd = fos.getFD(); long before = timestamp(); try (FramedTelemetryLogSink logSink = new FramedTelemetryLogSink(fd)) { - logSink.log(message); + logSink.log(logLevel, logFormat, message); } long after = timestamp(); @@ -54,7 +60,7 @@ public void logSingleFrame() throws IOException { // first 4 bytes indicate the type int type = buf.getInt(); - assertEquals(FrameType.LOG.getValue(), type); + assertEquals(FrameType.getValue(logLevel, logFormat), type); // next 4 bytes indicate the length of the message int len = buf.getInt(); @@ -71,7 +77,7 @@ public void logSingleFrame() throws IOException { assertArrayEquals(message, actual); // rest of buffer should be empty - while(buf.hasRemaining()) + while (buf.hasRemaining()) assertEquals(ZERO_BYTE, buf.get()); } @@ -79,13 +85,16 @@ public void logSingleFrame() throws IOException { public void logMultipleFrames() throws IOException { byte[] firstMessage = "hello world\nsomething on a new line!".getBytes(); byte[] secondMessage = "hello again\nhere's another message\n".getBytes(); + LogLevel logLevel = LogLevel.ERROR; + LogFormat logFormat = LogFormat.TEXT; + File tmpFile = tmpFolder.resolve("pipe").toFile(); FileOutputStream fos = new FileOutputStream(tmpFile); FileDescriptor fd = fos.getFD(); long before = timestamp(); try (FramedTelemetryLogSink logSink = new FramedTelemetryLogSink(fd)) { - logSink.log(firstMessage); - logSink.log(secondMessage); + logSink.log(logLevel, logFormat, firstMessage); + logSink.log(logLevel, logFormat, secondMessage); } long after = timestamp(); @@ -96,10 +105,10 @@ public void logMultipleFrames() throws IOException { // reset the position to the start buf.position(0); - for(byte[] message : Arrays.asList(firstMessage, secondMessage)) { + for (byte[] message : Arrays.asList(firstMessage, secondMessage)) { // first 4 bytes indicate the type int type = buf.getInt(); - assertEquals(FrameType.LOG.getValue(), type); + assertEquals(FrameType.getValue(logLevel, logFormat), type); // next 4 bytes indicate the length of the message int len = buf.getInt(); @@ -117,7 +126,7 @@ public void logMultipleFrames() throws IOException { } // rest of buffer should be empty - while(buf.hasRemaining()) + while (buf.hasRemaining()) assertEquals(ZERO_BYTE, buf.get()); } @@ -125,7 +134,7 @@ public void logMultipleFrames() throws IOException { * The implementation of FramedTelemetryLogSink was based on java.nio.channels.WritableByteChannel which would * throw ClosedByInterruptException if Thread.currentThread.interrupt() was called. The implementation was changed * and this test ensures that logging works even if the current thread was interrupted. - * + *

* https://t.corp.amazon.com/0304370986/ */ @Test @@ -138,7 +147,7 @@ public void interruptedThread() throws IOException { try (FramedTelemetryLogSink logSink = new FramedTelemetryLogSink(fd)) { Thread.currentThread().interrupt(); - logSink.log(message); + logSink.log(LogLevel.ERROR, LogFormat.TEXT, message); } byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; @@ -150,8 +159,8 @@ public void interruptedThread() throws IOException { assertEquals(expectedBytes, readBytes); - for(int i = 0; i < message.length; i++) { - assertEquals(buffer[i + headerSizeBytes], message[i]); + for (int i = 0; i < message.length; i++) { + assertEquals(message[i], buffer[i + headerSizeBytes]); } } finally { // clear interrupted status of the current thread 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 new file mode 100644 index 000000000..8630d5fe6 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java @@ -0,0 +1,57 @@ +package com.amazonaws.services.lambda.runtime.api.client.logging; + +import com.amazonaws.services.lambda.runtime.api.client.api.LambdaContext; +import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; +import com.amazonaws.services.lambda.runtime.serialization.factories.GsonFactory; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +public class JsonLogFormatterTest { + + @Test + void testFormattingWithoutLambdaContext() { + assertFormatsString("test log", LogLevel.WARN, null); + } + + @Test + void testFormattingWithLambdaContext() { + LambdaContext context = new LambdaContext( + 0, + 0, + "request-id", + null, + null, + "function-name", + null, + null, + "function-arn", + null + ); + assertFormatsString("test log", LogLevel.WARN, context); + } + + void assertFormatsString(String message, LogLevel logLevel, LambdaContext context) { + JsonLogFormatter logFormatter = new JsonLogFormatter(); + if (context != null) { + logFormatter.setLambdaContext(context); + } + String output = logFormatter.format(message, logLevel); + + PojoSerializer serializer = GsonFactory.getInstance().getSerializer(StructuredLogMessage.class); + assert_expected_log_message(serializer.fromJson(output), message, logLevel, context); + } + + void assert_expected_log_message(StructuredLogMessage result, String message, LogLevel logLevel, LambdaContext context) { + assertEquals(message, result.message); + assertEquals(logLevel, result.level); + assertNotNull(result.timestamp); + + if (context != null) { + assertEquals(context.getAwsRequestId(), result.AWSRequestId); + } + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSinkTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSinkTest.java index 83399646b..b1bbefc4c 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSinkTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSinkTest.java @@ -10,6 +10,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + public class StdOutLogSinkTest { private final PrintStream originalOutPrintStream = System.out; @@ -35,6 +38,20 @@ public void testSingleLog() { assertEquals("hello\nworld", bos.toString()); } + @Test + public void testSingleLogWithLogLevel() { + System.setOut(capturedOutPrintStream); + try { + try (StdOutLogSink logSink = new StdOutLogSink()) { + logSink.log(LogLevel.ERROR, LogFormat.TEXT, "hello\nworld".getBytes()); + } + } finally { + System.setOut(originalOutPrintStream); + } + + assertEquals("hello\nworld", bos.toString()); + } + @Test public void testContextLoggerWithStdoutLogSink_logBytes() { System.setOut(capturedOutPrintStream); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/TextLogFormatterTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/TextLogFormatterTest.java new file mode 100644 index 000000000..598074a3b --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/TextLogFormatterTest.java @@ -0,0 +1,25 @@ +package com.amazonaws.services.lambda.runtime.api.client.logging; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +class TextLogFormatterTest { + @Test + void testFormattingStringWithLogLevel() { + assertFormatsString("test log", LogLevel.WARN, "[WARN] test log"); + } + + @Test + void testFormattingStringWithoutLogLevel() { + assertFormatsString("test log", LogLevel.UNDEFINED, "test log"); + } + + void assertFormatsString(String input, LogLevel logLevel, String expected) { + LogFormatter logFormatter = new TextLogFormatter(); + String output = logFormatter.format(input, logLevel); + assertEquals(expected, output); + } +} \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/serialization/factories/JacksonFactoryTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/serialization/factories/JacksonFactoryTest.java deleted file mode 100644 index 8fc97f2b0..000000000 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/serialization/factories/JacksonFactoryTest.java +++ /dev/null @@ -1,18 +0,0 @@ -/* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ - -package com.amazonaws.services.lambda.runtime.serialization.factories; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -public class JacksonFactoryTest { - - @Test - public void deserializeVoidAsNonNull() throws Exception { - JacksonFactory instance = JacksonFactory.getInstance(); - Void actual = instance.getMapper().readValue("{}", Void.class); - assertNotNull(actual); - } - -} diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/test/lambda/handlers/POJOHanlderImpl.java b/aws-lambda-java-runtime-interface-client/src/test/java/test/lambda/handlers/POJOHanlderImpl.java new file mode 100644 index 000000000..ca1a6bd4f --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/test/lambda/handlers/POJOHanlderImpl.java @@ -0,0 +1,26 @@ +package test.lambda.handlers; + +import com.amazonaws.services.lambda.runtime.Context; + +@SuppressWarnings("unused") +public class POJOHanlderImpl { + @SuppressWarnings("unused") + public String noParamsHandler() { + return "success"; + } + + @SuppressWarnings("unused") + public String oneParamHandler_event(String event) { + return "success"; + } + + @SuppressWarnings("unused") + public String oneParamHandler_context(Context context) { + return "success"; + } + + @SuppressWarnings("unused") + public String twoParamsHandler(String event, Context context) { + return "success"; + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/test/lambda/handlers/RequestHandlerImpl.java b/aws-lambda-java-runtime-interface-client/src/test/java/test/lambda/handlers/RequestHandlerImpl.java new file mode 100644 index 000000000..47fbade4d --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/test/lambda/handlers/RequestHandlerImpl.java @@ -0,0 +1,12 @@ +package test.lambda.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + + +public class RequestHandlerImpl implements RequestHandler { + @Override + public String handleRequest(String event, Context context) { + return "success"; + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/test/lambda/handlers/RequestStreamHandlerImpl.java b/aws-lambda-java-runtime-interface-client/src/test/java/test/lambda/handlers/RequestStreamHandlerImpl.java new file mode 100644 index 000000000..2bf2212c1 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/test/lambda/handlers/RequestStreamHandlerImpl.java @@ -0,0 +1,16 @@ +package test.lambda.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +@SuppressWarnings("unused") +public class RequestStreamHandlerImpl implements RequestStreamHandler { + @Override + public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { + output.write("\"success\"".getBytes()); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/testpkg/StackTraceHelper.java b/aws-lambda-java-runtime-interface-client/src/test/java/testpkg/StackTraceHelper.java index b9442c25e..c7d8cb834 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/testpkg/StackTraceHelper.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/testpkg/StackTraceHelper.java @@ -2,9 +2,11 @@ package testpkg; +import com.amazonaws.services.lambda.crac.CheckpointException; + /** - * A helper class for throwing exception which is not in the `lambdainternal` package to avoid the stack traces from - * being filtered out. + * A helper class for throwing exception which is not in the com.amazonaws.services.lambda.runtime.api.client package + * to avoid the stack traces from being filtered out. * */ public class StackTraceHelper { @@ -21,4 +23,11 @@ public static void throwRuntimeException(String msg){ public static void callThenThrowRuntimeException(String msg){ throwRuntimeException(msg); } + + public static void throwCheckpointExceptionWithTwoSuppressedExceptions(String msg1, String msg2) throws CheckpointException { + CheckpointException e1 = new CheckpointException(); + e1.addSuppressed(new RuntimeException(msg1)); + e1.addSuppressed(new RuntimeException(msg2)); + throw e1; + } } 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 5cf074d1e..028e92f56 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.1.1 + 2.4.1 diff --git a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md index d1a3cd97d..5ca416845 100644 --- a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md @@ -1,3 +1,16 @@ +### December 1, 2023 +`1.1.5`: +- Add support for DynamodbEvent.DynamodbStreamRecord serialization + +### October 19, 2023 +`1.1.4`: +- Update org.json version to 20231013 +- Rollback relocation changes(1.1.3 version) + +### September 21, 2023 +`1.1.3`: +- Add support for event v4 lib + ### February 22, 2023 `1.1.1`: - Register `JodaModule` to JacksonFactory diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index 1885311cb..07ccecc8c 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.1 + 1.1.5 jar AWS Lambda Java Runtime Serialization @@ -34,7 +34,7 @@ com.amazonaws.lambda.thirdparty 2.14.2 2.10.1 - 5.9.1 + 20231013 7.3.2 @@ -68,26 +68,7 @@ org.json json - 20160810 - - - - org.junit.jupiter - junit-jupiter-engine - ${junit.version} - test - - - org.junit.jupiter - junit-jupiter-params - ${junit.version} - test - - - com.amazonaws - aws-lambda-java-events - 3.11.0 - test + ${json.version} @@ -235,13 +216,19 @@ com.google.gson ${relocation.prefix}.com.google.gson + + org.json + ${relocation.prefix}.org.json + org.joda.time ${relocation.prefix}.org.joda.time + - org.json - ${relocation.prefix}.org.json + com.amazonaws.lambda.unshade.thirdparty.org.joda.time + org.joda.time 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 b24b40609..4173211e1 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 @@ -158,7 +158,7 @@ public class LambdaEventSerializers { * 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( + 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"))), @@ -180,6 +180,14 @@ public class LambdaEventSerializers { "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( @@ -236,7 +244,7 @@ public static PojoSerializer serializerFor(Class eventClass, ClassLoad } // 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()); + 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)) { diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateTimeModule.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateTimeModule.java index 592d05338..a02857e00 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateTimeModule.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateTimeModule.java @@ -23,7 +23,8 @@ public class DateTimeModule extends JodaModule { * creates a DateTimeModule using customer class loader to pull org.joda.time.DateTime */ public DateTimeModule(ClassLoader classLoader) { - Class dateTimeClass = SerializeUtil.loadCustomerClass("org.joda.time.DateTime", classLoader); + // Workaround not to let maven shade plugin relocating string literals https://issues.apache.org/jira/browse/MSHADE-156 + Class dateTimeClass = SerializeUtil.loadCustomerClass("com.amazonaws.lambda.unshade.thirdparty.org.joda.time.DateTime", classLoader); this.addSerializer(dateTimeClass, getSerializer(dateTimeClass, classLoader)); this.addDeserializer(dateTimeClass, getDeserializer(dateTimeClass)); } diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/serializers/S3EventSerializer.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/serializers/S3EventSerializer.java index 6c6f65870..c833abcc1 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/serializers/S3EventSerializer.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/serializers/S3EventSerializer.java @@ -220,7 +220,8 @@ private JSONObject serializeEventNotificationRecord(A eventNotificationRecor Class requestParametersClass = SerializeUtil.loadCustomerClass(baseClassName + "$RequestParametersEntity", classLoader); Class responseElementsClass = SerializeUtil.loadCustomerClass(baseClassName + "$ResponseElementsEntity", classLoader); Class userIdentityClass = SerializeUtil.loadCustomerClass(baseClassName + "$UserIdentityEntity", classLoader); - Class dateTimeClass = SerializeUtil.loadCustomerClass("org.joda.time.DateTime", classLoader); + // Workaround not to let maven shade plugin relocating string literals https://issues.apache.org/jira/browse/MSHADE-156 + Class dateTimeClass = SerializeUtil.loadCustomerClass("com.amazonaws.lambda.unshade.thirdparty.org.joda.time.DateTime", classLoader); // serialize object JSONObject jsonObject = new JSONObject(); Functions.R0 getAwsRegionMethod = diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/factories/JacksonFactory.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/factories/JacksonFactory.java index f7c1dd4a4..660ca8f58 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/factories/JacksonFactory.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/factories/JacksonFactory.java @@ -114,37 +114,12 @@ private static ObjectMapper createObjectMapper() { mapper.setConfig(dcfg); mapper.setSerializationInclusion(Include.NON_NULL); - SimpleModule module = new SimpleModule(); - module.addDeserializer(Void.class, new VoidDeserializer()); - mapper.registerModule(module); - mapper.registerModule(new JavaTimeModule()); mapper.registerModule(new Jdk8Module()); return mapper; } - public static final class VoidDeserializer extends JsonDeserializer { - - private final static Void VOID = createVoid(); - - private static Void createVoid() { - try { - Constructor constructor = Void.class.getDeclaredConstructor(); - constructor.setAccessible(true); - return constructor.newInstance(); - } catch(Exception e) { - return null; - } - } - - @Override - public Void deserialize(JsonParser parser, DeserializationContext ctx) { - return VOID; - } - - } - private static JsonFactory createJsonFactory() { JsonFactory factory = JsonFactory.builder() //Json Read enabled diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/util/SerializeUtil.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/util/SerializeUtil.java index bd4c7450e..f6acb528f 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/util/SerializeUtil.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/util/SerializeUtil.java @@ -71,9 +71,10 @@ public static T deserializeDateTime(Class dateTimeClass, String dateTimeS */ @SuppressWarnings({"unchecked"}) public static String serializeDateTime(T dateTime, ClassLoader classLoader) { - Class dateTimeFormatterClass = loadCustomerClass("org.joda.time.format.DateTimeFormatter", classLoader); - Class dateTimeFormatClass = loadCustomerClass("org.joda.time.format.ISODateTimeFormat", classLoader); - Class readableInstantInterface = loadCustomerClass("org.joda.time.ReadableInstant", classLoader); + // Workaround not to let maven shade plugin relocating string literals https://issues.apache.org/jira/browse/MSHADE-156 + Class dateTimeFormatterClass = loadCustomerClass("com.amazonaws.lambda.unshade.thirdparty.org.joda.time.format.DateTimeFormatter", classLoader); + Class dateTimeFormatClass = loadCustomerClass("com.amazonaws.lambda.unshade.thirdparty.org.joda.time.format.ISODateTimeFormat", classLoader); + Class readableInstantInterface = loadCustomerClass("com.amazonaws.lambda.unshade.thirdparty.org.joda.time.ReadableInstant", classLoader); return serializeDateTimeHelper(dateTime, dateTimeFormatterClass, dateTimeFormatClass, readableInstantInterface); } diff --git a/aws-lambda-java-serialization/src/test/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializersTest.java b/aws-lambda-java-serialization/src/test/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializersTest.java deleted file mode 100644 index 13358f722..000000000 --- a/aws-lambda-java-serialization/src/test/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializersTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ - -package com.amazonaws.services.lambda.runtime.serialization.events; - -import com.amazonaws.services.lambda.runtime.events.*; -import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class LambdaEventSerializersTest { - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - public static final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader(); - - private static Stream serdeArguments() { - return Stream.of( - Arguments.of("api_gateway_proxy_request_event.json", APIGatewayProxyRequestEvent.class), - Arguments.of("api_gateway_proxy_response_event.json", APIGatewayProxyResponseEvent.class), - Arguments.of("cloud_front_event.json", CloudFrontEvent.class), - Arguments.of("cloud_watch_logs_event.json", CloudWatchLogsEvent.class), - Arguments.of("code_commit_event.json", CodeCommitEvent.class), - Arguments.of("api_gateway_proxy_response_event.json", APIGatewayProxyResponseEvent.class), - Arguments.of("cognito_event.json", CognitoEvent.class), - Arguments.of("config_event.json", ConfigEvent.class), - Arguments.of("dynamodb_event.json", DynamodbEvent.class), - Arguments.of("dynamodb_time_window_event.json", DynamodbTimeWindowEvent.class), - Arguments.of("iot_button_event.json", IoTButtonEvent.class), - Arguments.of("kinesis_analytics_firehose_input_preprocessing_event.json", KinesisAnalyticsFirehoseInputPreprocessingEvent.class), - Arguments.of("kinesis_analytics_input_preprocessing_response_event.json", KinesisAnalyticsInputPreprocessingResponse.class), - Arguments.of("kinesis_analytics_output_delivery_event.json", KinesisAnalyticsOutputDeliveryEvent.class), - Arguments.of("kinesis_analytics_output_delivery_response_event.json", KinesisAnalyticsOutputDeliveryResponse.class), - Arguments.of("kinesis_analytics_streams_input_preprocessing_event.json", KinesisAnalyticsStreamsInputPreprocessingEvent.class), - Arguments.of("kinesis_event.json", KinesisEvent.class), - Arguments.of("kinesis_time_window_event.json", KinesisTimeWindowEvent.class), - Arguments.of("kinesis_firehose_event.json", KinesisFirehoseEvent.class), - Arguments.of("lex_event.json", LexEvent.class), - Arguments.of("s3_event.json", S3Event.class), - Arguments.of("scheduled_event.json", ScheduledEvent.class), - Arguments.of("sns_event.json", SNSEvent.class), - Arguments.of("sqs_event.json", SQSEvent.class) - ); - } - - @ParameterizedTest(name = "Serde {0} Event") - @MethodSource("serdeArguments") - public void testAPIGatewayProxyRequestEvent(final String json, - final Class eventClass) throws IOException { - String expected = readEvent(json); - String actual = deserializeSerializeJsonToString(expected, eventClass); - - assertJsonEqual(expected, actual); - } - - private String readEvent(String filename) throws IOException { - Path filePath = Paths.get("src", "test", "resources", "event_models", filename); - byte[] bytes = Files.readAllBytes(filePath); - return bytesToString(bytes); - } - - private String deserializeSerializeJsonToString(String expected, Class modelClass) { - PojoSerializer serializer = LambdaEventSerializers.serializerFor(modelClass, SYSTEM_CLASS_LOADER); - - T event = serializer.fromJson(expected); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - serializer.toJson(event, baos); - return bytesToString(baos.toByteArray()); - } - - private String bytesToString(byte[] bytes) { - return new String(bytes, StandardCharsets.UTF_8); - } - - private void assertJsonEqual(String expected, String actual) throws IOException { - assertEquals(OBJECT_MAPPER.readTree(expected), OBJECT_MAPPER.readTree(actual)); - } -} diff --git a/aws-lambda-java-serialization/src/test/java/com/amazonaws/services/lambda/runtime/serialization/events/serializers/S3EventSerializerTest.java b/aws-lambda-java-serialization/src/test/java/com/amazonaws/services/lambda/runtime/serialization/events/serializers/S3EventSerializerTest.java deleted file mode 100644 index 8b2d2e875..000000000 --- a/aws-lambda-java-serialization/src/test/java/com/amazonaws/services/lambda/runtime/serialization/events/serializers/S3EventSerializerTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ - -package com.amazonaws.services.lambda.runtime.serialization.events.serializers; - -import com.amazonaws.services.lambda.runtime.events.S3Event; -import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import static org.junit.jupiter.api.Assertions.assertEquals; - - -public class S3EventSerializerTest { - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - public static final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader(); - - @Test - public void testSerDeS3Event() throws IOException { - S3EventSerializer s3EventSerializer = getS3EventSerializerWithClass(S3Event.class); - - String expected = readEvent("s3_event.json"); - String actual = deserializeSerializeJsonToString(s3EventSerializer, expected); - - assertJsonEqual(expected, actual); - } - - @Test - public void testSerDeS3EventNotification() throws IOException { - S3EventSerializer s3EventSerializer = getS3EventSerializerWithClass(S3EventNotification.class); - - String expected = readEvent("s3_event.json"); - String actual = deserializeSerializeJsonToString(s3EventSerializer, expected); - - assertJsonEqual(expected, actual); - } - - private S3EventSerializer getS3EventSerializerWithClass(Class modelClass) { - return new S3EventSerializer() - .withClass(modelClass) - .withClassLoader(SYSTEM_CLASS_LOADER); - } - - private String readEvent(String filename) throws IOException { - Path filePath = Paths.get("src", "test", "resources", "event_models", filename); - byte[] bytes = Files.readAllBytes(filePath); - return bytesToString(bytes); - } - - private String bytesToString(byte[] bytes) { - return new String(bytes, StandardCharsets.UTF_8); - } - - private void assertJsonEqual(String expected, String actual) throws IOException { - assertEquals(OBJECT_MAPPER.readTree(expected), OBJECT_MAPPER.readTree(actual)); - } - - private String deserializeSerializeJsonToString(S3EventSerializer s3EventSerializer, String expected) { - T event = s3EventSerializer.fromJson(expected); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - s3EventSerializer.toJson(event, baos); - return bytesToString(baos.toByteArray()); - } - -} diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/api_gateway_proxy_request_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/api_gateway_proxy_request_event.json deleted file mode 100644 index 66c5d9535..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/api_gateway_proxy_request_event.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "version": "1.0", - "path": "/test/hello", - "headers": { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Accept-Encoding": "gzip, deflate, lzma, sdch, br", - "Accept-Language": "en-US,en;q=0.8", - "CloudFront-Forwarded-Proto": "https", - "CloudFront-Is-Desktop-Viewer": "true", - "CloudFront-Is-Mobile-Viewer": "false", - "CloudFront-Is-SmartTV-Viewer": "false", - "CloudFront-Is-Tablet-Viewer": "false", - "CloudFront-Viewer-Country": "US", - "Host": "wt6mne2s9k.execute-api.us-west-2.amazonaws.com", - "Upgrade-Insecure-Requests": "1", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48", - "Via": "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)", - "X-Amz-Cf-Id": "nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g==", - "X-Forwarded-For": "192.168.100.1, 192.168.1.1", - "X-Forwarded-Port": "443", - "X-Forwarded-Proto": "https" - }, - "pathParameters": { - "proxy": "hello" - }, - "requestContext": { - "path": "/{proxy+}", - "accountId": "123456789012", - "resourceId": "nl9h80", - "stage": "test-invoke-stage", - "requestId": "test-invoke-request", - "identity": { - "cognitoIdentityPoolId": "", - "accountId": "123456789012", - "cognitoIdentityId": "", - "caller": "AIDAJTIRKKKER4HCKVJZG", - "apiKey": "test-invoke-api-key", - "sourceIp": "test-invoke-source-ip", - "accessKey": "ASIAI6ANUE2RZBMJDQ5A", - "cognitoAuthenticationType": "", - "cognitoAuthenticationProvider": "", - "userArn": "arn:aws:iam::123456789012:user/kdeding", - "userAgent": "Apache-HttpClient/4.5.x (Java/1.8.0_131)", - "user": "AIDAJTIRKKKER4HCKVJZG" - }, - "resourcePath": "/{proxy+}", - "httpMethod": "POST", - "apiId": "r275xc9bmd" - }, - "resource": "/{proxy+}", - "httpMethod": "GET", - "isBase64Encoded": false, - "body": "{ \"callerName\": \"John\" }", - "queryStringParameters": { - "name": "me" - }, - "stageVariables": { - "stageVarName": "stageVarValue" - } -} \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/api_gateway_proxy_response_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/api_gateway_proxy_response_event.json deleted file mode 100644 index 1b18bcfde..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/api_gateway_proxy_response_event.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "statusCode": 200, - "headers": { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Accept-Encoding": "gzip, deflate, lzma, sdch, br", - "Accept-Language": "en-US,en;q=0.8", - "CloudFront-Forwarded-Proto": "https", - "CloudFront-Is-Desktop-Viewer": "true", - "CloudFront-Is-Mobile-Viewer": "false", - "CloudFront-Is-SmartTV-Viewer": "false", - "CloudFront-Is-Tablet-Viewer": "false", - "CloudFront-Viewer-Country": "US", - "Host": "wt6mne2s9k.execute-api.us-west-2.amazonaws.com", - "Upgrade-Insecure-Requests": "1", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48", - "Via": "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)", - "X-Amz-Cf-Id": "nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g==", - "X-Forwarded-For": "192.168.100.1, 192.168.1.1", - "X-Forwarded-Port": "443", - "X-Forwarded-Proto": "https" - }, - "body": "Hello World" -} \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/cloud_front_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/cloud_front_event.json deleted file mode 100644 index e91674807..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/cloud_front_event.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EDFDVBD6EXAMPLE" - }, - "request": { - "clientIp": "2001:0db8:85a3:0:0:8a2e:0370:7334", - "method": "GET", - "uri": "/picture.jpg", - "headers": { - "host": [ - { - "key": "Host", - "value": "d111111abcdef8.cloudfront.net" - } - ], - "user-agent": [ - { - "key": "User-Agent", - "value": "curl/7.51.0" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/cloud_watch_logs_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/cloud_watch_logs_event.json deleted file mode 100644 index 2b455b9bc..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/cloud_watch_logs_event.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "awslogs": { - "data": "H4sIAAAAAAAAAHWPwQqCQBCGX0Xm7EFtK+smZBEUgXoLCdMhFtKV3akI8d0bLYmibvPPN3wz00CJxmQnTO41whwWQRIctmEcB6sQbFC3CjW3XW8kxpOpP+OC22d1Wml1qZkQGtoMsScxaczKN3plG8zlaHIta5KqWsozoTYw3/djzwhpLwivWFGHGpAFe7DL68JlBUk+l7KSN7tCOEJ4M3/qOI49vMHj+zCKdlFqLaU2ZHV2a4Ct/an0/ivdX8oYc1UVX860fQDQiMdxRQEAAA==" - } -} \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/code_commit_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/code_commit_event.json deleted file mode 100644 index 4e111cdd1..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/code_commit_event.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "Records": [ - { - "eventId": "163179e8-a983-4e22-8423-38473a2589fd", - "eventVersion": "1.0", - "eventTime": "2017-07-17T19:38:56.745Z", - "eventTriggerName": "notifyme", - "eventPartNumber": 1, - "codecommit": { - "references": [ - { - "commit": "7a95e78397313b32dc3c1beda4f5c2676c0c1bea", - "ref": "refs/heads/master", - "created": true - } - ] - }, - "eventName": "ReferenceChanges", - "eventTriggerConfigId": "e6f61a39-7e60-47b0-b206-0dfbd2ca0e6e", - "eventSourceARN": "arn:aws:codecommit:us-west-2:303480592763:garbage", - "userIdentityARN": "arn:aws:sts::303480592763:assumed-role/Admin/adsuresh-Isengard", - "eventSource": "aws:codecommit", - "awsRegion": "us-west-2", - "eventTotalParts": 1 - } - ] -} \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/cognito_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/cognito_event.json deleted file mode 100644 index 88cc60627..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/cognito_event.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 2, - "eventType": "SyncTrigger", - "region": "us-east-1", - "identityPoolId": "identityPoolId", - "identityId": "identityId", - "datasetName": "datasetName", - "datasetRecords": { - "sampleKey1": { - "oldValue": "oldValue1", - "newValue": "newValue1", - "op": "replace" - }, - "sampleKey2": { - "oldValue": "oldValue2", - "newValue": "newValue2", - "op": "replace" - } - } -} diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/config_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/config_event.json deleted file mode 100644 index a5d158de4..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/config_event.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2016-02-17T01:36:34.043Z\",\"awsAccountId\":\"000000000000\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"i-00000000\",\"ARN\":\"arn:aws:ec2:us-east-1:000000000000:instance/i-00000000\",\"awsRegion\":\"us-east-1\",\"availabilityZone\":\"us-east-1a\",\"resourceType\":\"AWS::EC2::Instance\",\"tags\":{\"Foo\":\"Bar\"},\"relationships\":[{\"resourceId\":\"eipalloc-00000000\",\"resourceType\":\"AWS::EC2::EIP\",\"name\":\"Is attached to ElasticIp\"}],\"configuration\":{\"foo\":\"bar\"}},\"messageType\":\"ConfigurationItemChangeNotification\"}", - "ruleParameters": "{\"myParameterKey\":\"myParameterValue\"}", - "resultToken": "myResultToken", - "eventLeftScope": false, - "executionRoleArn": "arn:aws:iam::012345678912:role/config-role", - "configRuleArn": "arn:aws:config:us-east-1:012345678912:config-rule/config-rule-0123456", - "configRuleName": "change-triggered-config-rule", - "configRuleId": "config-rule-0123456", - "accountId": "012345678912", - "version": "1.0" -} \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/dynamodb_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/dynamodb_event.json deleted file mode 100644 index 6a9c6ac4f..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/dynamodb_event.json +++ /dev/null @@ -1,166 +0,0 @@ -{ - "Records": [ - { - "eventID": "1", - "dynamodb": { - "Keys": { - "Id": { - "N": "101" - } - }, - "NewImage": { - "Message": { - "S": "New item!" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES", - "SequenceNumber": "111", - "SizeBytes": 26 - }, - "awsRegion": "us-west-2", - "eventName": "INSERT", - "eventSourceARN": "arn:aws:dynamodb:us-west-2:account-id:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - }, - { - "eventID": "2", - "dynamodb": { - "OldImage": { - "Message": { - "S": "New item!" - }, - "Id": { - "N": "101" - } - }, - "SequenceNumber": "222", - "Keys": { - "Id": { - "N": "101" - } - }, - "SizeBytes": 59, - "NewImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "awsRegion": "us-west-2", - "eventName": "MODIFY", - "eventSourceARN": "arn:aws:dynamodb:us-west-2:account-id:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - }, - { - "eventID": "3", - "dynamodb": { - "Keys": { - "Id": { - "N": "101" - } - }, - "SizeBytes": 38, - "SequenceNumber": "333", - "OldImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "awsRegion": "us-west-2", - "eventName": "REMOVE", - "eventSourceARN": "arn:aws:dynamodb:us-west-2:account-id:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - }, - { - "eventID": "77742f55658ca1effb9ae758c65c8a51", - "eventName": "INSERT", - "eventSource": "aws:dynamodb", - "awsRegion": "us-west-2", - "dynamodb": { - "ApproximateCreationDateTime": 1.44130524E9, - "Keys": { - "Id": { - "N": "147" - } - }, - "NewImage": { - "attr_M": { - "M": { - "attr_M1": { - "N": "1580756226" - }, - "attr_M2": { - "S": "1580756226" - } - } - }, - "attr_L": { - "L": [ - { - "N": "1580756226" - }, - { - "S": "1580756226" - } - ] - }, - "attr_BOOL": { - "BOOL": true - }, - "s3info": { - "S": "lambda-ddb-streams-int-test/a2b4c06b-2360-44ac-9bd7-2bab669246b8" - }, - "attr_NULL": { - "NULL": true - }, - "attr_NS": { - "NS": [ - "1580756227", - "1580756226" - ] - }, - "attr_SS": { - "SS": [ - "1580756226", - "1580756227" - ] - }, - "attr_BS": { - "BS": [ - "MTU4MDc1NjIyNg==", - "MTU4MDc1NjIyNw==" - ] - }, - "attr_S": { - "S": "1580756226" - }, - "Id": { - "N": "147" - }, - "attr_B": { - "B": "MTU4MDc1NjIyNg==" - }, - "attr_N": { - "N": "1580756226" - } - }, - "SequenceNumber": "2700000000000002229109", - "SizeBytes": 285, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "eventSourceARN": "arn:aws:dynamodb:us-west-2:059493405231:table/lambda-ddb-streams-int-test/stream/2015-06-26T18:21:25.123" - } - ] -} diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/dynamodb_time_window_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/dynamodb_time_window_event.json deleted file mode 100644 index 237b7bd2f..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/dynamodb_time_window_event.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "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": "stream-ARN" - }, - { - "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": "stream-ARN" - }, - { - "eventID": "3", - "eventName": "REMOVE", - "eventVersion": "1.0", - "eventSource": "aws:dynamodb", - "awsRegion": "us-east-1", - "dynamodb": { - "Keys": { - "Id": { - "N": "101" - } - }, - "OldImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "SequenceNumber": "333", - "SizeBytes": 38, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "eventSourceARN": "stream-ARN" - } - ], - "window": { - "start": "2021-10-26T17:00:00Z", - "end": "2021-10-26T17:05:00Z" - }, - "state": { - "1": "state1" - }, - "shardId": "shard123456789", - "eventSourceARN": "stream-ARN", - "isFinalInvokeForWindow": false, - "isWindowTerminatedEarly": false -} diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/iot_button_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/iot_button_event.json deleted file mode 100644 index 8988caf0e..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/iot_button_event.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "serialNumber": "ABCDEFG12345", - "clickType": "SINGLE", - "batteryVoltage": "2000 mV" -} \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_firehose_input_preprocessing_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_firehose_input_preprocessing_event.json deleted file mode 100644 index 862f17f29..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_firehose_input_preprocessing_event.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "invocationId":"00540a87-5050-496a-84e4-e7d92bbaf5e2", - "applicationArn":"arn:aws:kinesisanalytics:us-east-1:12345678911:application/lambda-test", - "streamArn":"arn:aws:firehose:us-east-1:AAAAAAAAAAAA:deliverystream/lambda-test", - "records":[ - { - "recordId":"49572672223665514422805246926656954630972486059535892482", - "data":"aGVsbG8gd29ybGQ=", - "kinesisFirehoseRecordMetadata":{ - "approximateArrivalTimestamp":1520280173 - } - } - ] - } \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_input_preprocessing_response_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_input_preprocessing_response_event.json deleted file mode 100644 index 59b899d6b..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_input_preprocessing_response_event.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "records": [ - { - "recordId": "49572672223665514422805246926656954630972486059535892482", - "result": "Ok", - "data": "SEVMTE8gV09STEQ=" - } - ] - } - \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_output_delivery_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_output_delivery_event.json deleted file mode 100644 index d42688d73..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_output_delivery_event.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "invocationId": "00540a87-5050-496a-84e4-e7d92bbaf5e2", - "applicationArn": "arn:aws:kinesisanalytics:us-east-1:12345678911:application/lambda-test", - "records": [ - { - "recordId": "49572672223665514422805246926656954630972486059535892482", - "data": "aGVsbG8gd29ybGQ=" - } - ] -} \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_output_delivery_response_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_output_delivery_response_event.json deleted file mode 100644 index dba7b21d1..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_output_delivery_response_event.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "records": [ - { - "recordId": "49572672223665514422805246926656954630972486059535892482", - "result": "Ok" - } - ] -} diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_streams_input_preprocessing_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_streams_input_preprocessing_event.json deleted file mode 100644 index f7ec383bd..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_analytics_streams_input_preprocessing_event.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "invocationId": "00540a87-5050-496a-84e4-e7d92bbaf5e2", - "applicationArn": "arn:aws:kinesisanalytics:us-east-1:12345678911:application/lambda-test", - "streamArn": "arn:aws:kinesis:us-east-1:AAAAAAAAAAAA:stream/lambda-test", - "records": [ - { - "recordId": "49572672223665514422805246926656954630972486059535892482", - "data": "aGVsbG8gd29ybGQ=", - "kinesisStreamRecordMetadata":{ - "shardId" :"shardId-000000000003", - "partitionKey":"7400791606", - "sequenceNumber":"49572672223665514422805246926656954630972486059535892482", - "approximateArrivalTimestamp":1520280173 - } - } - ] -} \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_event.json deleted file mode 100644 index 854794cfd..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_event.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "Records": [ - { - "kinesis": { - "partitionKey": "partitionKey-3", - "kinesisSchemaVersion": "1.0", - "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=", - "sequenceNumber": "49545115243490985018280067714973144582180062593244200961" - }, - "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": "us-east-1" - }, - { - "kinesis": { - "partitionKey": "partitionKey-3", - "kinesisSchemaVersion": "1.0", - "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=", - "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", - "approximateArrivalTimestamp": 1.455606024806E9 - }, - "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": "us-east-1" - } - ] -} diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_firehose_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_firehose_event.json deleted file mode 100644 index 3b1cd6c76..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_firehose_event.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "invocationId": "invoked123", - "deliveryStreamArn": "aws:lambda:events", - "region": "us-west-2", - "records": [ - { - "data": "SGVsbG8gV29ybGQ=", - "recordId": "record1", - "approximateArrivalEpoch": 1507217624302, - "approximateArrivalTimestamp": 1507217624302, - "kinesisRecordMetadata": { - "shardId": "shardId-000000000000", - "partitionKey": "4d1ad2b9-24f8-4b9d-a088-76e9947c317a", - "approximateArrivalTimestamp": "1507217624302", - "sequenceNumber": "49546986683135544286507457936321625675700192471156785154", - "subsequenceNumber": "" - } - }, - { - "data": "SGVsbG8gV29ybGQ=", - "recordId": "record2", - "approximateArrivalEpoch": 1507217624302, - "approximateArrivalTimestamp": 1507217624302, - "kinesisRecordMetadata": { - "shardId": "shardId-000000000001", - "partitionKey": "4d1ad2b9-24f8-4b9d-a088-76e9947c318a", - "approximateArrivalTimestamp": "1507217624302", - "sequenceNumber": "49546986683135544286507457936321625675700192471156785155", - "subsequenceNumber": "" - } - } - ] -} \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_time_window_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_time_window_event.json deleted file mode 100644 index 024992260..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/kinesis_time_window_event.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "Records": [ - { - "kinesis": { - "kinesisSchemaVersion": "1.0", - "partitionKey": "1", - "sequenceNumber": "49590338271490256608559692538361571095921575989136588898", - "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0Lg==", - "approximateArrivalTimestamp": 1545084650.987 - }, - "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-2", - "eventSourceARN": "arn:aws:kinesis:us-east-2:123456789012:stream/lambda-stream" - } - ], - "window": { - "start": "2021-10-26T17:00:00Z", - "end": "2021-10-26T17:05:00Z" - }, - "state": { - "1": "state1" - }, - "shardId": "shardId-000000000006", - "eventSourceARN": "arn:aws:kinesis:us-east-2:123456789012:stream/lambda-stream", - "isFinalInvokeForWindow": false, - "isWindowTerminatedEarly": false -} diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/lex_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/lex_event.json deleted file mode 100644 index 2961ac28c..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/lex_event.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "messageVersion": "1.0", - "invocationSource": "FulfillmentCodeHook or DialogCodeHook", - "userId": "user-id specified in the POST request to Amazon Lex.", - "sessionAttributes": { - "key1": "value1", - "key2": "value2" - }, - "bot": { - "name": "bot-name", - "alias": "bot-alias", - "version": "bot-version" - }, - "outputDialogMode": "Text or Voice, based on ContentType request header in runtime API request", - "currentIntent": { - "name": "intent-name", - "slots": { - "slot1": "value", - "slot2": "value", - "slot3": "value" - }, - "confirmationStatus": "None" - } -} \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/s3_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/s3_event.json deleted file mode 100644 index 519bc1ce0..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/s3_event.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "Records": [ - { - "eventVersion": "2.0", - "eventSource": "aws:s3", - "awsRegion": "us-east-1", - "eventTime": "1970-01-01T00:00:00.000Z", - "eventName": "ObjectCreated:Put", - "userIdentity": { - "principalId": "EXAMPLE" - }, - "requestParameters": { - "sourceIPAddress": "127.0.0.1" - }, - "responseElements": { - "x-amz-request-id": "C3D13FE58DE4C810", - "x-amz-id-2": "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" - }, - "s3": { - "s3SchemaVersion": "1.0", - "configurationId": "testConfigRule", - "bucket": { - "name": "sourcebucket", - "ownerIdentity": { - "principalId": "EXAMPLE" - }, - "arn": "arn:aws:s3:::mybucket" - }, - "object": { - "key": "Happy%20Face.jpg", - "size": 1024, - "urlDecodedKey": "Happy Face.jpg", - "versionId": "version", - "eTag": "d41d8cd98f00b204e9800998ecf8427e", - "sequencer": "Happy Sequencer" - } - } - } - ] -} diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/scheduled_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/scheduled_event.json deleted file mode 100644 index 3d803cc24..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/scheduled_event.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "account": "123456789012", - "region": "us-east-1", - "detail": {}, - "detail-type": "Scheduled Event", - "source": "aws.events", - "time": "1970-01-01T00:00:00.000Z", - "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", - "resources": [ - "arn:aws:events:us-east-1:123456789012:rule/my-schedule" - ] -} \ No newline at end of file diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/sns_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/sns_event.json deleted file mode 100644 index ff6246355..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/sns_event.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "Records": [ - { - "EventVersion": "1.0", - "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", - "EventSource": "aws:sns", - "Sns": { - "Signature": "EXAMPLE", - "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", - "Type": "Notification", - "TopicArn": "arn:aws:sns:EXAMPLE", - "MessageAttributes": { - "Test": { - "Type": "String", - "Value": "TestString" - }, - "TestBinary": { - "Type": "Binary", - "Value": "TestBinary" - } - }, - "SignatureVersion": "1", - "Timestamp": "2015-06-03T17:43:27.020Z", - "SigningCertUrl": "EXAMPLE", - "Message": "Hello from SNS!", - "UnsubscribeUrl": "EXAMPLE", - "Subject": "TestInvoke" - } - } - ] -} diff --git a/aws-lambda-java-serialization/src/test/resources/event_models/sqs_event.json b/aws-lambda-java-serialization/src/test/resources/event_models/sqs_event.json deleted file mode 100644 index aa69c3509..000000000 --- a/aws-lambda-java-serialization/src/test/resources/event_models/sqs_event.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "Records": [ - { - "messageId" : "MessageID_1", - "receiptHandle" : "MessageReceiptHandle", - "body" : "Message Body", - "md5OfBody" : "fce0ea8dd236ccb3ed9b37dae260836f", - "md5OfMessageAttributes" : "582c92c5c5b6ac403040a4f3ab3115c9", - "eventSourceARN": "arn:aws:sqs:us-west-2:123456789012:SQSQueue", - "eventSource": "aws:sqs", - "awsRegion": "us-west-2", - "attributes" : { - "ApproximateReceiveCount" : "2", - "SentTimestamp" : "1520621625029", - "SenderId" : "AROAIWPX5BD2BHG722MW4:sender", - "ApproximateFirstReceiveTimestamp" : "1520621634884" - }, - "messageAttributes" : { - "Attribute3" : { - "binaryValue" : "MTEwMA==", - "stringListValues" : ["abc", "123"], - "binaryListValues" : ["MA==", "MQ==", "MA=="], - "dataType" : "Binary" - }, - "Attribute2" : { - "stringValue" : "123", - "stringListValues" : [ ], - "binaryListValues" : ["MQ==", "MA=="], - "dataType" : "Number" - }, - "Attribute1" : { - "stringValue" : "AttributeValue1", - "stringListValues" : [ ], - "binaryListValues" : [ ], - "dataType" : "String" - } - } - } - ] -} diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index fa986750e..4b04ecf0a 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -32,7 +32,7 @@ 1.8 1.8 UTF-8 - 5.7.0 + 5.9.2 0.8.7 @@ -40,12 +40,12 @@ com.amazonaws aws-lambda-java-serialization - 1.0.0 + 1.1.5 com.amazonaws aws-lambda-java-events - 3.11.0 + 3.11.4 org.junit.jupiter @@ -65,13 +65,13 @@ org.apache.commons commons-lang3 - 3.11 + 3.12.0 org.assertj assertj-core - 3.18.1 + 3.24.2 test diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java index 68cd37d3d..7228fb90d 100644 --- a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java @@ -65,6 +65,10 @@ public static DynamodbEvent loadDynamoDbEvent(String filename) { return loadEvent(filename, DynamodbEvent.class); } + public static DynamodbEvent.DynamodbStreamRecord loadDynamoDbStreamRecord(String filename) { + return loadEvent(filename, DynamodbEvent.DynamodbStreamRecord.class); + } + public static KafkaEvent loadKafkaEvent(String filename) { return loadEvent(filename, KafkaEvent.class); } 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 8f8fe50a4..3177b9ccc 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 @@ -160,27 +160,43 @@ public void testLoadSNSEvent() { @Test public void testLoadDynamoEvent() { - DynamodbEvent event = EventLoader.loadDynamoDbEvent("dynamo_event.json"); + DynamodbEvent event = EventLoader.loadDynamoDbEvent("ddb/dynamo_event.json"); assertThat(event).isNotNull(); assertThat(event.getRecords()).hasSize(3); + assertDynamoDbStreamRecord(event.getRecords().get(1)); + } + + @Test + public void testLoadDynamoDbStreamRecord() { + assertDynamoDbStreamRecord(EventLoader.loadDynamoDbStreamRecord("ddb/dynamo_ddb_stream_record.json")); + } - DynamodbEvent.DynamodbStreamRecord record = event.getRecords().get(0); + private static void assertDynamoDbStreamRecord(final DynamodbEvent.DynamodbStreamRecord record) { assertThat(record) + .isNotNull() .returns("arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", from(DynamodbEvent.DynamodbStreamRecord::getEventSourceARN)) - .returns("INSERT", from(Record::getEventName)); + .returns("MODIFY", from(Record::getEventName)); StreamRecord streamRecord = record.getDynamodb(); assertThat(streamRecord) - .returns("4421584500000000017450439091", StreamRecord::getSequenceNumber) - .returns(26L, StreamRecord::getSizeBytes) + .returns("4421584500000000017450439092", StreamRecord::getSequenceNumber) + .returns(59L, StreamRecord::getSizeBytes) .returns("NEW_AND_OLD_IMAGES", StreamRecord::getStreamViewType) - .returns(Date.from(ofEpochSecond(1428537600)), StreamRecord::getApproximateCreationDateTime); - - assertThat(streamRecord.getKeys()).contains(entry("Id", new AttributeValue().withN("101"))); - assertThat(streamRecord.getNewImage()).containsAnyOf( - entry("Message", new AttributeValue("New item!")), - entry("Id", new AttributeValue().withN("101")) - ); + .returns(Date.from(ofEpochSecond(1635734407).plusNanos(123456789)), StreamRecord::getApproximateCreationDateTime); + + assertThat(streamRecord.getKeys()) + .isNotNull() + .contains(entry("Id", new AttributeValue().withN("101"))); + assertThat(streamRecord.getNewImage()) + .isNotNull() + .containsAnyOf( + entry("Message", new AttributeValue("This item has changed")), + entry("Id", new AttributeValue().withN("101"))); + assertThat(streamRecord.getOldImage()) + .isNotNull() + .containsAnyOf( + entry("Message", new AttributeValue("New item!")), + entry("Id", new AttributeValue().withN("101"))); } @Test @@ -205,6 +221,15 @@ public void testLoadActiveMQEvent() { assertThat(event.getMessages().get(1).getMessageID()).isEqualTo("ID:b-8bcfa572-428a-4642-879d-eb284b418fc8-1.mq.us-west-2.amazonaws.com-37557-1234520418293-4:1:1:1:1"); } + @Test + public void testLoadActiveMQEventWithProperties() { + ActiveMQEvent event = EventLoader.loadActiveMQEvent("mq_event.json"); + assertThat(event).isNotNull(); + assertThat(event.getMessages()).hasSize(2); + assertThat(event.getMessages().get(0).getProperties().get("testKey")).isEqualTo("testValue"); + assertThat(event.getMessages().get(1).getProperties().get("testKey")).isEqualTo("testValue"); + } + @Test public void testLoadCodeCommitEvent() { CodeCommitEvent event = EventLoader.loadCodeCommitEvent("codecommit_event.json"); diff --git a/aws-lambda-java-tests/src/test/resources/ddb/dynamo_ddb_stream_record.json b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_ddb_stream_record.json new file mode 100644 index 000000000..f5df23ff5 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_ddb_stream_record.json @@ -0,0 +1,35 @@ +{ + "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.635734407123456789E9, + "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" +} diff --git a/aws-lambda-java-tests/src/test/resources/dynamo_event.json b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_event.json similarity index 97% rename from aws-lambda-java-tests/src/test/resources/dynamo_event.json rename to aws-lambda-java-tests/src/test/resources/ddb/dynamo_event.json index f28ce0e6e..2e43ba497 100644 --- a/aws-lambda-java-tests/src/test/resources/dynamo_event.json +++ b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_event.json @@ -59,7 +59,7 @@ "N": "101" } }, - "ApproximateCreationDateTime": 1428537600, + "ApproximateCreationDateTime": 1.635734407123456789E9, "SequenceNumber": "4421584500000000017450439092", "SizeBytes": 59, "StreamViewType": "NEW_AND_OLD_IMAGES" 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 a9a798546..6505a22d4 100644 --- a/aws-lambda-java-tests/src/test/resources/mq_event.json +++ b/aws-lambda-java-tests/src/test/resources/mq_event.json @@ -13,7 +13,10 @@ }, "timestamp": 1598827811958, "brokerInTime": 1598827811958, - "brokerOutTime": 1598827811959 + "brokerOutTime": 1598827811959, + "properties": { + "testKey": "testValue" + } }, { "messageID": "ID:b-8bcfa572-428a-4642-879d-eb284b418fc8-1.mq.us-west-2.amazonaws.com-37557-1234520418293-4:1:1:1:1", @@ -26,7 +29,10 @@ }, "timestamp": 1598827811958, "brokerInTime": 1598827811958, - "brokerOutTime": 1598827811959 + "brokerOutTime": 1598827811959, + "properties": { + "testKey": "testValue" + } } ] } \ No newline at end of file diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index b4a949ba0..2a14336d3 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -41,12 +41,12 @@ com.amazonaws aws-lambda-java-core - 1.2.1 + 1.2.3 com.amazonaws aws-lambda-java-events - 3.11.0 + 3.11.4