All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.lambda.powertools.sqs.internal.SqsLargeMessageAspect Maven / Gradle / Ivy

package software.amazon.lambda.powertools.sqs.internal;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.utils.IoUtils;
import software.amazon.lambda.powertools.sqs.SqsLargeMessage;
import software.amazon.payloadoffloading.PayloadS3Pointer;

import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage;
import static java.lang.String.format;
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isHandlerMethod;
import static software.amazon.lambda.powertools.sqs.SqsUtils.s3Client;

@Aspect
public class SqsLargeMessageAspect {

    private static final Logger LOG = LoggerFactory.getLogger(SqsLargeMessageAspect.class);

    @SuppressWarnings({"EmptyMethod"})
    @Pointcut("@annotation(sqsLargeMessage)")
    public void callAt(SqsLargeMessage sqsLargeMessage) {
    }

    @Around(value = "callAt(sqsLargeMessage) && execution(@SqsLargeMessage * *.*(..))", argNames = "pjp,sqsLargeMessage")
    public Object around(ProceedingJoinPoint pjp,
                         SqsLargeMessage sqsLargeMessage) throws Throwable {
        Object[] proceedArgs = pjp.getArgs();

        if (isHandlerMethod(pjp)
                && placedOnSqsEventRequestHandler(pjp)) {
            List pointersToDelete = rewriteMessages((SQSEvent) proceedArgs[0]);

            Object proceed = pjp.proceed(proceedArgs);

            if (sqsLargeMessage.deletePayloads()) {
                pointersToDelete.forEach(SqsLargeMessageAspect::deleteMessage);
            }
            return proceed;
        }

        return pjp.proceed(proceedArgs);
    }

    private List rewriteMessages(SQSEvent sqsEvent) {
        List records = sqsEvent.getRecords();
        return processMessages(records);
    }

    public static List processMessages(final List records) {
        List s3Pointers = new ArrayList<>();
        for (SQSMessage sqsMessage : records) {
            if (isBodyLargeMessagePointer(sqsMessage.getBody())) {

                PayloadS3Pointer s3Pointer = PayloadS3Pointer.fromJson(sqsMessage.getBody())
                        .orElseThrow(() -> new FailedProcessingLargePayloadException(format("Failed processing SQS body to extract S3 details. [ %s ].", sqsMessage.getBody())));

                ResponseInputStream s3Object = callS3Gracefully(s3Pointer, pointer -> {
                    ResponseInputStream response = s3Client().getObject(GetObjectRequest.builder()
                            .bucket(pointer.getS3BucketName())
                            .key(pointer.getS3Key())
                            .build());

                    LOG.debug("Object downloaded with key: " + s3Pointer.getS3Key());
                    return response;
                });

                sqsMessage.setBody(readStringFromS3Object(s3Object, s3Pointer));
                s3Pointers.add(s3Pointer);
            }
        }

        return s3Pointers;
    }

    private static boolean isBodyLargeMessagePointer(String record) {
        return record.startsWith("[\"software.amazon.payloadoffloading.PayloadS3Pointer\"");
    }

    private static String readStringFromS3Object(ResponseInputStream response,
                                                 PayloadS3Pointer s3Pointer) {
        try (ResponseInputStream content = response) {
            return IoUtils.toUtf8String(content);
        } catch (IOException e) {
            LOG.error("Error converting S3 object to String", e);
            throw new FailedProcessingLargePayloadException(format("Failed processing S3 record with [Bucket Name: %s Bucket Key: %s]", s3Pointer.getS3BucketName(), s3Pointer.getS3Key()), e);
        }
    }

    public static void deleteMessage(PayloadS3Pointer s3Pointer) {
        callS3Gracefully(s3Pointer, pointer -> {
            s3Client().deleteObject(DeleteObjectRequest.builder()
                    .bucket(pointer.getS3BucketName())
                    .key(pointer.getS3Key())
                    .build());
            LOG.info("Message deleted from S3: " + s3Pointer.toJson());
            return null;
        });
    }

    private static  R callS3Gracefully(final PayloadS3Pointer pointer,
                                   final Function function) {
        try {
            return function.apply(pointer);
        } catch (S3Exception e) {
            LOG.error("A service exception", e);
            throw new FailedProcessingLargePayloadException(format("Failed processing S3 record with [Bucket Name: %s Bucket Key: %s]", pointer.getS3BucketName(), pointer.getS3Key()), e);
        } catch (SdkClientException e) {
            LOG.error("Some sort of client exception", e);
            throw new FailedProcessingLargePayloadException(format("Failed processing S3 record with [Bucket Name: %s Bucket Key: %s]", pointer.getS3BucketName(), pointer.getS3Key()), e);
        }
    }

    public static boolean placedOnSqsEventRequestHandler(ProceedingJoinPoint pjp) {
        return pjp.getArgs().length == 2
                && pjp.getArgs()[0] instanceof SQSEvent
                && pjp.getArgs()[1] instanceof Context;
    }

    public static class FailedProcessingLargePayloadException extends RuntimeException {
        public FailedProcessingLargePayloadException(String message, Throwable cause) {
            super(message, cause);
        }

        public FailedProcessingLargePayloadException(String message) {
            super(message);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy