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

com.amazonaws.services.sqs.util.SQSQueueUtils Maven / Gradle / Ivy

Go to download

An Amazon SQS client that supports creating lightweight, automatically-deleted temporary queues, for use in common messaging patterns such as Request/Response. See http://aws.amazon.com/sqs.

The newest version!
package com.amazonaws.services.sqs.util;

import static com.amazonaws.services.sqs.executors.ExecutorUtils.acceptIntOn;
import static com.amazonaws.services.sqs.executors.ExecutorUtils.acceptOn;
import static com.amazonaws.services.sqs.executors.ExecutorUtils.applyIntOn;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import software.amazon.awssdk.awscore.AwsRequest;
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
import software.amazon.awssdk.core.ApiName;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.util.DefaultSdkAutoConstructList;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.CreateQueueRequest;
import software.amazon.awssdk.services.sqs.model.GetQueueAttributesRequest;
import software.amazon.awssdk.services.sqs.model.GetQueueAttributesResponse;
import software.amazon.awssdk.services.sqs.model.ListQueueTagsRequest;
import software.amazon.awssdk.services.sqs.model.MessageAttributeValue;
import software.amazon.awssdk.services.sqs.model.QueueAttributeName;
import software.amazon.awssdk.services.sqs.model.QueueDoesNotExistException;
import software.amazon.awssdk.services.sqs.model.SendMessageRequest;

public class SQSQueueUtils {
    private static final Log LOG = LogFactory.getLog(SQSQueueUtils.class);

    public static final String ATTRIBUTE_NAMES_ALL = "All";

    public static final String MESSAGE_ATTRIBUTE_TYPE_STRING = "String";
    public static final String MESSAGE_ATTRIBUTE_TYPE_BOOLEAN = "String.boolean";
    public static final String MESSAGE_ATTRIBUTE_TYPE_LONG = "Number.long";

    public static final int SQS_LIST_QUEUES_LIMIT = 1000;

    public static final Consumer DEFAULT_EXCEPTION_HANDLER = e -> {
        LOG.error("Unexpected exception", e);
    };
    
    private SQSQueueUtils() {
        // Never instantiated
    }

    public static MessageAttributeValue stringMessageAttributeValue(String value) {
        return MessageAttributeValue.builder().dataType(MESSAGE_ATTRIBUTE_TYPE_STRING)
                .stringValue(value).build();
    }

    public static MessageAttributeValue longMessageAttributeValue(long value) {
        return MessageAttributeValue.builder().dataType(MESSAGE_ATTRIBUTE_TYPE_LONG)
                .stringValue(Long.toString(value)).build();
    }

    public static MessageAttributeValue booleanMessageAttributeValue(boolean value) {
        return MessageAttributeValue.builder().dataType(MESSAGE_ATTRIBUTE_TYPE_BOOLEAN)
                .stringValue(Boolean.toString(value)).build();
    }

    public static Optional getStringMessageAttributeValue(Map messageAttributes, String key) {
        return Optional.ofNullable(messageAttributes.get(key))
                .filter(value -> MESSAGE_ATTRIBUTE_TYPE_STRING.equals(value.dataType()))
                .map(MessageAttributeValue::stringValue);
    }

    public static Optional getLongMessageAttributeValue(Map messageAttributes, String key) {
        return Optional.ofNullable(messageAttributes.get(key))
                .filter(value -> MESSAGE_ATTRIBUTE_TYPE_LONG.equals(value.dataType()))
                .map(MessageAttributeValue::stringValue)
                .map(Long::parseLong);
    }

    public static boolean getBooleanMessageAttributeValue(Map messageAttributes, String key) {
        return Optional.ofNullable(messageAttributes.get(key))
                .filter(value -> MESSAGE_ATTRIBUTE_TYPE_BOOLEAN.equals(value.dataType()))
                .map(MessageAttributeValue::stringValue)
                .map(Boolean::parseBoolean).orElse(false);
    }

    // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_CreateQueue.html
    private static final String VALID_QUEUE_NAME_CHARACTERS;
    static {
        StringBuilder builder = new StringBuilder();
        IntStream.rangeClosed('a', 'z').forEach(i -> builder.append((char)i));
        IntStream.rangeClosed('A', 'Z').forEach(i -> builder.append((char)i));
        IntStream.rangeClosed('0', '9').forEach(i -> builder.append((char)i));
        builder.append('-');
        builder.append('_');
        VALID_QUEUE_NAME_CHARACTERS = builder.toString();
    }

    public static boolean isQueueEmpty(SqsClient sqs, String queueUrl) {
        String[] messageCountAttrs = {
                QueueAttributeName.APPROXIMATE_NUMBER_OF_MESSAGES.toString(),
                QueueAttributeName.APPROXIMATE_NUMBER_OF_MESSAGES_DELAYED.toString(),
                QueueAttributeName.APPROXIMATE_NUMBER_OF_MESSAGES_NOT_VISIBLE.toString()
        };

        GetQueueAttributesRequest.Builder getQueueAttributesBuilder = GetQueueAttributesRequest.builder()
                .queueUrl(queueUrl)
                .attributeNamesWithStrings(messageCountAttrs);
        GetQueueAttributesResponse result = sqs.getQueueAttributes(getQueueAttributesBuilder.build());
        Map attrValues = result.attributesAsStrings();
        return Stream.of(messageCountAttrs).allMatch(attr ->
                Long.parseLong(attrValues.get(attr)) == 0);
    }

