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

com.amazon.sqs.javamessaging.AmazonSQSExtendedClient Maven / Gradle / Ivy

Go to download

An extension to the Amazon SQS client that enables sending and receiving messages up to 2GB via Amazon S3.

There is a newer version: 2.1.1
Show newest version
/*
 * Copyright 2010-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.amazon.sqs.javamessaging;

import java.lang.UnsupportedOperationException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

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.awscore.exception.AwsServiceException;
import software.amazon.awssdk.core.ApiName;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.exception.SdkException;
import software.amazon.awssdk.core.util.VersionInfo;
import software.amazon.awssdk.services.sqs.SqsClient;

import software.amazon.awssdk.services.sqs.model.BatchEntryIdsNotDistinctException;
import software.amazon.awssdk.services.sqs.model.BatchRequestTooLongException;
import software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityBatchRequest;
import software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityBatchRequestEntry;
import software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityBatchResponse;
import software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityRequest;
import software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityResponse;
import software.amazon.awssdk.services.sqs.model.DeleteMessageBatchRequest;
import software.amazon.awssdk.services.sqs.model.DeleteMessageBatchRequestEntry;
import software.amazon.awssdk.services.sqs.model.DeleteMessageBatchResponse;
import software.amazon.awssdk.services.sqs.model.DeleteMessageRequest;
import software.amazon.awssdk.services.sqs.model.DeleteMessageResponse;
import software.amazon.awssdk.services.sqs.model.EmptyBatchRequestException;
import software.amazon.awssdk.services.sqs.model.InvalidBatchEntryIdException;
import software.amazon.awssdk.services.sqs.model.InvalidIdFormatException;
import software.amazon.awssdk.services.sqs.model.InvalidMessageContentsException;
import software.amazon.awssdk.services.sqs.model.Message;
import software.amazon.awssdk.services.sqs.model.MessageAttributeValue;
import software.amazon.awssdk.services.sqs.model.MessageNotInflightException;
import software.amazon.awssdk.services.sqs.model.OverLimitException;
import software.amazon.awssdk.services.sqs.model.PurgeQueueInProgressException;
import software.amazon.awssdk.services.sqs.model.PurgeQueueRequest;
import software.amazon.awssdk.services.sqs.model.PurgeQueueResponse;
import software.amazon.awssdk.services.sqs.model.QueueDoesNotExistException;
import software.amazon.awssdk.services.sqs.model.ReceiptHandleIsInvalidException;
import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest;
import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse;
import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequest;
import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequestEntry;
import software.amazon.awssdk.services.sqs.model.SendMessageBatchResponse;
import software.amazon.awssdk.services.sqs.model.SendMessageRequest;
import software.amazon.awssdk.services.sqs.model.SendMessageResponse;
import software.amazon.awssdk.services.sqs.model.SqsException;
import software.amazon.awssdk.services.sqs.model.TooManyEntriesInBatchRequestException;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.payloadoffloading.PayloadS3Pointer;
import software.amazon.payloadoffloading.PayloadStore;
import software.amazon.payloadoffloading.S3BackedPayloadStore;
import software.amazon.payloadoffloading.S3Dao;
import software.amazon.payloadoffloading.Util;


/**
 * Amazon SQS Extended Client extends the functionality of Amazon SQS client.
 * All service calls made using this client are blocking, and will not return
 * until the service call completes.
 *
 * 

* The Amazon SQS extended client enables sending and receiving large messages * via Amazon S3. You can use this library to: *

* *
    *
  • Specify whether messages are always stored in Amazon S3 or only when a * message size exceeds 256 KB.
  • *
  • Send a message that references a single message object stored in an * Amazon S3 bucket.
  • *
  • Get the corresponding message object from an Amazon S3 bucket.
  • *
  • Delete the corresponding message object from an Amazon S3 bucket.
  • *
*/ public class AmazonSQSExtendedClient extends AmazonSQSExtendedClientBase implements SqsClient { static final String USER_AGENT_NAME = AmazonSQSExtendedClient.class.getSimpleName(); static final String USER_AGENT_VERSION = VersionInfo.SDK_VERSION; private static final Log LOG = LogFactory.getLog(AmazonSQSExtendedClient.class); static final String LEGACY_RESERVED_ATTRIBUTE_NAME = "SQSLargePayloadSize"; static final List RESERVED_ATTRIBUTE_NAMES = Arrays.asList(LEGACY_RESERVED_ATTRIBUTE_NAME, SQSExtendedClientConstants.RESERVED_ATTRIBUTE_NAME); private ExtendedClientConfiguration clientConfiguration; private PayloadStore payloadStore; /** * Constructs a new Amazon SQS extended client to invoke service methods on * Amazon SQS with extended functionality using the specified Amazon SQS * client object. * *

* All service calls made using this new client object are blocking, and * will not return until the service call completes. * * @param sqsClient * The Amazon SQS client to use to connect to Amazon SQS. */ public AmazonSQSExtendedClient(SqsClient sqsClient) { this(sqsClient, new ExtendedClientConfiguration()); } /** * Constructs a new Amazon SQS extended client to invoke service methods on * Amazon SQS with extended functionality using the specified Amazon SQS * client object. * *

* All service calls made using this new client object are blocking, and * will not return until the service call completes. * * @param sqsClient * The Amazon SQS client to use to connect to Amazon SQS. * @param extendedClientConfig * The extended client configuration options controlling the * functionality of this client. */ public AmazonSQSExtendedClient(SqsClient sqsClient, ExtendedClientConfiguration extendedClientConfig) { super(sqsClient); this.clientConfiguration = new ExtendedClientConfiguration(extendedClientConfig); S3Dao s3Dao = new S3Dao(clientConfiguration.getS3Client(), clientConfiguration.getServerSideEncryptionStrategy(), clientConfiguration.getObjectCannedACL()); this.payloadStore = new S3BackedPayloadStore(s3Dao, clientConfiguration.getS3BucketName()); } /** *

* Delivers a message to the specified queue. *

* *

* A message can include only XML, JSON, and unformatted text. The following Unicode characters are allowed: *

*

* #x9 | #xA | #xD | #x20 to #xD7FF | * #xE000 to #xFFFD | #x10000 to #x10FFFF *

*

* Any characters not included in this list will be rejected. For more information, see the W3C specification for characters. *

*
* * @param sendMessageRequest * @return Result of the SendMessage operation returned by the service. * @throws InvalidMessageContentsException * The message contains characters outside the allowed set. * @throws UnsupportedOperationException * Error code 400. Unsupported operation. * @throws SdkException * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for * catch all scenarios. * @throws SdkClientException * If any client side error occurs such as an IO related failure, failure to get credentials, etc. * @throws SqsException * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. * @sample SqsClient.SendMessage * @see AWS API * Documentation */ public SendMessageResponse sendMessage(SendMessageRequest sendMessageRequest) { //TODO: Clone request since it's modified in this method and will cause issues if the client reuses request object. if (sendMessageRequest == null) { String errorMessage = "sendMessageRequest cannot be null."; LOG.error(errorMessage); throw SdkClientException.create(errorMessage); } SendMessageRequest.Builder sendMessageRequestBuilder = sendMessageRequest.toBuilder(); sendMessageRequest = appendUserAgent(sendMessageRequestBuilder).build(); if (!clientConfiguration.isPayloadSupportEnabled()) { return super.sendMessage(sendMessageRequest); } if (StringUtils.isEmpty(sendMessageRequest.messageBody())) { String errorMessage = "messageBody cannot be null or empty."; LOG.error(errorMessage); throw SdkClientException.create(errorMessage); } //Check message attributes for ExtendedClient related constraints checkMessageAttributes(sendMessageRequest.messageAttributes()); if (clientConfiguration.isAlwaysThroughS3() || isLarge(sendMessageRequest)) { sendMessageRequest = storeMessageInS3(sendMessageRequest); } return super.sendMessage(sendMessageRequest); } /** *

* Retrieves one or more messages (up to 10), from the specified queue. Using the WaitTimeSeconds * parameter enables long-poll support. For more information, see Amazon * SQS Long Polling in the Amazon Simple Queue Service Developer Guide. *

*

* Short poll is the default behavior where a weighted random set of machines is sampled on a * ReceiveMessage call. Thus, only the messages on the sampled machines are returned. If the number of * messages in the queue is small (fewer than 1,000), you most likely get fewer messages than you requested per * ReceiveMessage call. If the number of messages in the queue is extremely small, you might not * receive any messages in a particular ReceiveMessage response. If this happens, repeat the request. *

*

* For each message returned, the response includes the following: *

*
    *
  • *

    * The message body. *

    *
  • *
  • *

    * An MD5 digest of the message body. For information about MD5, see RFC1321. *

    *
  • *
  • *

    * The MessageId you received when you sent the message to the queue. *

    *
  • *
  • *

    * The receipt handle. *

    *
  • *
  • *

    * The message attributes. *

    *
  • *
  • *

    * An MD5 digest of the message attributes. *

    *
  • *
*

* The receipt handle is the identifier you must provide when deleting the message. For more information, see Queue and Message Identifiers in the Amazon Simple Queue Service Developer Guide. *

*

* You can provide the VisibilityTimeout parameter in your request. The parameter is applied to the * messages that Amazon SQS returns in the response. If you don't include the parameter, the overall visibility * timeout for the queue is used for the returned messages. For more information, see Visibility Timeout in the Amazon Simple Queue Service Developer Guide. *

*

* A message that isn't deleted or a message whose visibility isn't extended before the visibility timeout expires * counts as a failed receive. Depending on the configuration of the queue, the message might be sent to the * dead-letter queue. *

* *

* In the future, new attributes might be added. If you write code that calls this action, we recommend that you * structure your code so that it can handle new attributes gracefully. *

*
* * @param receiveMessageRequest * @return Result of the ReceiveMessage operation returned by the service. * @throws OverLimitException * The specified action violates a limit. For example, ReceiveMessage returns this error if the * maximum number of inflight messages is reached and AddPermission returns this error if the * maximum number of permissions for the queue is reached. * @throws SdkException * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for * catch all scenarios. * @throws SdkClientException * If any client side error occurs such as an IO related failure, failure to get credentials, etc. * @throws SqsException * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. * @sample SqsClient.ReceiveMessage * @see AWS API * Documentation */ public ReceiveMessageResponse receiveMessage(ReceiveMessageRequest receiveMessageRequest) { //TODO: Clone request since it's modified in this method and will cause issues if the client reuses request object. if (receiveMessageRequest == null) { String errorMessage = "receiveMessageRequest cannot be null."; LOG.error(errorMessage); throw SdkClientException.create(errorMessage); } ReceiveMessageRequest.Builder receiveMessageRequestBuilder = receiveMessageRequest.toBuilder(); appendUserAgent(receiveMessageRequestBuilder); if (!clientConfiguration.isPayloadSupportEnabled()) { return super.receiveMessage(receiveMessageRequestBuilder.build()); } //Remove before adding to avoid any duplicates List messageAttributeNames = new ArrayList<>(receiveMessageRequest.messageAttributeNames()); messageAttributeNames.removeAll(RESERVED_ATTRIBUTE_NAMES); messageAttributeNames.addAll(RESERVED_ATTRIBUTE_NAMES); receiveMessageRequestBuilder.messageAttributeNames(messageAttributeNames); receiveMessageRequest = receiveMessageRequestBuilder.build(); ReceiveMessageResponse receiveMessageResponse = super.receiveMessage(receiveMessageRequest); ReceiveMessageResponse.Builder receiveMessageResponseBuilder = receiveMessageResponse.toBuilder(); List messages = receiveMessageResponse.messages(); List modifiedMessages = new ArrayList<>(messages.size()); for (Message message : messages) { Message.Builder messageBuilder = message.toBuilder(); // for each received message check if they are stored in S3. Optional largePayloadAttributeName = getReservedAttributeNameIfPresent(message.messageAttributes()); if (largePayloadAttributeName.isPresent()) { String largeMessagePointer = message.body(); largeMessagePointer = largeMessagePointer.replace("com.amazon.sqs.javamessaging.MessageS3Pointer", "software.amazon.payloadoffloading.PayloadS3Pointer"); messageBuilder.body(payloadStore.getOriginalPayload(largeMessagePointer)); // remove the additional attribute before returning the message // to user. Map messageAttributes = new HashMap<>(message.messageAttributes()); messageAttributes.keySet().removeAll(RESERVED_ATTRIBUTE_NAMES); messageBuilder.messageAttributes(messageAttributes); // Embed s3 object pointer in the receipt handle. String modifiedReceiptHandle = embedS3PointerInReceiptHandle( message.receiptHandle(), largeMessagePointer); messageBuilder.receiptHandle(modifiedReceiptHandle); } modifiedMessages.add(messageBuilder.build()); } receiveMessageResponseBuilder.messages(modifiedMessages); return receiveMessageResponseBuilder.build(); } /** *

* Deletes the specified message from the specified queue. To select the message to delete, use the * ReceiptHandle of the message (not the MessageId which you receive when you send * the message). Amazon SQS can delete a message from a queue even if a visibility timeout setting causes the * message to be locked by another consumer. Amazon SQS automatically deletes messages left in a queue longer than * the retention period configured for the queue. *

* *

* The ReceiptHandle is associated with a specific instance of receiving a message. If you * receive a message more than once, the ReceiptHandle is different each time you receive a message. * When you use the DeleteMessage action, you must provide the most recently received * ReceiptHandle for the message (otherwise, the request succeeds, but the message might not be * deleted). *

*

* For standard queues, it is possible to receive a message even after you delete it. This might happen on rare * occasions if one of the servers which stores a copy of the message is unavailable when you send the request to * delete the message. The copy remains on the server and might be returned to you during a subsequent receive * request. You should ensure that your application is idempotent, so that receiving a message more than once does * not cause issues. *

*
* * @param deleteMessageRequest * @return Result of the DeleteMessage operation returned by the service. * @throws InvalidIdFormatException * The specified receipt handle isn't valid for the current version. * @throws ReceiptHandleIsInvalidException * The specified receipt handle isn't valid. * @throws SdkException * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for * catch all scenarios. * @throws SdkClientException * If any client side error occurs such as an IO related failure, failure to get credentials, etc. * @throws SqsException * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. * @sample SqsClient.DeleteMessage * @see AWS API * Documentation */ public DeleteMessageResponse deleteMessage(DeleteMessageRequest deleteMessageRequest) { if (deleteMessageRequest == null) { String errorMessage = "deleteMessageRequest cannot be null."; LOG.error(errorMessage); throw SdkClientException.create(errorMessage); } DeleteMessageRequest.Builder deleteMessageRequestBuilder = deleteMessageRequest.toBuilder(); appendUserAgent(deleteMessageRequestBuilder); if (!clientConfiguration.isPayloadSupportEnabled()) { return super.deleteMessage(deleteMessageRequestBuilder.build()); } String receiptHandle = deleteMessageRequest.receiptHandle(); String origReceiptHandle = receiptHandle; // Update original receipt handle if needed if (isS3ReceiptHandle(receiptHandle)) { origReceiptHandle = getOrigReceiptHandle(receiptHandle); // Delete pay load from S3 if needed if (clientConfiguration.doesCleanupS3Payload()) { String messagePointer = getMessagePointerFromModifiedReceiptHandle(receiptHandle); payloadStore.deleteOriginalPayload(messagePointer); } } deleteMessageRequestBuilder.receiptHandle(origReceiptHandle); return super.deleteMessage(deleteMessageRequestBuilder.build()); } /** *

* Changes the visibility timeout of a specified message in a queue to a new value. The default visibility timeout * for a message is 30 seconds. The minimum is 0 seconds. The maximum is 12 hours. For more information, see * Visibility Timeout in the Amazon Simple Queue Service Developer Guide. *

*

* For example, you have a message with a visibility timeout of 5 minutes. After 3 minutes, you call * ChangeMessageVisibility with a timeout of 10 minutes. You can continue to call * ChangeMessageVisibility to extend the visibility timeout to the maximum allowed time. If you try to * extend the visibility timeout beyond the maximum, your request is rejected. *

*

* An Amazon SQS message has three basic states: *

*
    *
  1. *

    * Sent to a queue by a producer. *

    *
  2. *
  3. *

    * Received from the queue by a consumer. *

    *
  4. *
  5. *

    * Deleted from the queue. *

    *
  6. *
*

* A message is considered to be stored after it is sent to a queue by a producer, but not yet received from * the queue by a consumer (that is, between states 1 and 2). There is no limit to the number of stored messages. A * message is considered to be in flight after it is received from a queue by a consumer, but not yet deleted * from the queue (that is, between states 2 and 3). There is a limit to the number of inflight messages. *

*

* Limits that apply to inflight messages are unrelated to the unlimited number of stored messages. *

*

* For most standard queues (depending on queue traffic and message backlog), there can be a maximum of * approximately 120,000 inflight messages (received from a queue by a consumer, but not yet deleted from the * queue). If you reach this limit, Amazon SQS returns the OverLimit error message. To avoid reaching * the limit, you should delete messages from the queue after they're processed. You can also increase the number of * queues you use to process your messages. To request a limit increase, file a support request. *

*

* For FIFO queues, there can be a maximum of 20,000 inflight messages (received from a queue by a consumer, but not * yet deleted from the queue). If you reach this limit, Amazon SQS returns no error messages. *

* *

* If you attempt to set the VisibilityTimeout to a value greater than the maximum time left, Amazon * SQS returns an error. Amazon SQS doesn't automatically recalculate and increase the timeout to the maximum * remaining time. *

*

* Unlike with a queue, when you change the visibility timeout for a specific message the timeout value is applied * immediately but isn't saved in memory for that message. If you don't delete a message after it is received, the * visibility timeout for the message reverts to the original timeout value (not to the value you set using the * ChangeMessageVisibility action) the next time the message is received. *

*
* * @param changeMessageVisibilityRequest * @return Result of the ChangeMessageVisibility operation returned by the service. * @throws MessageNotInflightException * The specified message isn't in flight. * @throws ReceiptHandleIsInvalidException * The specified receipt handle isn't valid. * @throws SdkException * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for * catch all scenarios. * @throws SdkClientException * If any client side error occurs such as an IO related failure, failure to get credentials, etc. * @throws SqsException * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. * @sample SqsClient.ChangeMessageVisibility * @see AWS * API Documentation */ public ChangeMessageVisibilityResponse changeMessageVisibility(ChangeMessageVisibilityRequest changeMessageVisibilityRequest) throws AwsServiceException, SdkClientException { ChangeMessageVisibilityRequest.Builder changeMessageVisibilityRequestBuilder = changeMessageVisibilityRequest.toBuilder(); if (isS3ReceiptHandle(changeMessageVisibilityRequest.receiptHandle())) { changeMessageVisibilityRequestBuilder.receiptHandle( getOrigReceiptHandle(changeMessageVisibilityRequest.receiptHandle())); } return amazonSqsToBeExtended.changeMessageVisibility(changeMessageVisibilityRequestBuilder.build()); } /** *

* Delivers up to ten messages to the specified queue. This is a batch version of SendMessage. * For a FIFO queue, multiple messages within a single batch are enqueued in the order they are sent. *

*

* The result of sending each message is reported individually in the response. Because the batch request can result * in a combination of successful and unsuccessful actions, you should check for batch errors even when the call * returns an HTTP status code of 200. *

*

* The maximum allowed individual message size and the maximum total payload size (the sum of the individual lengths * of all of the batched messages) are both 256 KB (262,144 bytes). *

* *

* A message can include only XML, JSON, and unformatted text. The following Unicode characters are allowed: *

*

* #x9 | #xA | #xD | #x20 to #xD7FF | * #xE000 to #xFFFD | #x10000 to #x10FFFF *

*

* Any characters not included in this list will be rejected. For more information, see the W3C specification for characters. *

*
*

* If you don't specify the DelaySeconds parameter for an entry, Amazon SQS uses the default value for * the queue. *

*

* Some actions take lists of parameters. These lists are specified using the param.n notation. Values * of n are integers starting from 1. For example, a parameter list with two elements looks like this: *

*

* &AttributeName.1=first *

*

* &AttributeName.2=second *

* * @param sendMessageBatchRequest * @return Result of the SendMessageBatch operation returned by the service. * @throws TooManyEntriesInBatchRequestException * The batch request contains more entries than permissible. * @throws EmptyBatchRequestException * The batch request doesn't contain any entries. * @throws BatchEntryIdsNotDistinctException * Two or more batch entries in the request have the same Id. * @throws BatchRequestTooLongException * The length of all the messages put together is more than the limit. * @throws InvalidBatchEntryIdException * The Id of a batch entry in a batch request doesn't abide by the specification. * @throws UnsupportedOperationException * Error code 400. Unsupported operation. * @throws SdkException * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for * catch all scenarios. * @throws SdkClientException * If any client side error occurs such as an IO related failure, failure to get credentials, etc. * @throws SqsException * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. * @sample SqsClient.SendMessageBatch * @see AWS API * Documentation */ public SendMessageBatchResponse sendMessageBatch(SendMessageBatchRequest sendMessageBatchRequest) { if (sendMessageBatchRequest == null) { String errorMessage = "sendMessageBatchRequest cannot be null."; LOG.error(errorMessage); throw SdkClientException.create(errorMessage); } SendMessageBatchRequest.Builder sendMessageBatchRequestBuilder = sendMessageBatchRequest.toBuilder(); appendUserAgent(sendMessageBatchRequestBuilder); sendMessageBatchRequest = sendMessageBatchRequestBuilder.build(); if (!clientConfiguration.isPayloadSupportEnabled()) { return super.sendMessageBatch(sendMessageBatchRequest); } List batchEntries = new ArrayList<>(sendMessageBatchRequest.entries().size()); boolean hasS3Entries = false; for (SendMessageBatchRequestEntry entry : sendMessageBatchRequest.entries()) { //Check message attributes for ExtendedClient related constraints checkMessageAttributes(entry.messageAttributes()); if (clientConfiguration.isAlwaysThroughS3() || isLarge(entry)) { entry = storeMessageInS3(entry); hasS3Entries = true; } batchEntries.add(entry); } if (hasS3Entries) { sendMessageBatchRequest = sendMessageBatchRequest.toBuilder().entries(batchEntries).build(); } return super.sendMessageBatch(sendMessageBatchRequest); } /** *

* Deletes up to ten messages from the specified queue. This is a batch version of * DeleteMessage. The result of the action on each message is reported individually in the * response. *

* *

* Because the batch request can result in a combination of successful and unsuccessful actions, you should check * for batch errors even when the call returns an HTTP status code of 200. *

*
*

* Some actions take lists of parameters. These lists are specified using the param.n notation. Values * of n are integers starting from 1. For example, a parameter list with two elements looks like this: *

*

* &AttributeName.1=first *

*

* &AttributeName.2=second *

* * @param deleteMessageBatchRequest * @return Result of the DeleteMessageBatch operation returned by the service. * @throws TooManyEntriesInBatchRequestException * The batch request contains more entries than permissible. * @throws EmptyBatchRequestException * The batch request doesn't contain any entries. * @throws BatchEntryIdsNotDistinctException * Two or more batch entries in the request have the same Id. * @throws InvalidBatchEntryIdException * The Id of a batch entry in a batch request doesn't abide by the specification. * @throws SdkException * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for * catch all scenarios. * @throws SdkClientException * If any client side error occurs such as an IO related failure, failure to get credentials, etc. * @throws SqsException * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. * @sample SqsClient.DeleteMessageBatch * @see AWS API * Documentation */ public DeleteMessageBatchResponse deleteMessageBatch(DeleteMessageBatchRequest deleteMessageBatchRequest) { if (deleteMessageBatchRequest == null) { String errorMessage = "deleteMessageBatchRequest cannot be null."; LOG.error(errorMessage); throw SdkClientException.create(errorMessage); } DeleteMessageBatchRequest.Builder deleteMessageBatchRequestBuilder = deleteMessageBatchRequest.toBuilder(); appendUserAgent(deleteMessageBatchRequestBuilder); if (!clientConfiguration.isPayloadSupportEnabled()) { return super.deleteMessageBatch(deleteMessageBatchRequest); } List entries = new ArrayList<>(deleteMessageBatchRequest.entries().size()); for (DeleteMessageBatchRequestEntry entry : deleteMessageBatchRequest.entries()) { DeleteMessageBatchRequestEntry.Builder entryBuilder = entry.toBuilder(); String receiptHandle = entry.receiptHandle(); String origReceiptHandle = receiptHandle; // Update original receipt handle if needed if (isS3ReceiptHandle(receiptHandle)) { origReceiptHandle = getOrigReceiptHandle(receiptHandle); // Delete s3 payload if needed if (clientConfiguration.doesCleanupS3Payload()) { String messagePointer = getMessagePointerFromModifiedReceiptHandle(receiptHandle); payloadStore.deleteOriginalPayload(messagePointer); } } entryBuilder.receiptHandle(origReceiptHandle); entries.add(entryBuilder.build()); } deleteMessageBatchRequestBuilder.entries(entries); return super.deleteMessageBatch(deleteMessageBatchRequestBuilder.build()); } /** *

* Changes the visibility timeout of multiple messages. This is a batch version of * ChangeMessageVisibility. The result of the action on each message is reported individually * in the response. You can send up to 10 ChangeMessageVisibility requests with each * ChangeMessageVisibilityBatch action. *

* *

* Because the batch request can result in a combination of successful and unsuccessful actions, you should check * for batch errors even when the call returns an HTTP status code of 200. *

*
*

* Some actions take lists of parameters. These lists are specified using the param.n notation. Values * of n are integers starting from 1. For example, a parameter list with two elements looks like this: *

*

* &AttributeName.1=first *

*

* &AttributeName.2=second *

* * @param changeMessageVisibilityBatchRequest * @return Result of the ChangeMessageVisibilityBatch operation returned by the service. * @throws TooManyEntriesInBatchRequestException * The batch request contains more entries than permissible. * @throws EmptyBatchRequestException * The batch request doesn't contain any entries. * @throws BatchEntryIdsNotDistinctException * Two or more batch entries in the request have the same Id. * @throws InvalidBatchEntryIdException * The Id of a batch entry in a batch request doesn't abide by the specification. * @throws SdkException * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for * catch all scenarios. * @throws SdkClientException * If any client side error occurs such as an IO related failure, failure to get credentials, etc. * @throws SqsException * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. * @sample SqsClient.ChangeMessageVisibilityBatch * @see AWS API Documentation */ public ChangeMessageVisibilityBatchResponse changeMessageVisibilityBatch( ChangeMessageVisibilityBatchRequest changeMessageVisibilityBatchRequest) throws AwsServiceException, SdkClientException { List entries = new ArrayList<>(changeMessageVisibilityBatchRequest.entries().size()); for (ChangeMessageVisibilityBatchRequestEntry entry : changeMessageVisibilityBatchRequest.entries()) { ChangeMessageVisibilityBatchRequestEntry.Builder entryBuilder = entry.toBuilder(); if (isS3ReceiptHandle(entry.receiptHandle())) { entryBuilder.receiptHandle(getOrigReceiptHandle(entry.receiptHandle())); } entries.add(entryBuilder.build()); } return amazonSqsToBeExtended.changeMessageVisibilityBatch( changeMessageVisibilityBatchRequest.toBuilder().entries(entries).build()); } /** *

* Deletes the messages in a queue specified by the QueueURL parameter. *

* *

* When you use the PurgeQueue action, you can't retrieve any messages deleted from a queue. *

*

* The message deletion process takes up to 60 seconds. We recommend waiting for 60 seconds regardless of your * queue's size. *

*
*

* Messages sent to the queue before you call PurgeQueue might be received but are deleted * within the next minute. *

*

* Messages sent to the queue after you call PurgeQueue might be deleted while the queue is * being purged. *

* * @param purgeQueueRequest * @return Result of the PurgeQueue operation returned by the service. * @throws QueueDoesNotExistException * The specified queue doesn't exist. * @throws PurgeQueueInProgressException * Indicates that the specified queue previously received a PurgeQueue request within the last * 60 seconds (the time it can take to delete the messages in the queue). * @throws SdkException * Base class for all exceptions that can be thrown by the SDK (both service and client). Can be used for * catch all scenarios. * @throws SdkClientException * If any client side error occurs such as an IO related failure, failure to get credentials, etc. * @throws SqsException * Base class for all service exceptions. Unknown exceptions will be thrown as an instance of this type. * @sample SqsClient.PurgeQueue * @see AWS API * Documentation */ public PurgeQueueResponse purgeQueue(PurgeQueueRequest purgeQueueRequest) throws AwsServiceException, SdkClientException { LOG.warn("Calling purgeQueue deletes SQS messages without deleting their payload from S3."); if (purgeQueueRequest == null) { String errorMessage = "purgeQueueRequest cannot be null."; LOG.error(errorMessage); throw SdkClientException.create(errorMessage); } PurgeQueueRequest.Builder purgeQueueRequestBuilder = purgeQueueRequest.toBuilder(); appendUserAgent(purgeQueueRequestBuilder); return super.purgeQueue(purgeQueueRequestBuilder.build()); } private void checkMessageAttributes(Map messageAttributes) { int msgAttributesSize = getMsgAttributesSize(messageAttributes); if (msgAttributesSize > clientConfiguration.getPayloadSizeThreshold()) { String errorMessage = "Total size of Message attributes is " + msgAttributesSize + " bytes which is larger than the threshold of " + clientConfiguration.getPayloadSizeThreshold() + " Bytes. Consider including the payload in the message body instead of message attributes."; LOG.error(errorMessage); throw SdkClientException.create(errorMessage); } int messageAttributesNum = messageAttributes.size(); if (messageAttributesNum > SQSExtendedClientConstants.MAX_ALLOWED_ATTRIBUTES) { String errorMessage = "Number of message attributes [" + messageAttributesNum + "] exceeds the maximum allowed for large-payload messages [" + SQSExtendedClientConstants.MAX_ALLOWED_ATTRIBUTES + "]."; LOG.error(errorMessage); throw SdkClientException.create(errorMessage); } Optional largePayloadAttributeName = getReservedAttributeNameIfPresent(messageAttributes); if (largePayloadAttributeName.isPresent()) { String errorMessage = "Message attribute name " + largePayloadAttributeName.get() + " is reserved for use by SQS extended client."; LOG.error(errorMessage); throw SdkClientException.create(errorMessage); } } /** * TODO: Wrap the message pointer as-is to the receiptHandle so that it can be generic * and does not use any LargeMessageStore implementation specific details. */ private String embedS3PointerInReceiptHandle(String receiptHandle, String pointer) { PayloadS3Pointer s3Pointer = PayloadS3Pointer.fromJson(pointer); String s3MsgBucketName = s3Pointer.getS3BucketName(); String s3MsgKey = s3Pointer.getS3Key(); String modifiedReceiptHandle = SQSExtendedClientConstants.S3_BUCKET_NAME_MARKER + s3MsgBucketName + SQSExtendedClientConstants.S3_BUCKET_NAME_MARKER + SQSExtendedClientConstants.S3_KEY_MARKER + s3MsgKey + SQSExtendedClientConstants.S3_KEY_MARKER + receiptHandle; return modifiedReceiptHandle; } private String getOrigReceiptHandle(String receiptHandle) { int secondOccurence = receiptHandle.indexOf(SQSExtendedClientConstants.S3_KEY_MARKER, receiptHandle.indexOf(SQSExtendedClientConstants.S3_KEY_MARKER) + 1); return receiptHandle.substring(secondOccurence + SQSExtendedClientConstants.S3_KEY_MARKER.length()); } private String getFromReceiptHandleByMarker(String receiptHandle, String marker) { int firstOccurence = receiptHandle.indexOf(marker); int secondOccurence = receiptHandle.indexOf(marker, firstOccurence + 1); return receiptHandle.substring(firstOccurence + marker.length(), secondOccurence); } private boolean isS3ReceiptHandle(String receiptHandle) { return receiptHandle.contains(SQSExtendedClientConstants.S3_BUCKET_NAME_MARKER) && receiptHandle.contains(SQSExtendedClientConstants.S3_KEY_MARKER); } private String getMessagePointerFromModifiedReceiptHandle(String receiptHandle) { String s3MsgBucketName = getFromReceiptHandleByMarker(receiptHandle, SQSExtendedClientConstants.S3_BUCKET_NAME_MARKER); String s3MsgKey = getFromReceiptHandleByMarker(receiptHandle, SQSExtendedClientConstants.S3_KEY_MARKER); PayloadS3Pointer payloadS3Pointer = new PayloadS3Pointer(s3MsgBucketName, s3MsgKey); return payloadS3Pointer.toJson(); } private boolean isLarge(SendMessageRequest sendMessageRequest) { int msgAttributesSize = getMsgAttributesSize(sendMessageRequest.messageAttributes()); long msgBodySize = Util.getStringSizeInBytes(sendMessageRequest.messageBody()); long totalMsgSize = msgAttributesSize + msgBodySize; return (totalMsgSize > clientConfiguration.getPayloadSizeThreshold()); } private boolean isLarge(SendMessageBatchRequestEntry batchEntry) { int msgAttributesSize = getMsgAttributesSize(batchEntry.messageAttributes()); long msgBodySize = Util.getStringSizeInBytes(batchEntry.messageBody()); long totalMsgSize = msgAttributesSize + msgBodySize; return (totalMsgSize > clientConfiguration.getPayloadSizeThreshold()); } private Optional getReservedAttributeNameIfPresent(Map msgAttributes) { String reservedAttributeName = null; if (msgAttributes.containsKey(SQSExtendedClientConstants.RESERVED_ATTRIBUTE_NAME)) { reservedAttributeName = SQSExtendedClientConstants.RESERVED_ATTRIBUTE_NAME; } else if (msgAttributes.containsKey(LEGACY_RESERVED_ATTRIBUTE_NAME)) { reservedAttributeName = LEGACY_RESERVED_ATTRIBUTE_NAME; } return Optional.ofNullable(reservedAttributeName); } private int getMsgAttributesSize(Map msgAttributes) { int totalMsgAttributesSize = 0; for (Map.Entry entry : msgAttributes.entrySet()) { totalMsgAttributesSize += Util.getStringSizeInBytes(entry.getKey()); MessageAttributeValue entryVal = entry.getValue(); if (entryVal.dataType() != null) { totalMsgAttributesSize += Util.getStringSizeInBytes(entryVal.dataType()); } String stringVal = entryVal.stringValue(); if (stringVal != null) { totalMsgAttributesSize += Util.getStringSizeInBytes(entryVal.stringValue()); } SdkBytes binaryVal = entryVal.binaryValue(); if (binaryVal != null) { totalMsgAttributesSize += binaryVal.asByteArray().length; } } return totalMsgAttributesSize; } private SendMessageBatchRequestEntry storeMessageInS3(SendMessageBatchRequestEntry batchEntry) { // Read the content of the message from message body String messageContentStr = batchEntry.messageBody(); Long messageContentSize = Util.getStringSizeInBytes(messageContentStr); SendMessageBatchRequestEntry.Builder batchEntryBuilder = batchEntry.toBuilder(); batchEntryBuilder.messageAttributes( updateMessageAttributePayloadSize(batchEntry.messageAttributes(), messageContentSize)); // Store the message content in S3. String largeMessagePointer = storeOriginalPayload(messageContentStr); batchEntryBuilder.messageBody(largeMessagePointer); return batchEntryBuilder.build(); } private SendMessageRequest storeMessageInS3(SendMessageRequest sendMessageRequest) { // Read the content of the message from message body String messageContentStr = sendMessageRequest.messageBody(); Long messageContentSize = Util.getStringSizeInBytes(messageContentStr); SendMessageRequest.Builder sendMessageRequestBuilder = sendMessageRequest.toBuilder(); sendMessageRequestBuilder.messageAttributes( updateMessageAttributePayloadSize(sendMessageRequest.messageAttributes(), messageContentSize)); // Store the message content in S3. String largeMessagePointer = storeOriginalPayload(messageContentStr); sendMessageRequestBuilder.messageBody(largeMessagePointer); return sendMessageRequestBuilder.build(); } private String storeOriginalPayload(String messageContentStr) { String s3KeyPrefix = clientConfiguration.getS3KeyPrefix(); if (StringUtils.isBlank(s3KeyPrefix)) { return payloadStore.storeOriginalPayload(messageContentStr); } return payloadStore.storeOriginalPayload(messageContentStr, s3KeyPrefix + UUID.randomUUID()); } private Map updateMessageAttributePayloadSize( Map messageAttributes, Long messageContentSize) { Map updatedMessageAttributes = new HashMap<>(messageAttributes); // Add a new message attribute as a flag MessageAttributeValue.Builder messageAttributeValueBuilder = MessageAttributeValue.builder(); messageAttributeValueBuilder.dataType("Number"); messageAttributeValueBuilder.stringValue(messageContentSize.toString()); MessageAttributeValue messageAttributeValue = messageAttributeValueBuilder.build(); if (!clientConfiguration.usesLegacyReservedAttributeName()) { updatedMessageAttributes.put(SQSExtendedClientConstants.RESERVED_ATTRIBUTE_NAME, messageAttributeValue); } else { updatedMessageAttributes.put(LEGACY_RESERVED_ATTRIBUTE_NAME, messageAttributeValue); } return updatedMessageAttributes; } @SuppressWarnings("unchecked") private static T appendUserAgent(final T builder) { return (T) builder .overrideConfiguration( AwsRequestOverrideConfiguration.builder() .addApiName(ApiName.builder().name(USER_AGENT_NAME) .version(USER_AGENT_VERSION).build()) .build()); } @Override public void close() { super.close(); this.clientConfiguration.getS3Client().close(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy