Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Configure Objectmapper used by Java AWS Lambda

I am trying to develop an AWS Lambda function that is triggered by events from SQS.

I am using the spring-cloud-function-adapter-aws (version 1.0.0.RELEASE) and in specifically a SpringBootRequestHandler.

However, the ObjectMapper that is being used is case-sensitive and therefore failing to successful convert the Json coming from SQS.

SQS publishes the following Json and it is the Records field in particular that I'm having the problem with.

    {
  "Records": [
    {
      "body": "Hello from SQS!",
      "receiptHandle": "MessageReceiptHandle",
      "md5OfBody": "7b270e59b47ff90a553787216d55d91d",
      "eventSourceARN": "arn:aws:sqs:eu-west-1:123456789012:MyQueue",
      "eventSource": "aws:sqs",
      "awsRegion": "eu-west-1",
      "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
      "attributes": {
        "ApproximateFirstReceiveTimestamp": "1523232000001",
        "SenderId": "123456789012",
        "ApproximateReceiveCount": "1",
        "SentTimestamp": "1523232000000"
      },
      "messageAttributes": {}
    }
  ]
}

I have tried the suggestions in this question, but to no avail. Configuring ObjectMapper in Spring

In my POJO, I've also added the below annotation but it isn't working either whilst it would outside of Lambda.

@JsonProperty("Records")
private List<SqsRecord> Records;

Any help would be much appreciated.

My Lambda handler is defined as:

public class SqsEventHandler extends SpringBootRequestHandler<SqsEvent, String> {}

The POJO defined as:

public class SqsEvent {

@JsonProperty("Records")
private List<SqsRecord> records;

@Data
public class SqsRecord {
    private String body;
    private String receiptHandle;
    private String md5OfBody;
    private String eventSourceARN;
    private String eventSource;
    private String awsRegion;
    private String messageId;
}

}

I expect the Json from the sample message to be able to be read in by the ObjectMapper, but the field "records" is null.

like image 772
Stephen Gibson Avatar asked Aug 10 '18 20:08

Stephen Gibson


People also ask

Can we use spring boot in AWS Lambda?

You can use the aws-serverless-java-container library to run a Spring Boot application in AWS Lambda. You can use the library within your Lambda handler to load your Spring Boot application and proxy events to it.

Can I use Java in AWS Lambda?

You can run Java code in AWS Lambda. Lambda provides runtimes for Java that run your code to process events. Your code runs in an Amazon Linux environment that includes AWS credentials from an AWS Identity and Access Management (IAM) role that you manage. Lambda supports the following Java runtimes.

How do I write Java code in AWS Lambda?

Sign in to AWS Amazon and then click on Lambda under services. This page will show the lambda functions list, which is already created. Code Entry Type and Function Package: Select “Upload a . ZIP and Jar file” and click on “Upload” button.

Is Java Slow on AWS Lambda?

Once the Java Lambda has warmed up (i.e. fully loaded and compiled), it's barely 500µs slower on average than JavaScript. A note to take into account about the Lambdas: these have been written using the instructions and libraries as directed by AWS.


2 Answers

I got this issue solved in a more simple manner.

Referencing https://docs.aws.amazon.com/lambda/latest/dg/java-handler-io-type-stream.html and in specific

if Lambda's serialization approach does not meet your needs, you can use the byte stream implementation

I am now using the SpringBootStreamHandler directly and I have created an ObjectMapper instance with my required configuration options in my Spring Configuration class as:

@Bean
public ObjectMapper objectMapper() {
    final ObjectMapper mapper = new ObjectMapper();
    mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    return mapper;
}
like image 98
Stephen Gibson Avatar answered Oct 10 '22 15:10

Stephen Gibson


we've got this problem with a lot of AWS services. You must define a new mapper like this :

SQSMixin :

 private static interface SQSEventMixin {
    public static final String ATTRIBUTES = "attributes";
    public static final String AWS_REGION = "awsRegion";
    public static final String BODY = "body";
    public static final String EVENT_SOURCE = "eventSource";
    public static final String EVENT_SOURCE_ARN = "eventSourceARN";
    public static final String MD5_OF_BOBY = "md5OfBody";
    public static final String MD5_OF_MESSAGE_ATTRIBUTES = "md5OfMessageAttributes";
    public static final String MESSAGE_ID = "messageId";
    public static final String RECEIPT_HANDLE = "receiptHandle";

    @JsonProperty(value = "Records")
    public List<?> getRecords();

    static interface MessageMixin {
        @JsonProperty(ATTRIBUTES)
        public String getAttributes();

        @JsonProperty(ATTRIBUTES)
        public void setAttributes(String attributes);

        @JsonProperty(AWS_REGION)
        public String getAwsRegion();

        @JsonProperty(AWS_REGION)
        public void setAwsRegion(String awsRegion);

        @JsonProperty(BODY)
        public Object getBody();

        @JsonProperty(BODY)
        public void setBody(Object body);

        @JsonProperty(EVENT_SOURCE)
        public String getEventSource();

        @JsonProperty(EVENT_SOURCE)
        public void setEventSource(String eventSource);

        @JsonProperty(EVENT_SOURCE_ARN)
        public String getEventSourceArn();

        @JsonProperty(EVENT_SOURCE_ARN)
        public void setEventSourceArn(String eventSourceArn);

        @JsonProperty(MD5_OF_BOBY)
        public String getMd5OfBody();

        @JsonProperty(MD5_OF_BOBY)
        public void setMd5OfBody(String md5OfBody);

        @JsonProperty(MD5_OF_MESSAGE_ATTRIBUTES)
        public String getMd5OfMessageAttributes();

        @JsonProperty(MD5_OF_MESSAGE_ATTRIBUTES)
        public void setMd5OfMessageAttributes(String md5OfMessageAttributes);

        @JsonProperty(MESSAGE_ID)
        public String getMessageId();

        @JsonProperty(MESSAGE_ID)
        public void setMessageId(String messageId);

        @JsonProperty(RECEIPT_HANDLE)
        public String getReceiptHandle();

        @JsonProperty(RECEIPT_HANDLE)
        public void setReceiptHandle(String receiptHandle);
    }
}

A Strategy for record :

private static class UpperCaseRecordsPropertyNamingStrategy extends PropertyNamingStrategy.PropertyNamingStrategyBase {

    private static final long serialVersionUID = 1L;

    @Override
    public String translate(String propertyName) {
        if (propertyName.equals("records")) {
            return "Records";
        }
        return propertyName;
    }
}

Formatter for Date :

private static final DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTime()
        .withZone(new FixedDateTimeZone("GMT", "GMT", 0, 0));

private static class DateTimeMapperModule extends SimpleModule {

    private static final long serialVersionUID = 1L;

    public DateTimeMapperModule() {
        super("DateTimeMapperModule");

        super.addSerializer(DateTime.class, new DateTimeSerializer());
        super.addDeserializer(DateTime.class, new DateTimeDeserializer());
    }
}

private static class DateTimeSerializer extends JsonSerializer<DateTime> {

    @Override
    public void serialize(DateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException {

        gen.writeString(dateTimeFormatter.print(value));
    }
}

private static class DateTimeDeserializer extends JsonDeserializer<DateTime> {

    @Override
    public DateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException {

        return dateTimeFormatter.parseDateTime(parser.getText());
    }
}

And declare your mapper :

    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
    mapper.setPropertyNamingStrategy(new UpperCaseRecordsPropertyNamingStrategy());
    mapper.registerModule(new DateTimeMapperModule());
    mapper.addMixIn(SQSMessage.class, SQSEventMixin.MessageMixin.class);
    SQSEvent request = mapper.convertValue(inputObject, SQSEvent.class);
like image 33
CiliaFred Avatar answered Oct 10 '22 13:10

CiliaFred