package com.bizo.awsstubs.services.sqs; // com.amazonaws.services.sqs.model defines its own UnsupportedOperationException, for some reason import java.lang.UnsupportedOperationException; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.PriorityQueue; import java.util.TreeMap; import java.util.List; import java.util.Map; import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.AmazonWebServiceRequest; import com.amazonaws.ResponseMetadata; import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; import com.amazonaws.services.sqs.AmazonSQS; import com.amazonaws.services.sqs.model.*; /** * An AmazonSQS stub which stores its queues in memory. * <p> * This stub doesn't handle visibility timeouts (<a href="http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html">http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html</a>), * i.e. if a received message hasn't been deleted before its visibility timeout expires, it doesn't become visible and * return to its queue automatically. To simulate a message being returned to its queue, use the {@link * #returnMessage(String, Message)} method. */ public class AmazonSQSStub implements AmazonSQS { private final Map<String, Queue> queuesByQueueUrl = new TreeMap<String, Queue>(); private Region region = Region.getRegion(Regions.US_EAST_1); @Override public void shutdown() { throw new UnsupportedOperationException(); } @Override public void setEndpoint(final String arg0) { throw new UnsupportedOperationException(); } @Override public void setRegion(final Region region) { this.region = region; } @Override public SetQueueAttributesResult setQueueAttributes(final SetQueueAttributesRequest arg0) { throw new UnsupportedOperationException(); } @Override public ChangeMessageVisibilityBatchResult changeMessageVisibilityBatch(final ChangeMessageVisibilityBatchRequest arg0) { throw new UnsupportedOperationException(); } @Override public ChangeMessageVisibilityResult changeMessageVisibility(final ChangeMessageVisibilityRequest arg0) { throw new UnsupportedOperationException(); } @Override public GetQueueUrlResult getQueueUrl(final GetQueueUrlRequest request) { return new GetQueueUrlResult().withQueueUrl(generateQueueUrl(request.getQueueName())); } @Override public RemovePermissionResult removePermission(final RemovePermissionRequest arg0) { throw new UnsupportedOperationException(); } @Override public GetQueueAttributesResult getQueueAttributes(final GetQueueAttributesRequest arg0) { throw new UnsupportedOperationException(); } @Override public SendMessageBatchResult sendMessageBatch(final SendMessageBatchRequest arg0) { throw new UnsupportedOperationException(); } @Override public DeleteQueueResult deleteQueue(final DeleteQueueRequest arg0) { throw new UnsupportedOperationException(); } @Override public SendMessageResult sendMessage(final SendMessageRequest request) { final Message message = new Message().withBody(request.getMessageBody()).withMessageAttributes(request.getMessageAttributes()); final Queue queue = queuesByQueueUrl.get(request.getQueueUrl()); queue.sendMessage(message); return new SendMessageResult().withMessageId(message.getMessageId()); } @Override public ReceiveMessageResult receiveMessage(final ReceiveMessageRequest request) { final Queue queue = queuesByQueueUrl.get(request.getQueueUrl()); final List<Message> messages = new ArrayList<Message>(); final int maxNumberOfMessages = request.getMaxNumberOfMessages() == null ? 1 : Math.min(request.getMaxNumberOfMessages(), 10); Message m; while (messages.size() < maxNumberOfMessages && (m = queue.receiveMessage()) != null) { final Map<String, String> attributes = new HashMap<String, String>(m.getAttributes()); attributes.keySet().retainAll(request.getAttributeNames()); final Map<String, MessageAttributeValue> messageAttributes = new HashMap<String, MessageAttributeValue>(m.getMessageAttributes()); messageAttributes.keySet().retainAll(request.getMessageAttributeNames()); Message transformedMessage = new Message() .withBody(m.getBody()) .withMessageId(m.getMessageId()) .withReceiptHandle(m.getReceiptHandle()) .withAttributes(attributes) .withMessageAttributes(messageAttributes); messages.add(transformedMessage); } return new ReceiveMessageResult().withMessages(messages); } @Override public ListQueuesResult listQueues(final ListQueuesRequest arg0) { throw new UnsupportedOperationException(); } @Override public ListQueuesResult listQueues() { return new ListQueuesResult().withQueueUrls(queuesByQueueUrl.keySet()); } @Override public DeleteMessageBatchResult deleteMessageBatch(final DeleteMessageBatchRequest arg0) { throw new UnsupportedOperationException(); } @Override public CreateQueueResult createQueue(final CreateQueueRequest request) { final String queueName = request.getQueueName(); final String queueUrl = generateQueueUrl(queueName); if (queuesByQueueUrl.containsKey(queueUrl)) { throw new QueueNameExistsException(queueName); } queuesByQueueUrl.put(queueUrl, new Queue()); return new CreateQueueResult().withQueueUrl(queueUrl); } @Override public AddPermissionResult addPermission(final AddPermissionRequest arg0) { throw new UnsupportedOperationException(); } @Override public DeleteMessageResult deleteMessage(final DeleteMessageRequest request) { final Queue queue = queuesByQueueUrl.get(request.getQueueUrl()); queue.deleteMessage(request.getReceiptHandle()); return new DeleteMessageResult(); } @Override public ResponseMetadata getCachedResponseMetadata(final AmazonWebServiceRequest arg0) { throw new UnsupportedOperationException(); } @Override public ListDeadLetterSourceQueuesResult listDeadLetterSourceQueues(final ListDeadLetterSourceQueuesRequest listDeadLetterSourceQueuesRequest) { throw new UnsupportedOperationException(); } @Override public SetQueueAttributesResult setQueueAttributes(final String queueUrl, final Map<String, String> attributes) { throw new UnsupportedOperationException(); } @Override public ChangeMessageVisibilityBatchResult changeMessageVisibilityBatch( final String queueUrl, final List<ChangeMessageVisibilityBatchRequestEntry> entries) { throw new UnsupportedOperationException(); } @Override public ChangeMessageVisibilityResult changeMessageVisibility(final String queueUrl, final String receiptHandle, final Integer visibilityTimeout) { throw new UnsupportedOperationException(); } @Override public GetQueueUrlResult getQueueUrl(final String queueName) { return getQueueUrl(new GetQueueUrlRequest().withQueueName(queueName)); } @Override public RemovePermissionResult removePermission(final String queueUrl, final String label) { throw new UnsupportedOperationException(); } @Override public GetQueueAttributesResult getQueueAttributes(final String queueUrl, final List<String> attributeNames) { throw new UnsupportedOperationException(); } @Override public SendMessageBatchResult sendMessageBatch(final String queueUrl, final List<SendMessageBatchRequestEntry> entries) { throw new UnsupportedOperationException(); } @Override public DeleteQueueResult deleteQueue(final String queueUrl) { throw new UnsupportedOperationException(); } @Override public SendMessageResult sendMessage(final String queueUrl, final String messageBody) { return sendMessage(new SendMessageRequest().withQueueUrl(queueUrl).withMessageBody(messageBody)); } @Override public ReceiveMessageResult receiveMessage(final String queueUrl) { return receiveMessage(new ReceiveMessageRequest().withQueueUrl(queueUrl)); } @Override public ListQueuesResult listQueues(final String queueNamePrefix) { throw new UnsupportedOperationException(); } @Override public DeleteMessageBatchResult deleteMessageBatch(final String queueUrl, final List<DeleteMessageBatchRequestEntry> entries) { throw new UnsupportedOperationException(); } @Override public CreateQueueResult createQueue(final String queueName) { return createQueue(new CreateQueueRequest().withQueueName(queueName)); } @Override public AddPermissionResult addPermission(final String queueUrl, final String label, final List<String> aWSAccountIds, final List<String> actions) { throw new UnsupportedOperationException(); } @Override public DeleteMessageResult deleteMessage(final String queueUrl, final String receiptHandle) { deleteMessage(new DeleteMessageRequest().withQueueUrl(queueUrl).withReceiptHandle(receiptHandle)); return new DeleteMessageResult(); } // Testing support public String generateQueueUrl(final String queueName) { return "https://sqs." + region.getName() + ".amazonaws.com/000000000000/" + queueName; } /** * Return message back to a queue, so it can be received again. * <p> * @see <a href="http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html">http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html</a> */ public void returnMessage(final String queueUrl, final Message message) { final Queue queue = queuesByQueueUrl.get(queueUrl); queue.returnMessage(message); } /** * Get messages belonging to a particular queue so that they can be examined. */ public List<Message> getMessages(final String queueUrl) { final Queue queue = queuesByQueueUrl.get(queueUrl); return new ArrayList<Message>(queue.messageQueue); } private static class Queue { private int sequenceNumber = 0; // Sent messages go into this queue and can be received. If a received message is returned, it goes back into // this queue. The message with the lowest sequence number is received first. private final PriorityQueue<Message> messageQueue = new PriorityQueue<Message>(11, new Comparator<Message>() { @Override public int compare(Message m1, Message m2) { return Integer.parseInt(m1.getMessageId()) - Integer.parseInt(m2.getMessageId()); } }); // Received messages move into this list and are considered in-flight and are not visible. private final List<Message> inflightMessages = new LinkedList<Message>(); public void sendMessage(final Message message) { message.setMessageId(String.valueOf(++sequenceNumber)); messageQueue.offer(message); } public Message receiveMessage() { final Message message = messageQueue.poll(); if (message != null) { message.setReceiptHandle(message.getMessageId() + "-" + System.currentTimeMillis()); inflightMessages.add(message); } return message; } public DeleteMessageResult deleteMessage(final String receiptHandle) throws ReceiptHandleIsInvalidException { final Iterator<Message> it = inflightMessages.iterator(); while (it.hasNext()) { final Message m = it.next(); if (m.getReceiptHandle().equals(receiptHandle)) { it.remove(); return new DeleteMessageResult(); } } throw new ReceiptHandleIsInvalidException(receiptHandle); } public void returnMessage(final Message message) { inflightMessages.remove(message); messageQueue.offer(message); } } @Override public PurgeQueueResult purgeQueue(PurgeQueueRequest purgeQueueRequest) throws AmazonServiceException, AmazonClientException { throw new UnsupportedOperationException(); } }