com.kolich.aws.services.sqs.impl.KolichSQSClient.java Source code

Java tutorial

Introduction

Here is the source code for com.kolich.aws.services.sqs.impl.KolichSQSClient.java

Source

/**
 * Copyright (c) 2014 Mark S. Kolich
 * http://mark.koli.ch
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package com.kolich.aws.services.sqs.impl;

import com.amazonaws.services.sqs.model.CreateQueueResult;
import com.amazonaws.services.sqs.model.ListQueuesResult;
import com.amazonaws.services.sqs.model.ReceiveMessageResult;
import com.amazonaws.services.sqs.model.SendMessageResult;
import com.amazonaws.services.sqs.model.transform.CreateQueueResultStaxUnmarshaller;
import com.amazonaws.services.sqs.model.transform.ListQueuesResultStaxUnmarshaller;
import com.amazonaws.services.sqs.model.transform.ReceiveMessageResultStaxUnmarshaller;
import com.amazonaws.services.sqs.model.transform.SendMessageResultStaxUnmarshaller;
import com.amazonaws.transform.StaxUnmarshallerContext;
import com.amazonaws.transform.Unmarshaller;
import com.kolich.aws.services.AbstractAwsService;
import com.kolich.aws.services.AbstractAwsSigner;
import com.kolich.aws.services.sqs.SQSClient;
import com.kolich.aws.services.sqs.SQSRegion;
import com.kolich.aws.transport.AwsHttpRequest;
import com.kolich.common.functional.either.Either;
import com.kolich.common.functional.option.None;
import com.kolich.common.functional.option.Option;
import com.kolich.common.functional.option.Some;
import com.kolich.http.common.response.HttpFailure;
import com.kolich.http.common.response.HttpSuccess;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpRequestBase;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import java.net.URI;
import java.util.regex.Pattern;

import static com.amazonaws.ResponseMetadata.AWS_REQUEST_ID;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.kolich.aws.services.sqs.SQSRegion.DEFAULT;
import static java.util.regex.Pattern.compile;
import static javax.xml.stream.XMLInputFactory.newInstance;
import static org.apache.http.HttpStatus.SC_OK;

public final class KolichSQSClient extends AbstractAwsService implements SQSClient {

    /**
     * SQS visibility timeouts can only be at most 43200 seconds (12-hours).
     */
    private static final int SQS_MAX_VISIBILITY_TIMEOUT = 43200; // seconds

    /**
     * The maximum number of messages to receive on any given request
     * cannot be more than 10.
     */
    private static final int SQS_MAX_MESSAGES_PER_REQUEST = 10;

    /**
     * The maximum amount of time SQS will allow any client to long
     * poll waiting for message delivery.
     */
    private static final int SQS_MAX_LONG_POLL_WAIT_TIME_SECS = 20; // seconds

    private static final String SQS_ACTION_PARAM = "Action";
    private static final String SQS_QUEUE_NAME_PARAM = "QueueName";
    private static final String SQS_DEFAULT_VISIBILITY_TIMEOUT_PARAM = "DefaultVisibilityTimeout";
    private static final String SQS_LONG_POLL_WAIT_TIME_PARAM = "WaitTimeSeconds";
    private static final String SQS_VISIBILITY_TIMEOUT_PARAM = "VisibilityTimeout";
    private static final String SQS_MESSAGE_BODY_PARAM = "MessageBody";
    private static final String SQS_RECEIPT_HANDLE_PARAM = "ReceiptHandle";
    private static final String SQS_MAX_MESSAGES_PARAM = "MaxNumberOfMessages";

    private static final String SQS_ACTION_LIST_QUEUES = "ListQueues";
    private static final String SQS_ACTION_CREATE_QUEUE = "CreateQueue";
    private static final String SQS_ACTION_DELETE_QUEUE = "DeleteQueue";
    private static final String SQS_ACTION_SEND_MESSAGE = "SendMessage";
    private static final String SQS_ACTION_RECEIVE_MESSAGE = "ReceiveMessage";
    private static final String SQS_ACTION_DELETE_MESSAGE = "DeleteMessage";
    private static final String SQS_ACTION_CHANGE_VISIBILITY = "ChangeMessageVisibility";

    /**
     * Queue names can only contain alphanumeric characters, hyphens,
     * and underscores and must be between 1 and 80-characters in length.
     */
    private static final Pattern VALID_QUEUE_NAME_PATTERN = compile("\\A[a-z0-9_\\-]{1,80}\\Z");

    private final HttpClient client_;

    public KolichSQSClient(final HttpClient client, final AbstractAwsSigner signer, final SQSRegion region) {
        super(signer, region.getApiEndpoint());
        client_ = client;
    }

    public KolichSQSClient(final HttpClient client, final String key, final String secret, final SQSRegion region) {
        this(client, new KolichSQSSigner(key, secret), region);
    }

    public KolichSQSClient(final HttpClient client, final String key, final String secret) {
        this(client, key, secret, DEFAULT);
    }

    private abstract class AwsSQSHttpClosure<S> extends AwsBaseHttpClosure<S> {
        private final Unmarshaller<S, StaxUnmarshallerContext> unmarshaller_;

        public AwsSQSHttpClosure(final HttpClient client, final int expectStatus,
                final Unmarshaller<S, StaxUnmarshallerContext> unmarshaller) {
            super(client, expectStatus);
            unmarshaller_ = unmarshaller;
        }

        public AwsSQSHttpClosure(final HttpClient client, final int expectStatus) {
            this(client, expectStatus, null);
        }

        @Override
        public final void before(final HttpRequestBase request) throws Exception {
            final AwsHttpRequest wrapped = new AwsHttpRequest(request);
            validate();
            prepare(wrapped);
            signRequest(wrapped);
        }

        public void validate() throws Exception {
            // Default, nothing.
        }

        public void prepare(final AwsHttpRequest request) throws Exception {
            // Default, nothing.
        }

        @Override
        public S success(final HttpSuccess success) throws Exception {
            return (unmarshaller_ != null) ? unmarshall(success) : null;
        }

        private final S unmarshall(final HttpSuccess success) throws Exception {
            XMLEventReader reader = null;
            try {
                final XMLInputFactory xmlInputFactory = newInstance();
                reader = xmlInputFactory.createXMLEventReader(success.getContent());
                final StaxUnmarshallerContext stax = new StaxUnmarshallerContext(reader);
                stax.registerMetadataExpression("ResponseMetadata/RequestId", 2, AWS_REQUEST_ID);
                stax.registerMetadataExpression("requestId", 2, AWS_REQUEST_ID);
                return unmarshaller_.unmarshall(stax);
            } finally {
                if (reader != null) {
                    reader.close();
                }
            }
        }

        public final Either<HttpFailure, S> post() {
            return post(SLASH_STRING);
        }

        public final Option<HttpFailure> postOption(final URI uri) {
            final Either<HttpFailure, S> either = post(uri);
            return either.success() ? None.<HttpFailure>none() : Some.<HttpFailure>some(either.left());
        }
    }

    @Override
    public Either<HttpFailure, ListQueuesResult> listQueues() {
        return new AwsSQSHttpClosure<ListQueuesResult>(client_, SC_OK, new ListQueuesResultStaxUnmarshaller()) {
            @Override
            public void prepare(final AwsHttpRequest request) throws Exception {
                request.addParameter(SQS_ACTION_PARAM, SQS_ACTION_LIST_QUEUES);
            }
        }.post();
    }

    @Override
    public Either<HttpFailure, CreateQueueResult> createQueue(final String queueName,
            final Integer defaultVisibilityTimeout) {
        return new AwsSQSHttpClosure<CreateQueueResult>(client_, SC_OK, new CreateQueueResultStaxUnmarshaller()) {
            @Override
            public void validate() throws Exception {
                checkNotNull(queueName, "Queue name cannot be null.");
                checkState(isValidQueueName(queueName),
                        "Invalid queue name, " + "did not match expected queue name pattern.");
                if (defaultVisibilityTimeout != null) {
                    checkState(defaultVisibilityTimeout <= SQS_MAX_VISIBILITY_TIMEOUT,
                            "Default visibility timeout cannot be greater than: " + SQS_MAX_VISIBILITY_TIMEOUT);
                }
            }

            @Override
            public void prepare(final AwsHttpRequest request) throws Exception {
                request.addParameter(SQS_ACTION_PARAM, SQS_ACTION_CREATE_QUEUE);
                request.addParameter(SQS_QUEUE_NAME_PARAM, queueName);
                if (defaultVisibilityTimeout != null) {
                    request.addParameter(SQS_DEFAULT_VISIBILITY_TIMEOUT_PARAM,
                            Integer.toString(defaultVisibilityTimeout));
                }
            }
        }.post();
    }

    @Override
    public Either<HttpFailure, CreateQueueResult> createQueue(final String queueName) {
        return createQueue(queueName, null);
    }

    @Override
    public Option<HttpFailure> deleteQueue(final URI queueURI) {
        return new AwsSQSHttpClosure<Void>(client_, SC_OK) {
            @Override
            public void validate() throws Exception {
                checkNotNull(queueURI, "Queue URI cannot be null.");
            }

            @Override
            public void prepare(final AwsHttpRequest request) throws Exception {
                request.addParameter(SQS_ACTION_PARAM, SQS_ACTION_DELETE_QUEUE);
            }
        }.postOption(queueURI);
    }

    @Override
    public Either<HttpFailure, SendMessageResult> sendMessage(final URI queueURI, final String message) {
        return new AwsSQSHttpClosure<SendMessageResult>(client_, SC_OK, new SendMessageResultStaxUnmarshaller()) {
            @Override
            public void validate() throws Exception {
                checkNotNull(queueURI, "Queue URI cannot be null.");
                checkNotNull(message, "Message to send cannot be null.");
            }

            @Override
            public void prepare(final AwsHttpRequest request) throws Exception {
                request.addParameter(SQS_ACTION_PARAM, SQS_ACTION_SEND_MESSAGE);
                request.addParameter(SQS_MESSAGE_BODY_PARAM, message);
            }
        }.post(queueURI);
    }

    @Override
    public Either<HttpFailure, ReceiveMessageResult> receiveMessage(final URI queueURI,
            final Integer longPollWaitSecs, final Integer maxNumberOfMessages) {
        return new AwsSQSHttpClosure<ReceiveMessageResult>(client_, SC_OK,
                new ReceiveMessageResultStaxUnmarshaller()) {
            @Override
            public void validate() throws Exception {
                checkNotNull(queueURI, "Queue URI cannot be null.");
                if (longPollWaitSecs != null) {
                    checkState(longPollWaitSecs <= SQS_MAX_LONG_POLL_WAIT_TIME_SECS,
                            "Cannot long poll wait on a queue longer than (secs): "
                                    + SQS_MAX_LONG_POLL_WAIT_TIME_SECS);
                }
                if (maxNumberOfMessages != null) {
                    checkState(maxNumberOfMessages <= SQS_MAX_MESSAGES_PER_REQUEST,
                            "Max number of messages to receive cannot be greater than: "
                                    + SQS_MAX_MESSAGES_PER_REQUEST);
                }
            }

            @Override
            public void prepare(final AwsHttpRequest request) throws Exception {
                request.addParameter(SQS_ACTION_PARAM, SQS_ACTION_RECEIVE_MESSAGE);
                if (longPollWaitSecs != null) {
                    request.addParameter(SQS_LONG_POLL_WAIT_TIME_PARAM, Integer.toString(longPollWaitSecs));
                }
                if (maxNumberOfMessages != null) {
                    request.addParameter(SQS_MAX_MESSAGES_PARAM, Integer.toString(maxNumberOfMessages));
                }
            }
        }.post(queueURI);
    }

    @Override
    public Either<HttpFailure, ReceiveMessageResult> receiveMessage(final URI queueURI,
            final Integer longPollWaitSecs) {
        return receiveMessage(queueURI, longPollWaitSecs, null);
    }

    @Override
    public Either<HttpFailure, ReceiveMessageResult> receiveMessage(final URI queueURI) {
        return receiveMessage(queueURI, null);
    }

    @Override
    public Option<HttpFailure> deleteMessage(final URI queueURI, final String receiptHandle) {
        return new AwsSQSHttpClosure<Void>(client_, SC_OK) {
            @Override
            public void validate() throws Exception {
                checkNotNull(queueURI, "Queue URI cannot be null.");
                checkNotNull(receiptHandle, "Message receipt handle cannot " + "be null.");
            }

            @Override
            public void prepare(final AwsHttpRequest request) throws Exception {
                request.addParameter(SQS_ACTION_PARAM, SQS_ACTION_DELETE_MESSAGE);
                request.addParameter(SQS_RECEIPT_HANDLE_PARAM, receiptHandle);
            }
        }.postOption(queueURI);
    }

    @Override
    public Option<HttpFailure> changeMessageVisibility(final URI queueURI, final String receiptHandle,
            final Integer visibilityTimeout) {
        return new AwsSQSHttpClosure<Void>(client_, SC_OK) {
            @Override
            public void validate() throws Exception {
                checkNotNull(queueURI, "Queue URI cannot be null.");
                checkNotNull(receiptHandle, "Message receipt handle cannot " + "be null.");
                checkNotNull(visibilityTimeout, "Message visibility timeout " + "cannot be null.");
                checkState(visibilityTimeout <= SQS_MAX_VISIBILITY_TIMEOUT,
                        "Message visibility timeout cannot be greater than: " + SQS_MAX_VISIBILITY_TIMEOUT);
            }

            @Override
            public void prepare(final AwsHttpRequest request) throws Exception {
                request.addParameter(SQS_ACTION_PARAM, SQS_ACTION_CHANGE_VISIBILITY);
                request.addParameter(SQS_RECEIPT_HANDLE_PARAM, receiptHandle);
                request.addParameter(SQS_VISIBILITY_TIMEOUT_PARAM, Integer.toString(visibilityTimeout));
            }
        }.postOption(queueURI);
    }

    private static final boolean isValidQueueName(final String queueName) {
        return VALID_QUEUE_NAME_PATTERN.matcher(queueName).matches();
    }

}