/*
*
* Copyright 2021 Jacky Zong. All rights reserved.
*
*
* 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.util.HashSet;
import java.util.Set;
import javax.jms.InvalidDestinationException;
import javax.jms.JMSException;
import javax.jms.JMSSecurityException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.model.ChangeMessageVisibilityBatchRequest;
import com.amazonaws.services.sqs.model.ChangeMessageVisibilityBatchResult;
import com.amazonaws.services.sqs.model.ChangeMessageVisibilityRequest;
import com.amazonaws.services.sqs.model.CreateQueueResult;
import com.amazonaws.services.sqs.model.DeleteMessageBatchRequest;
import com.amazonaws.services.sqs.model.DeleteMessageBatchResult;
import com.amazonaws.services.sqs.model.DeleteMessageRequest;
import com.amazonaws.services.sqs.model.CreateQueueRequest;
import com.amazonaws.services.sqs.model.GetQueueUrlRequest;
import com.amazonaws.services.sqs.model.GetQueueUrlResult;
import com.amazonaws.services.sqs.model.QueueDoesNotExistException;
import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
import com.amazonaws.services.sqs.model.ReceiveMessageResult;
import com.amazonaws.services.sqs.model.SendMessageRequest;
import com.amazonaws.services.sqs.model.SendMessageResult;
/**
* This is a JMS Wrapper of <code>AmazonSQSClient</code>. This class changes all
* <code>AmazonServiceException</code> and <code>AmazonClientException</code> into
* JMSException/JMSSecurityException.
*/
public class AmazonSQSMessagingClientWrapper {
private static final Log LOG = LogFactory.getLog(AmazonSQSMessagingClientWrapper.class);
private static final Set<String> SECURITY_EXCEPTION_ERROR_CODES;
static {
SECURITY_EXCEPTION_ERROR_CODES = new HashSet<String>();
SECURITY_EXCEPTION_ERROR_CODES.add("MissingClientTokenId");
SECURITY_EXCEPTION_ERROR_CODES.add("InvalidClientTokenId");
SECURITY_EXCEPTION_ERROR_CODES.add("MissingAuthenticationToken");
SECURITY_EXCEPTION_ERROR_CODES.add("AccessDenied");
}
private final AmazonSQS amazonSQSClient;
private final AWSCredentialsProvider credentialsProvider;
public AmazonSQSMessagingClientWrapper(AmazonSQS amazonSQSClient) throws JMSException {
this(amazonSQSClient, null);
}
/**
* @param amazonSQSClient
* The AWS SDK Client for SQS.
* @throws JMSException
* if the client is null
*/
public AmazonSQSMessagingClientWrapper(AmazonSQS amazonSQSClient, AWSCredentialsProvider credentialsProvider) throws JMSException {
if (amazonSQSClient == null) {
throw new JMSException("Amazon SQS client cannot be null");
}
this.amazonSQSClient = amazonSQSClient;
this.credentialsProvider = credentialsProvider;
}
public AmazonSQS getAmazonSQSClient() {
return amazonSQSClient;
}
/**
* Sets SQS endpoint and wraps IllegalArgumentException.
* Deprecated. Instead of manipulating settings of existing AmazonSQS client, provide correct configuration when creating it through SQSConnectionFactory constructors.
*
* @param endpoint
* The endpoint (ex: "sqs.us-east-1.amazonaws.com") of the region
* specific AWS endpoint this client will communicate with.
* @throws JMSException
*/
@Deprecated
public void setEndpoint(String endpoint) throws JMSException {
try {
amazonSQSClient.setEndpoint(endpoint);
} catch (IllegalArgumentException ase) {
JMSException jmsException = new JMSException(ase.getMessage());
throw (JMSException) jmsException.initCause(ase);
}
}
public void deleteMessage(DeleteMessageRequest deleteMessageRequest) throws JMSException {
try {
prepareRequest(deleteMessageRequest);
amazonSQSClient.deleteMessage(deleteMessageRequest);
} catch (AmazonClientException e) {
throw handleException(e, "deleteMessage");
}
}
public DeleteMessageBatchResult deleteMessageBatch(DeleteMessageBatchRequest deleteMessageBatchRequest) throws JMSException {
try {
prepareRequest(deleteMessageBatchRequest);
return amazonSQSClient.deleteMessageBatch(deleteMessageBatchRequest);
} catch (AmazonClientException e) {
throw handleException(e, "deleteMessageBatch");
}
}
/**
* Calls <code>sendMessage</code> and wraps
* <code>AmazonClientException</code>.
*
* @param sendMessageRequest
* Container for the necessary parameters to execute the
* sendMessage service method on AmazonSQS.
* @return The response from the sendMessage service method, as returned by
* AmazonSQS
* @throws JMSException
*/
public SendMessageResult sendMessage(SendMessageRequest sendMessageRequest) throws JMSException {
try {
prepareRequest(sendMessageRequest);
return amazonSQSClient.sendMessage(sendMessageRequest);
} catch (AmazonClientException e) {
throw handleException(e, "sendMessage");
}
}
public boolean queueExists(String queueName) throws JMSException {
try {
amazonSQSClient.getQueueUrl(prepareRequest(new GetQueueUrlRequest(queueName)));
return true;
} catch (QueueDoesNotExistException e) {
return false;
} catch (AmazonClientException e) {
throw handleException(e, "getQueueUrl");
}
}
/**
* Check if the requested queue exists. This function calls
* <code>GetQueueUrl</code> for the given queue name with the given owner
* accountId, returning true on success, false if it gets
* <code>QueueDoesNotExistException</code>.
*
* @param queueName
* the queue to check
* @param queueOwnerAccountId
* The AWS accountId of the account that created the queue
* @return true if the queue exists, false if it doesn't.
* @throws JMSException
*/
public boolean queueExists(String queueName, String queueOwnerAccountId) throws JMSException {
try {
GetQueueUrlRequest getQueueUrlRequest = new GetQueueUrlRequest(queueName);
getQueueUrlRequest.setQueueOwnerAWSAccountId(queueOwnerAccountId);
prepareRequest(getQueueUrlRequest);
amazonSQSClient.getQueueUrl(getQueueUrlRequest);
return true;
} catch (QueueDoesNotExistException e) {
return false;
} catch (AmazonClientException e) {
throw handleException(e, "getQueueUrl");
}
}
/**
* Gets the queueUrl of a queue given a queue name.
*
* @param queueName
* @return The response from the GetQueueUrl service method, as returned by
* AmazonSQS, which will include queue`s URL
* @throws JMSException
*/
public GetQueueUrlResult getQueueUrl(String queueName) throws JMSException {
return getQueueUrl(new GetQueueUrlRequest(queueName));
}
/**
* Gets the queueUrl of a queue given a queue name owned by the provided accountId.
*
* @param queueName
* @param queueOwnerAccountId The AWS accountId of the account that created the queue
* @return The response from the GetQueueUrl service method, as returned by
* AmazonSQS, which will include queue`s URL
* @throws JMSException
*/
public GetQueueUrlResult getQueueUrl(String queueName, String queueOwnerAccountId) throws JMSException {
return getQueueUrl(new GetQueueUrlRequest(queueName).withQueueOwnerAWSAccountId(queueOwnerAccountId));
}
/**
* Calls <code>getQueueUrl</code> and wraps <code>AmazonClientException</code>
*
* @param getQueueUrlRequest
* Container for the necessary parameters to execute the
* getQueueUrl service method on AmazonSQS.
* @return The response from the GetQueueUrl service method, as returned by
* AmazonSQS, which will include queue`s URL
* @throws JMSException
*/
public GetQueueUrlResult getQueueUrl(GetQueueUrlRequest getQueueUrlRequest) throws JMSException {
try {
prepareRequest(getQueueUrlRequest);
return amazonSQSClient.getQueueUrl(getQueueUrlRequest);
} catch (AmazonClientException e) {
throw handleException(e, "getQueueUrl");
}
}
/**
* Calls <code>createQueue</code> to create the queue with the default queue attributes,
* and wraps <code>AmazonClientException</code>
*
* @param queueName
* @return The response from the createQueue service method, as returned by
* AmazonSQS. This call creates a new queue, or returns the URL of
* an existing one.
* @throws JMSException
*/
public CreateQueueResult createQueue(String queueName) throws JMSException {
return createQueue(new CreateQueueRequest(queueName));
}
/**
* Calls <code>createQueue</code> to create the queue with the provided queue attributes
* if any, and wraps <code>AmazonClientException</code>
*
* @param createQueueRequest
* Container for the necessary parameters to execute the
* createQueue service method on AmazonSQS.
* @return The response from the createQueue service method, as returned by
* AmazonSQS. This call creates a new queue, or returns the URL of
* an existing one.
* @throws JMSException
*/
public CreateQueueResult createQueue(CreateQueueRequest createQueueRequest) throws JMSException {
try {
prepareRequest(createQueueRequest);
return amazonSQSClient.createQueue(createQueueRequest);
} catch (AmazonClientException e) {
throw handleException(e, "createQueue");
}
}
/**
* Calls <code>receiveMessage</code> and wraps <code>AmazonClientException</code>. Used by
* {@link SQSMessageConsumerPrefetch} to receive up to minimum of
* (<code>numberOfMessagesToPrefetch</code>,10) messages from SQS queue into consumer
* prefetch buffers.
*
* @param receiveMessageRequest
* Container for the necessary parameters to execute the
* receiveMessage service method on AmazonSQS.
* @return The response from the ReceiveMessage service method, as returned
* by AmazonSQS.
* @throws JMSException
*/
public ReceiveMessageResult receiveMessage(ReceiveMessageRequest receiveMessageRequest) throws JMSException {
try {
prepareRequest(receiveMessageRequest);
return amazonSQSClient.receiveMessage(receiveMessageRequest);
} catch (AmazonClientException e) {
throw handleException(e, "receiveMessage");
}
}
/**
* Calls <code>changeMessageVisibility</code> and wraps <code>AmazonClientException</code>. This is
* used to for negative acknowledge of a single message, so that messages can be received again without any delay.
*
* @param changeMessageVisibilityRequest
* Container for the necessary parameters to execute the
* changeMessageVisibility service method on AmazonSQS.
* @throws JMSException
*/
public void changeMessageVisibility(ChangeMessageVisibilityRequest changeMessageVisibilityRequest) throws JMSException {
try {
prepareRequest(changeMessageVisibilityRequest);
amazonSQSClient.changeMessageVisibility(changeMessageVisibilityRequest);
} catch (AmazonClientException e) {
throw handleException(e, "changeMessageVisibility");
}
}
/**
* Calls <code>changeMessageVisibilityBatch</code> and wraps <code>AmazonClientException</code>. This is
* used to for negative acknowledge of messages in batch, so that messages
* can be received again without any delay.
*
* @param changeMessageVisibilityBatchRequest
* Container for the necessary parameters to execute the
* changeMessageVisibilityBatch service method on AmazonSQS.
* @return The response from the changeMessageVisibilityBatch service
* method, as returned by AmazonSQS.
* @throws JMSException
*/
public ChangeMessageVisibilityBatchResult changeMessageVisibilityBatch(ChangeMessageVisibilityBatchRequest changeMessageVisibilityBatchRequest)
throws JMSException {
try {
prepareRequest(changeMessageVisibilityBatchRequest);
return amazonSQSClient.changeMessageVisibilityBatch(changeMessageVisibilityBatchRequest);
} catch (AmazonClientException e) {
throw handleException(e, "changeMessageVisibilityBatch");
}
}
/**
* Create generic error message for <code>AmazonServiceException</code>. Message include
* Action, RequestId, HTTPStatusCode, and AmazonErrorCode.
*/
private String logAndGetAmazonServiceException(AmazonServiceException ase, String action) {
String errorMessage = "AmazonServiceException: " + action + ". RequestId: " + ase.getRequestId() +
"\nHTTPStatusCode: " + ase.getStatusCode() + " AmazonErrorCode: " +
ase.getErrorCode();
LOG.error(errorMessage, ase);
return errorMessage;
}
/**
* Create generic error message for <code>AmazonClientException</code>. Message include
* Action.
*/
private String logAndGetAmazonClientException(AmazonClientException ace, String action) {
String errorMessage = "AmazonClientException: " + action + ".";
LOG.error(errorMessage, ace);
return errorMessage;
}
private JMSException handleException(AmazonClientException e, String operationName) throws JMSException {
JMSException jmsException;
if (e instanceof AmazonServiceException) {
AmazonServiceException se = ( AmazonServiceException ) e;
if (e instanceof QueueDoesNotExistException) {
jmsException = new InvalidDestinationException(
logAndGetAmazonServiceException(se, operationName), se.getErrorCode());
} else if (isJMSSecurityException(se)) {
jmsException = new JMSSecurityException(
logAndGetAmazonServiceException(se, operationName), se.getErrorCode());
} else {
jmsException = new JMSException(
logAndGetAmazonServiceException(se, operationName), se.getErrorCode());
}
} else {
jmsException = new JMSException(logAndGetAmazonClientException(e, operationName));
}
jmsException.initCause(e);
return jmsException;
}
private boolean isJMSSecurityException(AmazonServiceException e) {
return SECURITY_EXCEPTION_ERROR_CODES.contains(e.getErrorCode()) ;
}
private <T extends AmazonWebServiceRequest> T prepareRequest(T request) {
request.getRequestClientOptions().appendUserAgent(SQSMessagingClientConstants.APPENDED_USER_AGENT_HEADER_VERSION);
if (credentialsProvider != null) {
request.setRequestCredentialsProvider(credentialsProvider);
}
return request;
}
}