com.amazon.sqs.javamessaging.SQSSession Maven / Gradle / Ivy
Show all versions of amazon-sqs-java-messaging-lib Show documentation
/*
* Copyright 2010-2014 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.io.Serializable;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.jms.BytesMessage;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueBrowser;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.StreamMessage;
import javax.jms.TemporaryQueue;
import javax.jms.TemporaryTopic;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;
import javax.jms.IllegalStateException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.amazon.sqs.javamessaging.SQSMessageConsumerPrefetch.MessageManager;
import com.amazon.sqs.javamessaging.acknowledge.AcknowledgeMode;
import com.amazon.sqs.javamessaging.acknowledge.Acknowledger;
import com.amazon.sqs.javamessaging.acknowledge.NegativeAcknowledger;
import com.amazon.sqs.javamessaging.message.SQSBytesMessage;
import com.amazon.sqs.javamessaging.message.SQSObjectMessage;
import com.amazon.sqs.javamessaging.message.SQSTextMessage;
import com.amazon.sqs.javamessaging.util.SQSMessagingClientThreadFactory;
/**
* A session serves several purposes:
*
* - It is a factory for its message producers and consumers.
* - It provides a way to create Queue objects for those clients that need to
* dynamically manipulate provider-specific destination names.
* - It retains messages it consumes until they have been acknowledged.
* - It serializes execution of message listeners registered with its message
* consumers.
*
*
* Not safe for concurrent use.
*
* This session object does not support:
*
* - (Temporary)Topic
* - Temporary Queue
* - Browser
* - MapMessage
* - StreamMessage
* - MessageSelector
* - Transactions
*
*/
public class SQSSession implements Session, QueueSession {
private static final Log LOG = LogFactory.getLog(SQSSession.class);
private static final int SESSION_EXECUTOR_GRACEFUL_SHUTDOWN_TIME = 10;
static final String SESSION_EXECUTOR_NAME = "SessionCallBackScheduler";
/** Used to create session callback scheduler threads */
static final SQSMessagingClientThreadFactory SESSION_THREAD_FACTORY = new SQSMessagingClientThreadFactory(
SESSION_EXECUTOR_NAME, false, true);
static final String CONSUMER_PREFETCH_EXECUTER_NAME = "ConsumerPrefetch";
/** Used to create consumer prefetcher threads */
static final SQSMessagingClientThreadFactory CONSUMER_PREFETCH_THREAD_FACTORY = new SQSMessagingClientThreadFactory(
CONSUMER_PREFETCH_EXECUTER_NAME, true);
/**
* Non standard acknowledge mode. This is a variation of CLIENT_ACKNOWLEDGE
* where Clients need to remember to call acknowledge on message. Difference
* is that calling acknowledge on a message only acknowledge the message
* being called.
*/
public static final int UNORDERED_ACKNOWLEDGE = 100;
/**
* True if Session is closed.
*/
private volatile boolean closed = false;
/**
* False if Session is stopped.
*/
volatile boolean running = false;
/**
* True if Session is closed or close is in-progress.
*/
private volatile boolean closing = false;
private final AmazonSQSMessagingClientWrapper amazonSQSClient;
private final SQSConnection parentSQSConnection;
/**
* AcknowledgeMode of this Session.
*/
private final AcknowledgeMode acknowledgeMode;
/**
* Acknowledger of this Session.
*/
private final Acknowledger acknowledger;
/**
* Set of MessageProducer under this Session
*/
private final Set messageProducers;
/**
* Set of MessageConsumer under this Session
*/
private final Set messageConsumers;
/**
* Thread that is responsible to guarantee serial execution of message
* delivery on message listeners
*/
private final SQSSessionCallbackScheduler sqsSessionRunnable;
/**
* Executor service for running MessageListener.
*/
private final ExecutorService executor;
private final Object stateLock = new Object();
/**
* Used to determine if the caller thread is the session callback thread.
* Guarded by stateLock
*/
private Thread activeCallbackSessionThread;
/**
* Used to determine the active consumer, whose is dispatching the message
* on the callback. Guarded by stateLock
*/
private SQSMessageConsumer activeConsumerInCallback = null;
SQSSession(SQSConnection parentSQSConnection, AcknowledgeMode acknowledgeMode) throws JMSException{
this(parentSQSConnection, acknowledgeMode,
Collections.newSetFromMap(new ConcurrentHashMap()),
Collections.newSetFromMap(new ConcurrentHashMap()));
}
SQSSession(SQSConnection parentSQSConnection, AcknowledgeMode acknowledgeMode,
Set messageConsumers,
Set messageProducers) throws JMSException{
this.parentSQSConnection = parentSQSConnection;
this.amazonSQSClient = parentSQSConnection.getWrappedAmazonSQSClient();
this.acknowledgeMode = acknowledgeMode;
this.acknowledger = this.acknowledgeMode.createAcknowledger(amazonSQSClient, this);
this.sqsSessionRunnable = new SQSSessionCallbackScheduler(this, acknowledgeMode, acknowledger);
this.executor = Executors.newSingleThreadExecutor(SESSION_THREAD_FACTORY);
this.messageConsumers = messageConsumers;
this.messageProducers = messageProducers;
executor.execute(sqsSessionRunnable);
}
SQSConnection getParentConnection() {
return parentSQSConnection;
}
/**
* @return True if the current thread is the callback thread
*/
boolean isActiveCallbackSessionThread() {
synchronized (stateLock) {
return activeCallbackSessionThread == Thread.currentThread();
}
}
/**
* Creates a QueueReceiver
for the specified queue.
* @param queue
* a queue receiver
* @return new message consumer
* @throws JMSException
* If session is closed
*/
@Override
public QueueReceiver createReceiver(Queue queue) throws JMSException {
return (QueueReceiver) createConsumer(queue);
}
/**
* Creates a QueueReceiver
for the specified queue. Does not
* support messageSelector. It will drop anything in messageSelector.
*
* @param queue
* a queue destination
* @param messageSelector
* @return new message receiver
* @throws JMSException
* If session is closed
*/
@Override
public QueueReceiver createReceiver(Queue queue, String messageSelector) throws JMSException {
return createReceiver(queue);
}
/**
* Creates a QueueSender
for the specified queue.
*
* @param queue
* a queue destination
* @return new message sender
* @throws JMSException
* If session is closed
*/
@Override
public QueueSender createSender(Queue queue) throws JMSException {
return (QueueSender) createProducer(queue);
}
/**
* Creates a BytesMessage
.
*
* @return new BytesMessage
* @throws JMSException
* If session is closed or internal error
*/
@Override
public BytesMessage createBytesMessage() throws JMSException {
checkClosed();
return new SQSBytesMessage();
}
/**
* According to JMS specification, a message can be sent with only headers
* without any payload, SQS does not support messages with empty payload. so
* this method is not supported
*/
@Override
public Message createMessage() throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/**
* Creates a ObjectMessage
.
*
* @return new ObjectMessage
* @throws JMSException
* If session is closed or internal error
*/
@Override
public ObjectMessage createObjectMessage() throws JMSException {
checkClosed();
return new SQSObjectMessage();
}
/**
* Creates an initialized ObjectMessage
.
*
* @param object
* The initialized ObjectMessage
* @return new ObjectMessage
* @throws JMSException
* If session is closed or internal error
*/
@Override
public ObjectMessage createObjectMessage(Serializable object) throws JMSException {
checkClosed();
return new SQSObjectMessage(object);
}
/**
* Creates a TextMessage
.
*
* @return new TextMessage
* @throws JMSException
* If session is closed or internal error
*/
@Override
public TextMessage createTextMessage() throws JMSException {
checkClosed();
return new SQSTextMessage();
}
/**
* Creates an initialized TextMessage
.
*
* @param text
* The initialized TextMessage
* @return new TextMessage
* @throws JMSException
* If session is closed or internal error
*/
@Override
public TextMessage createTextMessage(String text) throws JMSException {
checkClosed();
return new SQSTextMessage(text);
}
/**
* Returns the acknowledge mode of the session. The acknowledge mode is set
* at the time that the session is created.
*
* @return acknowledge mode
*/
@Override
public int getAcknowledgeMode() throws JMSException {
return acknowledgeMode.getOriginalAcknowledgeMode();
}
/**
* Closes the session.
*
* This will not return until all the message consumers and producers close
* internally, which blocks until receives and/or message listeners in
* progress have completed. A blocked message consumer receive call returns
* null when this session is closed.
*
* Since consumer prefetch threads use SQS long-poll feature with 20 seconds
* timeout, closing each consumer prefetch thread can take up to 20 seconds,
* which in-turn will impact the time on session close.
*
* This method is safe for concurrent use.
*
* A message listener must not attempt to close its own session; otherwise
* throws a IllegalStateException.
*
* Invoking any other session method on a closed session must throw a
* IllegalStateException
.
*
* @throws IllegalStateException
* If called by a message listener on its own
* Session
.
* @throws JMSException
* On internal error.
*/
@Override
public void close() throws JMSException {
if (closed) {
return;
}
/**
* A MessageListener must not attempt to close its own Session as
* this would lead to deadlock
*/
if (isActiveCallbackSessionThread()) {
throw new IllegalStateException(
"MessageListener must not attempt to close its own Session to prevent potential deadlock issues");
}
doClose();
}
void doClose() throws JMSException {
boolean shouldClose = false;
synchronized (stateLock) {
if (!closing) {
shouldClose = true;
closing = true;
}
stateLock.notifyAll();
}
if (closed) {
return;
}
if (shouldClose) {
try {
parentSQSConnection.removeSession(this);
for (SQSMessageConsumer messageConsumer : messageConsumers) {
messageConsumer.close();
/** Nack the messages that were delivered but not acked */
messageConsumer.recover();
}
try {
if (executor != null) {
LOG.info("Shutting down " + SESSION_EXECUTOR_NAME + " executor");
/** Shut down executor. */
executor.shutdown();
waitForCallbackComplete();
sqsSessionRunnable.close();
for (MessageProducer messageProducer : messageProducers) {
messageProducer.close();
}
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
LOG.warn("Can't terminate executor service " + SESSION_EXECUTOR_NAME + " after " +
SESSION_EXECUTOR_GRACEFUL_SHUTDOWN_TIME +
" seconds, some running threads will be shutdown immediately");
executor.shutdownNow();
}
}
} catch (InterruptedException e) {
LOG.error("Interrupted while closing the session.", e);
}
} finally {
synchronized (stateLock) {
closed = true;
running = false;
stateLock.notifyAll();
}
}
}/** Blocks until closing of the session completes */
else {
synchronized (stateLock) {
while (!closed) {
try {
stateLock.wait();
} catch (InterruptedException e) {
LOG.error("Interrupted while waiting the session to close.", e);
}
}
}
}
}
/**
* Negative acknowledges all the messages on the session that is delivered
* but not acknowledged.
*
* @throws JMSException
* If session is closed or on internal error.
*/
@Override
public void recover() throws JMSException {
checkClosed();
for (SQSMessageConsumer messageConsumer : messageConsumers) {
messageConsumer.recover();
}
}
@Override
public void run() {
}
/**
* Creates a MessageProducer
for the specified destination.
* Only queue destinations are supported at this time.
*
* @param destination
* a queue destination
* @return new message producer
* @throws JMSException
* If session is closed or queue destination is not used
*/
@Override
public MessageProducer createProducer(Destination destination) throws JMSException {
checkClosed();
if (destination != null && !(destination instanceof SQSQueueDestination)) {
throw new JMSException("Actual type of Destination/Queue has to be SQSQueueDestination");
}
SQSMessageProducer messageProducer;
synchronized (stateLock) {
checkClosing();
messageProducer = new SQSMessageProducer(amazonSQSClient, this, (SQSQueueDestination) destination);
messageProducers.add(messageProducer);
}
return messageProducer;
}
/**
* Creates a MessageConsumer
for the specified destination.
* Only queue destinations are supported at this time.
*
* @param destination
* a queue destination
* @return new message consumer
* @throws JMSException
* If session is closed or queue destination is not used
*/
@Override
public MessageConsumer createConsumer(Destination destination) throws JMSException {
checkClosed();
if (!(destination instanceof SQSQueueDestination)) {
throw new JMSException("Actual type of Destination/Queue has to be SQSQueueDestination");
}
SQSMessageConsumer messageConsumer;
synchronized (stateLock) {
checkClosing();
messageConsumer = createSQSMessageConsumer((SQSQueueDestination) destination);
messageConsumers.add(messageConsumer);
if( running ) {
messageConsumer.startPrefetch();
}
}
return messageConsumer;
}
SQSMessageConsumer createSQSMessageConsumer(SQSQueueDestination destination) {
return new SQSMessageConsumer(
parentSQSConnection, this, sqsSessionRunnable, (SQSQueueDestination) destination,
acknowledger, new NegativeAcknowledger(amazonSQSClient),
CONSUMER_PREFETCH_THREAD_FACTORY);
}
/**
*
* Creates a MessageConsumer
for the specified destination.
* Only queue destinations are supported at this time.
* It will ignore any argument in messageSelector.
*
* @param destination
* a queue destination
* @param messageSelector
* @return new message consumer
* @throws JMSException
* If session is closed or queue destination is not used
*/
@Override
public MessageConsumer createConsumer(Destination destination, String messageSelector) throws JMSException {
if (messageSelector != null) {
throw new JMSException("SQSSession does not support MessageSelector. This should be null.");
}
return createConsumer(destination);
}
/**
* Creates a MessageConsumer
for the specified destination.
* Only queue destinations are supported at this time. It will ignore any
* argument in messageSelector and NoLocal.
*
* @param destination
* a queue destination
* @param messageSelector
* @param NoLocal
* @return new message consumer
* @throws JMSException
* If session is closed or queue destination is not used
*/
@Override
public MessageConsumer createConsumer(Destination destination, String messageSelector, boolean NoLocal) throws JMSException {
if (messageSelector != null) {
throw new JMSException("SQSSession does not support MessageSelector. This should be null.");
}
return createConsumer(destination);
}
/**
* This does not create SQS Queue. This method is only to create JMS Queue Object.
* Make sure the queue exists corresponding to the queueName.
* @param queueName
* @return a queue destination
* @throws JMSException
* If session is closed or invalid queue is provided
*/
@Override
public Queue createQueue(String queueName) throws JMSException {
checkClosed();
return new SQSQueueDestination(queueName, amazonSQSClient.getQueueUrl(queueName).getQueueUrl());
}
/**
* This does not create SQS Queue. This method is only to create JMS Queue
* Object. Make sure the queue exists corresponding to the queueName and
* ownerAccountId.
*
* @param queueName
* @param ownerAccountId
* the account id, which originally created the queue on SQS
* @return a queue destination
* @throws JMSException
* If session is closed or invalid queue is provided
*/
public Queue createQueue(String queueName, String ownerAccountId) throws JMSException {
checkClosed();
return new SQSQueueDestination(
queueName, amazonSQSClient.getQueueUrl(queueName, ownerAccountId).getQueueUrl());
}
/**
* This is used in MessageConsumer. When MessageConsumer is closed
* it will remove itself from list of consumers.
*/
void removeConsumer(SQSMessageConsumer consumer) {
messageConsumers.remove(consumer);
}
/**
* This is used in MessageProducer. When MessageProducer is closed
* it will remove itself from list of producers.
*/
void removeProducer(SQSMessageProducer producer) {
messageProducers.remove(producer);
}
void startingCallback(SQSMessageConsumer consumer) throws InterruptedException, JMSException {
if (closed) {
return;
}
synchronized (stateLock) {
if (activeConsumerInCallback != null) {
throw new IllegalStateException("Callback already in progress");
}
assert activeCallbackSessionThread == null;
while (!running && !closing) {
try {
stateLock.wait();
} catch (InterruptedException e) {
LOG.warn("Interrupted while waiting on session start. Continue to wait...", e);
}
}
checkClosing();
activeConsumerInCallback = consumer;
activeCallbackSessionThread = Thread.currentThread();
}
}
void finishedCallback() throws JMSException {
synchronized (stateLock) {
if (activeConsumerInCallback == null) {
throw new IllegalStateException("Callback not in progress");
}
activeConsumerInCallback = null;
activeCallbackSessionThread = null;
stateLock.notifyAll();
}
}
void waitForConsumerCallbackToComplete(SQSMessageConsumer consumer) throws InterruptedException {
synchronized (stateLock) {
while (activeConsumerInCallback == consumer) {
try {
stateLock.wait();
} catch (InterruptedException e) {
LOG.warn(
"Interrupted while waiting the active consumer in callback to complete. Continue to wait...",
e);
}
}
}
}
void waitForCallbackComplete() {
synchronized (stateLock) {
while (activeConsumerInCallback != null) {
try {
stateLock.wait();
} catch (InterruptedException e) {
LOG.warn("Interrupted while waiting on session callback completion. Continue to wait...", e);
}
}
}
}
/** SQS does not support transacted. Transacted will always be false. */
@Override
public boolean getTransacted() throws JMSException {
return false;
}
/** This method is not supported. This method is related to transaction which SQS doesn't support */
@Override
public void commit() throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. This method is related to transaction which SQS doesn't support */
@Override
public void rollback() throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. This method is related to Topic which SQS doesn't support */
@Override
public void unsubscribe(String name) throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. */
@Override
public Topic createTopic(String topicName) throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. */
@Override
public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. */
@Override
public TopicSubscriber createDurableSubscriber(Topic topic, String name, String messageSelector, boolean noLocal) throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. */
@Override
public QueueBrowser createBrowser(Queue queue) throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. */
@Override
public QueueBrowser createBrowser(Queue queue, String messageSelector) throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. */
@Override
public TemporaryQueue createTemporaryQueue() throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. */
@Override
public TemporaryTopic createTemporaryTopic() throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. */
@Override
public MessageListener getMessageListener() throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. */
@Override
public void setMessageListener(MessageListener listener) throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. */
@Override
public StreamMessage createStreamMessage() throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
/** This method is not supported. */
@Override
public MapMessage createMapMessage() throws JMSException {
throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
}
static class CallbackEntry {
private final MessageListener messageListener;
private final MessageManager messageManager;
CallbackEntry(MessageListener messageListener, MessageManager messageManager) {
this.messageListener = messageListener;
this.messageManager = messageManager;
}
public MessageListener getMessageListener() {
return messageListener;
}
public MessageManager getMessageManager() {
return messageManager;
}
}
/**
* Check if session is closed.
*/
public void checkClosed() throws IllegalStateException {
if (closed) {
throw new IllegalStateException("Session is closed");
}
}
/**
* Check if session is closed or closing.
*/
public void checkClosing() throws IllegalStateException {
if (closing) {
throw new IllegalStateException("Session is closed or closing");
}
}
void start() throws IllegalStateException {
checkClosed();
synchronized (stateLock) {
checkClosing();
running = true;
for (SQSMessageConsumer messageConsumer : messageConsumers) {
messageConsumer.startPrefetch();
}
stateLock.notifyAll();
}
}
void stop() throws IllegalStateException {
checkClosed();
synchronized (stateLock) {
checkClosing();
running = false;
for (SQSMessageConsumer messageConsumer : messageConsumers) {
messageConsumer.stopPrefetch();
}
waitForCallbackComplete();
stateLock.notifyAll();
}
}
/*
* Unit Tests Utility Functions
*/
boolean isCallbackActive() {
return activeConsumerInCallback != null;
}
void setActiveConsumerInCallback(SQSMessageConsumer consumer) {
activeConsumerInCallback = consumer;
}
Object getStateLock() {
return stateLock;
}
boolean isClosed() {
return closed;
}
boolean isClosing() {
return closing;
}
void setClosed(boolean closed) {
this.closed = closed;
}
void setClosing(boolean closing) {
this.closing = closing;
}
void setRunning(boolean running) {
this.running = running;
}
boolean isRunning() {
return running;
}
SQSSessionCallbackScheduler getSqsSessionRunnable() {
return sqsSessionRunnable;
}
}