    public static boolean awaitWithPolling(long period, long timeout, TimeUnit unit, Supplier test) throws InterruptedException {
        long deadlineNanos = System.nanoTime() + unit.toNanos(timeout);
        while (!test.get()) {
            if (deadlineNanos < System.nanoTime()) {
                return false;
            }
            // TODO-RS: Use a ScheduledExecutorService instead of sleeping?
            unit.sleep(period);
        }
        return true;
    }

    public static boolean awaitEmptyQueue(SqsClient sqs, String queueUrl, long timeout, TimeUnit unit) throws InterruptedException {
        // There's no way to be directly notified unfortunately.
        return awaitWithPolling(unit.convert(2, TimeUnit.SECONDS), timeout, unit, () -> isQueueEmpty(sqs, queueUrl));
    }

    public static boolean doesQueueExist(SqsClient sqs, String queueUrl) {
        try {
            ListQueueTagsRequest.Builder builder = ListQueueTagsRequest.builder().queueUrl(queueUrl);
            sqs.listQueueTags(builder.build());
            return true;
        } catch (QueueDoesNotExistException e) {
            return false;
        }
    }
    
    public static boolean awaitQueueCreated(SqsClient sqs, String queueUrl, long timeout, TimeUnit unit) throws InterruptedException {
        return awaitWithPolling(unit.convert(2, TimeUnit.SECONDS), timeout, unit, () -> doesQueueExist(sqs, queueUrl));
    }

    public static boolean awaitQueueDeleted(SqsClient sqs, String queueUrl, long timeout, TimeUnit unit) throws InterruptedException {
        return awaitWithPolling(unit.convert(2, TimeUnit.SECONDS), timeout, unit, () -> !doesQueueExist(sqs, queueUrl));
    }

    public static void forEachQueue(ExecutorService executor, Function> lister, String prefix, int limit, Consumer action) {
        List queueUrls = lister.apply(prefix);
        if (queueUrls.size() >= limit) {
            // Manually work around the 1000 queue limit by forking for each
            // possible next character. Yes this is exponential with a factor of
            // 64, but we only fork when the results are more than 1000.
            VALID_QUEUE_NAME_CHARACTERS
                    .chars()
                    .parallel()
                    .forEach(acceptIntOn(executor, c ->
            forEachQueue(executor, lister, prefix + (char)c, limit, action)));
        } else {
            queueUrls.forEach(acceptOn(executor, action));
        }
    }

    public static List listQueues(ExecutorService executor, Function> lister, String prefix, int limit) {
        List queueUrls = new ArrayList<>(lister.apply(prefix));

        if (queueUrls.size() >= limit) {
            // Manually work around the 1000 queue limit by forking for each
            // possible next character. Yes this is exponential with a factor of
            // 64, but we only fork when the results are more than 1000.
            List> collect = VALID_QUEUE_NAME_CHARACTERS.chars().parallel().mapToObj(applyIntOn(executor, c -> listQueues(executor, lister, prefix + (char) c, limit))).collect(Collectors.toList());
            return VALID_QUEUE_NAME_CHARACTERS
                    .chars()
                    .parallel()
                    .mapToObj(applyIntOn(executor, c -> listQueues(executor, lister, prefix + (char)c, limit)))
                    .map(List::stream)
                    .flatMap(Function.identity())
                    .collect(Collectors.toList());
        } else {
            return queueUrls;
        }
    }

    public static CreateQueueRequest copyWithExtraAttributes(CreateQueueRequest request, Map extraAttrs) {
        Map newAttributes = new HashMap<>(request.attributesAsStrings());
        newAttributes.putAll(extraAttrs);

        // Create a shallow copy that includes the superclass properties.
        return request.toBuilder().copy()
                .queueName(request.queueName())
                .attributesWithStrings(newAttributes).build();
    }

    public static SendMessageRequest copyWithExtraAttributes(SendMessageRequest request, Map extraAttrs) {
        Map newAttributes = new HashMap<>(request.messageAttributes());
        newAttributes.putAll(extraAttrs);

        // Create a shallow copy that includes the superclass properties.
        return request.toBuilder().copy()
                .queueUrl(request.queueUrl())
                .messageBody(request.messageBody())
                .messageAttributes(newAttributes)
                .delaySeconds(request.delaySeconds()).build();
    }
    
    /**
     * this method carefully waits for futures. If waiting throws, it converts the exceptions to the
     * exceptions that SQS clients expect. This is what we use to turn asynchronous calls into
     * synchronous ones.
     */
    // TODO-RS: Copied from QueueBuffer in the buffered asynchronous client
    public static  V waitForFuture(Future future) {
        V toReturn = null;
        try {
            toReturn = future.get();
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
            throw SdkClientException.create(
                    "Thread interrupted while waiting for execution result", ie);
        } catch (ExecutionException ee) {
            // if the cause of the execution exception is an SQS exception, extract it
            // and throw the extracted exception to the clients
            // otherwise, wrap ee in an SQS exception and throw that.
            Throwable cause = ee.getCause();

            if (cause instanceof SdkClientException) {
                throw (SdkClientException) cause;
            }

            throw SdkClientException.create(
                    "Caught an exception while waiting for request to complete...", ee);
        }

        return toReturn;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy