spring-cloud-function aws: S3EventNotification can't be deserialized

26 Views Asked by At

Describe the bug I'm really facing up with S3 event lambda function deserialization.

I'm getting:

ClassCastException: class org.springframework.util.LinkedMultiValueMap cannot be cast to class com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification

Detailed trace:

ClassCastException: class org.springframework.util.LinkedMultiValueMap cannot be cast to class com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification (org.springframework.util.LinkedMultiValueMap and com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification are in unnamed module of loader 'app')] with root cause

java.lang.ClassCastException: class org.springframework.util.LinkedMultiValueMap cannot be cast to class com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification (org.springframework.util.LinkedMultiValueMap and com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification are in unnamed module of loader 'app')
        at net.gencat.transversal.espaidoc.presentation.backoffice.apigateway.document.pushed.PushedDocumentLambdaConsumer.apply(PushedDocumentLambdaConsumer.java:17) ~[classes/:na]
        at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.invokeFunctionAndEnrichResultIfNecessary(SimpleFunctionRegistry.java:958) ~[spring-cloud-function-context-4.1.0.jar:4.1.0]
        at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.invokeFunction(SimpleFunctionRegistry.java:904) ~[spring-cloud-function-context-4.1.0.jar:4.1.0]
        at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.doApply(SimpleFunctionRegistry.java:740) ~[spring-cloud-function-context-4.1.0.jar:4.1.0]
        at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.apply(SimpleFunctionRegistry.java:580) ~[spring-cloud-function-context-4.1.0.jar:4.1.0]
        at org.springframework.cloud.function.web.util.FunctionWebRequestProcessingHelper.processRequest(FunctionWebRequestProcessingHelper.java:132) ~[spring-cloud-function-web-4.1.0.jar:4.1.0]
        at org.springframework.cloud.function.web.mvc.FunctionController.form(FunctionController.java:98) ~[spring-cloud-function-web-4.1.0.jar:4.1.0]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:259) ~[spring-web-6.1.4.jar:6.1.4]

Sample Here you can find code repo.

I'm able to reach my function since I've added spring-cloud-starter-function-web dependency.

Then, here my test s3 notification:

{
   "Records":[
      {
         "body":"{\"Records\": [{\"eventVersion\": \"2.1\", \"eventSource\": \"aws:s3\", \"awsRegion\": \"us-east-1\", \"eventTime\": \"2024-02-28T05:30:34.882Z\", \"eventName\": \"ObjectCreated:Put\", \"userIdentity\": {\"principalId\": \"AIDAJDPLRKLG7UEXAMPLE\"}, \"requestParameters\": {\"sourceIPAddress\": \"127.0.0.1\"}, \"responseElements\": {\"x-amz-request-id\": \"b38d4b33\", \"x-amz-id-2\": \"eftixk72aD6Ap51TnqcoF8eFidJG9Z/2\"}, \"s3\": {\"s3SchemaVersion\": \"1.0\", \"configurationId\": \"058322eb\", \"bucket\": {\"name\": \"espaidoc\", \"ownerIdentity\": {\"principalId\": \"A3NL1KOZZKExample\"}, \"arn\": \"arn:aws:s3:::espaidoc\"}, \"object\": {\"key\": \"references/reference1.readme\", \"sequencer\": \"0055AED6DCD90281E5\", \"size\": 1917, \"eTag\": \"e8b5cd0ccb83955551ec584503cc4bd4\"}}}]}",
         "receiptHandle":"ZWQ1YmRjYjctODU0OC00NzMyLWIzNWEtOWMyZjRjMjk4MzdjIGFybjphd3M6c3FzOnVzLWVhc3QtMTowMDAwMDAwMDAwMDA6U3FzUXVldWVDcmVhdGUgMDdlYWZlMTQtOTRjMi00YjA5LTgwNjMtNTU2M2MyOTViMmFjIDE3MDkwOTgyMzUuODg5MDgwOA==",
         "md5OfBody":"dff5137ad0700f8f44763d5eed490032",
         "eventSourceARN":"arn:aws:sqs:us-east-1:000000000000:SqsQueueCreate",
         "eventSource":"aws:sqs",
         "awsRegion":"us-east-1",
         "messageId":"07eafe14-94c2-4b09-8063-5563c295b2ac",
         "attributes":{
            "SenderId":"000000000000",
            "SentTimestamp":"1709098234899",
            "ApproximateReceiveCount":"1",
            "ApproximateFirstReceiveTimestamp":"1709098235889"
         },
         "messageAttributes":{

         }
      }
   ]
}

In order to reach my function via http I'm using:

curl -i localhost:8080/pushedDocumentConsumer --data @s3-event.json

My Consumer is:

@Component
@RequiredArgsConstructor
@Slf4j
public class PushedDocumentLambdaConsumer implements Function<S3EventNotification, String> {

    @Override
    public String apply(S3EventNotification s3EventNotification) {
        log.info("************************************************* RECEIVED TOSTRING()");
        log.info(s3EventNotification.toString());
        log.info("*************************************************");

        log.info("processing event");
        if (s3EventNotification != null && s3EventNotification.getRecords() != null) {
            log.info("Number of records: {}", s3EventNotification.getRecords().size());
            for (S3EventNotificationRecord s3EventNotificationRecord : s3EventNotification.getRecords()) {
                log.info("event name: {}", s3EventNotificationRecord.getEventName());
                log.info("event source: {}", s3EventNotificationRecord.getEventSource());

                S3Entity s3Entity = s3EventNotificationRecord.getS3();
                if (null != s3Entity) {
                    log.info("s3Entity bucket name: {}", s3Entity.getBucket().getName());
                    log.info("s3Entity object name: {}", s3Entity.getObject().getKey());
                }
            }
        }

        log.info("======================");
        return "Ok";
    }

My pom.xml is:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>net.gencat.transversal.espaidoc</groupId>
    <artifactId>backoffice-apigateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>backoffice-apigateway</name>
    <description>espaidoc backoffice apigateway</description>

    <properties>
        <maven.compiler.target>17</maven.compiler.target>
    <maven.compiler.source>17</maven.compiler.source>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <!-- <spring-boot-dependencies.version>3.2.2</spring-boot-dependencies.version> -->
        <spring-cloud-dependencies.version>2023.0.0</spring-cloud-dependencies.version>
    <spring-cloud-aws.version>3.1.0</spring-cloud-aws.version>
        <spring-cloud-function.version>4.1.0</spring-cloud-function.version>
        <thin-wrapper-layout.version>1.0.31.RELEASE</thin-wrapper-layout.version>

        <aws-lambda-events.version>3.11.4</aws-lambda-events.version>
        <aws-lambda-java-core.version>1.2.3</aws-lambda-java-core.version>
        <aws-lambda-java-serialization.version>1.1.5</aws-lambda-java-serialization.version>

        <jackson.version>2.16.1</jackson.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-function-dependencies</artifactId>
                <version>${spring-cloud-function.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.amazonaws</groupId>
                <artifactId>aws-java-sdk-bom</artifactId>
                <version>1.12.660</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-function-adapter-aws</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-function-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-events</artifactId>
            <version>${aws-lambda-events.version}</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>${aws-lambda-java-core.version}</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-serialization</artifactId>
            <version>${aws-lambda-java-serialization.version}</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-s3</artifactId>
        </dependency>
        <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-java-sdk-sqs</artifactId>
    </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-deploy-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <!-- <plugin> -->
            <!--    <groupId>org.springframework.boot</groupId> -->
            <!--    <artifactId>spring-boot-maven-plugin</artifactId> -->
            <!--    <dependencies> -->
            <!--        <dependency> -->
            <!--            <groupId>org.springframework.boot.experimental</groupId> -->
            <!--            <artifactId>spring-boot-thin-layout</artifactId> -->
            <!--            <version>${thin-wrapper-layout.version}</version> -->
            <!--        </dependency> -->
            <!--    </dependencies> -->
            <!-- </plugin> -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                    <shadedArtifactAttached>true</shadedArtifactAttached>
                    <shadedClassifierName>aws</shadedClassifierName>

                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

0

There are 0 best solutions below