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

com.jashmore.sqs.util.LocalSqsAsyncClientImpl Maven / Gradle / Ivy

package com.jashmore.sqs.util;

import static com.jashmore.sqs.util.SqsQueuesConfig.DEFAULT_SQS_SERVER_URL;
import static com.jashmore.sqs.util.SqsQueuesConfig.QueueConfig.DEFAULT_MAX_RECEIVE_COUNT;
import static java.util.stream.Collectors.toList;
import static software.amazon.awssdk.services.sqs.model.QueueAttributeName.APPROXIMATE_NUMBER_OF_MESSAGES;
import static software.amazon.awssdk.services.sqs.model.QueueAttributeName.APPROXIMATE_NUMBER_OF_MESSAGES_NOT_VISIBLE;

import com.jashmore.sqs.util.concurrent.CompletableFutureUtils;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import lombok.experimental.Delegate;
import lombok.extern.slf4j.Slf4j;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsAsyncClient;
import software.amazon.awssdk.services.sqs.SqsAsyncClientBuilder;
import software.amazon.awssdk.services.sqs.model.CreateQueueRequest;
import software.amazon.awssdk.services.sqs.model.CreateQueueResponse;
import software.amazon.awssdk.services.sqs.model.GetQueueAttributesResponse;
import software.amazon.awssdk.services.sqs.model.GetQueueUrlResponse;
import software.amazon.awssdk.services.sqs.model.ListQueuesResponse;
import software.amazon.awssdk.services.sqs.model.PurgeQueueResponse;
import software.amazon.awssdk.services.sqs.model.QueueAttributeName;
import software.amazon.awssdk.services.sqs.model.SendMessageRequest;
import software.amazon.awssdk.services.sqs.model.SendMessageResponse;
import software.amazon.awssdk.utils.SdkAutoCloseable;

/**
 * Helper implementation of the {@link SqsAsyncClient} that can be used to connect to a locally running SQS server.
 *
 * 

The implementation is kept to an absolute minimum and it will only connect to the server and set up any queues that should be set up on initial * connection. For any further configuration options look at creating your own implementation. */ @Slf4j public class LocalSqsAsyncClientImpl implements LocalSqsAsyncClient { @Delegate(excludes = SdkAutoCloseable.class) private final SqsAsyncClient delegate; public LocalSqsAsyncClientImpl(final SqsQueuesConfig sqsQueuesConfig) { this(sqsQueuesConfig, builder -> {}); } public LocalSqsAsyncClientImpl(final SqsQueuesConfig sqsQueuesConfig, final Consumer clientBuilderConsumer) { final String serverUrl = Optional.ofNullable(sqsQueuesConfig.getSqsServerUrl()).orElse(DEFAULT_SQS_SERVER_URL); log.info("Connecting to local SQS service at {}", serverUrl); final URI serverUri; try { serverUri = new URI(serverUrl); } catch (URISyntaxException uriSyntaxException) { throw new RuntimeException("Invalid Server URL for SQS Server", uriSyntaxException); } final SqsAsyncClientBuilder clientBuilder = SqsAsyncClient .builder() .endpointOverride(serverUri) .region(Region.of("localstack")) .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("accessKeyId", "secretAccessKey"))); clientBuilderConsumer.accept(clientBuilder); delegate = clientBuilder.build(); if (!sqsQueuesConfig.getQueues().isEmpty()) { try { buildQueues(delegate, sqsQueuesConfig).get(); } catch (InterruptedException interruptedException) { Thread.currentThread().interrupt(); throw new RuntimeException("Interrupted waiting for queues to be built", interruptedException); } catch (ExecutionException executionException) { throw new RuntimeException("Error building initial queues", executionException.getCause()); } } } @SuppressWarnings("unused") public LocalSqsAsyncClientImpl(final SqsAsyncClient delegate) { this.delegate = delegate; } @Override public CompletableFuture sendMessage(final String queueName, final String messageBody) { return sendMessage(queueName, builder -> builder.messageBody(messageBody)); } @Override public CompletableFuture sendMessage(final String queueName, final SendMessageRequest sendMessageRequest) { return getQueueUrl(builder -> builder.queueName(queueName)) .thenApply(GetQueueUrlResponse::queueUrl) .thenCompose(queueUrl -> sendMessage(sendMessageRequest.toBuilder().queueUrl(queueUrl).build())); } @Override public CompletableFuture sendMessage( final String queueName, final Consumer sendMessageRequestBuilderConsumer ) { return getQueueUrl(builder -> builder.queueName(queueName)) .thenApply(GetQueueUrlResponse::queueUrl) .thenCompose( queueUrl -> sendMessage( builder -> { sendMessageRequestBuilderConsumer.accept(builder); builder.queueUrl(queueUrl); } ) ); } @Override public CompletableFuture createRandomQueue() { final String queueName = UUID.randomUUID().toString().replace("-", ""); log.info("Creating queue with name: {}", queueName); return createQueue(requestBuilder -> requestBuilder.queueName(queueName).build()) .thenApply( createQueueResponse -> CreateRandomQueueResponse.builder().response(createQueueResponse).queueName(queueName).build() ); } @Override public CompletableFuture createRandomFifoQueue() { return createRandomFifoQueue(builder -> {}); } @Override public CompletableFuture createRandomFifoQueue( Consumer sendMessageRequestBuilderConsumer ) { final String queueName = UUID.randomUUID().toString().replace("-", "") + ".fifo"; log.info("Creating FIFO queue with name: {}", queueName); return createQueue( requestBuilder -> { sendMessageRequestBuilderConsumer.accept(requestBuilder); final Map originalAttributes = requestBuilder.build().attributes(); final Map actualAttributes = new HashMap<>(originalAttributes); actualAttributes.putIfAbsent(QueueAttributeName.FIFO_QUEUE, String.valueOf(true)); actualAttributes.putIfAbsent(QueueAttributeName.CONTENT_BASED_DEDUPLICATION, String.valueOf(false)); requestBuilder.queueName(queueName).attributes(actualAttributes).build(); } ) .thenApply( createQueueResponse -> CreateRandomQueueResponse.builder().response(createQueueResponse).queueName(queueName).build() ); } @Override public CompletableFuture> purgeAllQueues() { return delegate .listQueues() .thenApply(ListQueuesResponse::queueUrls) .thenCompose( queueUrls -> CompletableFutureUtils.allOf( queueUrls.stream().map(url -> purgeQueue(builder -> builder.queueUrl(url))).collect(toList()) ) ); } @Override public CompletableFuture getApproximateMessages(final String queueName) { return getQueueUrl(builder -> builder.queueName(queueName)) .thenApply(GetQueueUrlResponse::queueUrl) .thenCompose( queueUrl -> getQueueAttributes( builder -> builder .queueUrl(queueUrl) .attributeNames(APPROXIMATE_NUMBER_OF_MESSAGES, APPROXIMATE_NUMBER_OF_MESSAGES_NOT_VISIBLE) ) ) .thenApply(GetQueueAttributesResponse::attributes) .thenApply( attributes -> Integer.parseInt(attributes.get(APPROXIMATE_NUMBER_OF_MESSAGES)) + Integer.parseInt(attributes.get(APPROXIMATE_NUMBER_OF_MESSAGES_NOT_VISIBLE)) ); } /** * Build all of the queues that are required for this local client once the bean has been created. */ private static CompletableFuture> buildQueues(SqsAsyncClient delegate, SqsQueuesConfig sqsQueuesConfig) { final List> queueFutures = sqsQueuesConfig .getQueues() .stream() .map(queueConfig -> buildQueue(delegate, queueConfig)) .collect(toList()); return CompletableFutureUtils.allOf(queueFutures); } private static CompletableFuture buildQueue(SqsAsyncClient delegate, SqsQueuesConfig.QueueConfig queueConfig) { log.debug("Creating local queue: {}", queueConfig.getQueueName()); final Map attributes = new HashMap<>(); if (queueConfig.getVisibilityTimeout() != null) { attributes.put(QueueAttributeName.VISIBILITY_TIMEOUT, String.valueOf(queueConfig.getVisibilityTimeout())); } final CompletableFuture createDeadLetterQueueFuture; if (queueConfig.getMaxReceiveCount() != null || queueConfig.getDeadLetterQueueName() != null) { final String deadLetterQueueName = Optional .ofNullable(queueConfig.getDeadLetterQueueName()) .orElse(queueConfig.getQueueName() + "-dlq"); final int maxReceiveCount = Optional.ofNullable(queueConfig.getMaxReceiveCount()).orElse(DEFAULT_MAX_RECEIVE_COUNT); createDeadLetterQueueFuture = createDeadLetterQueue(delegate, queueConfig, deadLetterQueueName) .thenAccept( deadLetterQueueArn -> attributes.put( QueueAttributeName.REDRIVE_POLICY, String.format( "{\"deadLetterTargetArn\":\"%s\",\"maxReceiveCount\":\"%d\"}", deadLetterQueueArn, maxReceiveCount ) ) ); } else { createDeadLetterQueueFuture = CompletableFuture.completedFuture(null); } if (queueConfig.isFifoQueue()) { if (!queueConfig.getQueueName().endsWith(".fifo")) { throw new IllegalArgumentException("Queue name must end in .fifo for FIFO queues"); } attributes.put(QueueAttributeName.FIFO_QUEUE, String.valueOf(true)); attributes.put(QueueAttributeName.CONTENT_BASED_DEDUPLICATION, String.valueOf(false)); } return createDeadLetterQueueFuture.thenCompose( ignored -> delegate.createQueue(builder -> builder.queueName(queueConfig.getQueueName()).attributes(attributes)) ); } /** * Create a Dead Letter Queue that should be used for the queue with the provided name. * * @param queueName the name of the queue that this dead letter queue is for * @return the future with the queue ARN of the dead letter queue created */ private static CompletableFuture createDeadLetterQueue( SqsAsyncClient delegate, SqsQueuesConfig.QueueConfig queueConfig, final String queueName ) { log.debug("Creating dead letter queue: {}", queueName); return delegate .createQueue( ( builder -> { if (queueConfig.isFifoQueue()) { final Map attributes = new HashMap<>(); attributes.put(QueueAttributeName.FIFO_QUEUE, "true"); attributes.put(QueueAttributeName.CONTENT_BASED_DEDUPLICATION, "false"); builder.attributes(attributes); } builder.queueName(queueName); } ) ) .thenCompose( createQueueResponse -> delegate.getQueueAttributes( builder -> builder.queueUrl(createQueueResponse.queueUrl()).attributeNames(QueueAttributeName.QUEUE_ARN) ) ) .thenApply(queueAttributes -> queueAttributes.attributes().get(QueueAttributeName.QUEUE_ARN)); } @Override public void close() {} }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy