org.skyscreamer.nevado.jms.NevadoSession Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nevado-jms Show documentation
Show all versions of nevado-jms Show documentation
JMS Provider for Amazon's cloud services (uses SQS/SNS)
package org.skyscreamer.nevado.jms;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.skyscreamer.nevado.jms.destination.*;
import org.skyscreamer.nevado.jms.message.*;
import org.skyscreamer.nevado.jms.util.MessageIdUtil;
import javax.jms.*;
import javax.jms.IllegalStateException;
import javax.jms.Queue;
import java.io.Serializable;
import java.util.*;
/**
* Nevado implementation of the general JMS Session interface.
*
* @author Carter Page
*/
public class NevadoSession implements Session {
private final Log _log = LogFactory.getLog(getClass());
protected boolean _closed = false;
private final NevadoConnection _connection;
private boolean _transacted;
private int _acknowledgeMode;
private Integer _overrideJMSDeliveryMode;
private Long _overrideJMSTTL;
private Integer _overrideJMSPriority;
private MessageListener _messageListener;
private final AsyncConsumerRunner _asyncConsumerRunner;
private final MessageHolder _incomingStagedMessages = new MessageHolder(this);
private final Map> _outgoingTxMessages
= new HashMap>();
private final Set _consumers = new HashSet();
private final Set _producers = new HashSet();
private boolean _TESTING_ONLY_break = false;
protected NevadoSession(NevadoConnection connection, boolean transacted, int acknowledgeMode)
{
_connection = connection;
_transacted = transacted;
_acknowledgeMode = acknowledgeMode;
_asyncConsumerRunner = new AsyncConsumerRunner(_connection);
}
@Override
public NevadoBytesMessage createBytesMessage() throws JMSException
{
checkClosed();
NevadoBytesMessage message = new NevadoBytesMessage();
message.setNevadoSession(this);
return message;
}
@Override
public NevadoMapMessage createMapMessage() throws JMSException
{
checkClosed();
NevadoMapMessage message = new NevadoMapMessage();
message.setNevadoSession(this);
return message;
}
@Override
public NevadoMessage createMessage() throws JMSException
{
checkClosed();
NevadoMessage message = new NevadoBlankMessage();
message.setNevadoSession(this);
return message;
}
@Override
public NevadoObjectMessage createObjectMessage() throws JMSException
{
checkClosed();
NevadoObjectMessage message = new NevadoObjectMessage();
message.setNevadoSession(this);
return message;
}
@Override
public NevadoObjectMessage createObjectMessage(Serializable serializable) throws JMSException
{
checkClosed();
NevadoObjectMessage message = createObjectMessage();
message.setObject(serializable);
return message;
}
@Override
public NevadoStreamMessage createStreamMessage() throws JMSException
{
checkClosed();
NevadoStreamMessage message = new NevadoStreamMessage();
message.setNevadoSession(this);
return message;
}
@Override
public NevadoTextMessage createTextMessage() throws JMSException
{
checkClosed();
NevadoTextMessage message = new NevadoTextMessage();
message.setNevadoSession(this);
return message;
}
@Override
public NevadoTextMessage createTextMessage(String text) throws JMSException
{
checkClosed();
NevadoTextMessage message = createTextMessage();
message.setText(text);
return message;
}
@Override
public boolean getTransacted() throws JMSException
{
return _transacted;
}
@Override
public int getAcknowledgeMode() throws JMSException
{
return _acknowledgeMode;
}
// TODO - Think about how to handle a failure during commit (a break mid-way would fool atomicity)
@Override
public void commit() throws JMSException
{
checkClosed();
if (!_transacted)
{
throw new IllegalStateException("Cannot commit an untransacted session");
}
for(NevadoDestination destination : _outgoingTxMessages.keySet())
{
List outgoingMessages = _outgoingTxMessages.get(destination);
_connection.getSQSConnector().sendMessages(destination, outgoingMessages);
}
_outgoingTxMessages.clear();
_incomingStagedMessages.acknowledgeConsumedMessages();
}
@Override
public void rollback() throws JMSException
{
checkClosed();
if (!_transacted)
{
throw new IllegalStateException("Cannot rollback an untransacted session");
}
_outgoingTxMessages.clear();
_incomingStagedMessages.reset();
}
@Override
public synchronized void close() throws JMSException
{
if (!_closed)
{
stop();
_incomingStagedMessages.close();
for(NevadoMessageProducer producer : _producers)
{
producer.close();
}
for(NevadoMessageConsumer consumer : _consumers)
{
consumer.close();
}
_closed = true;
}
}
@Override
public void recover() throws JMSException
{
checkClosed();
if (_acknowledgeMode == CLIENT_ACKNOWLEDGE) {
_incomingStagedMessages.reset();
}
}
@Override
public MessageListener getMessageListener() throws JMSException
{
checkClosed();
return null; // TODO
}
@Override
public void setMessageListener(MessageListener messageListener) throws JMSException
{
checkClosed();
// TODO
}
@Override
public void run()
{
// TODO
}
@Override
public NevadoMessageProducer createProducer(Destination destination) throws JMSException
{
checkClosed();
NevadoMessageProducer producer = new NevadoMessageProducer(this, NevadoDestination.getInstance(destination));
_producers.add(producer);
return producer;
}
@Override
public NevadoMessageConsumer createConsumer(Destination destination) throws JMSException
{
return createConsumer(destination, null);
}
@Override
public NevadoMessageConsumer createConsumer(Destination destination, String selector) throws JMSException
{
return createConsumer(destination, selector, false);
}
@Override
public NevadoMessageConsumer createConsumer(Destination destination, String selector, boolean noLocal) throws JMSException
{
checkClosed();
checkValidDestination(destination);
NevadoMessageConsumer consumer = new NevadoMessageConsumer(this, NevadoDestination.getInstance(destination),
selector, noLocal);
_asyncConsumerRunner.addAsyncConsumer(consumer);
_consumers.add(consumer);
return consumer;
}
@Override
public QueueBrowser createBrowser(Queue queue) throws JMSException
{
throw new UnsupportedOperationException("QueueBrowsers are currently not supported");
}
@Override
public QueueBrowser createBrowser(Queue queue, String s) throws JMSException
{
throw new UnsupportedOperationException("QueueBrowsers are currently not supported");
}
@Override
public NevadoTemporaryQueue createTemporaryQueue() throws JMSException
{
checkClosed();
return _connection.createTemporaryQueue();
}
@Override
public NevadoQueue createQueue(String name) throws JMSException
{
if (!NevadoProviderQueuePrefix.isValidQueueName(name))
{
throw new InvalidDestinationException("Queue name is not valid: " + name);
}
return createInternalQueue(name);
}
protected NevadoQueue createInternalQueue(String name) throws JMSException
{
checkClosed();
return new NevadoQueue(name);
}
@Override
public NevadoTopic createTopic(String name) throws JMSException
{
checkClosed();
return new NevadoTopic(name);
}
@Override
public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException
{
checkClosed();
return createDurableSubscriber(topic, name, null, false);
}
@Override
public TopicSubscriber createDurableSubscriber(Topic topic, String name, String selector, boolean noLocal) throws JMSException
{
checkClosed();
checkValidDestination(topic);
String queueName = getDurableEndpointQueueName(name);
if (hasActiveDurableSubscriber(queueName))
{
throw new JMSException("There is already a durable subscriber with name " + name);
}
NevadoMessageConsumer consumer = new NevadoMessageConsumer(this, NevadoTopic.getInstance(topic), name, selector,
noLocal);
_asyncConsumerRunner.addAsyncConsumer(consumer);
_consumers.add(consumer);
return consumer;
}
@Override
public NevadoTemporaryTopic createTemporaryTopic() throws JMSException
{
checkClosed();
return _connection.createTemporaryTopic();
}
@Override
public void unsubscribe(String name) throws JMSException
{
checkClosed();
String queueName = getDurableEndpointQueueName(name);
if (hasActiveDurableSubscriber(queueName))
{
throw new JMSException("Cannot unsubscribe durable topic-subscription '"
+ name + "': There is an active TopicSubscriber");
}
// Check for unacknowledged/uncommitted messaages
for (NevadoMessage message : _incomingStagedMessages.getConsumedMessages())
{
if (message.getJMSDestination() instanceof NevadoTopic)
{
NevadoTopic topic = (NevadoTopic)message.getJMSDestination();
if (topic.isDurable() && queueName.equals(topic.getTopicEndpoint().getQueueName()))
{
throw new JMSException("Cannot unsubscribe durable topic-subscription '"
+ name + "': There is an unacknowledged or uncommitted message from the topic");
}
}
}
NevadoQueue durableQueue = new NevadoQueue(queueName);
_connection.deleteQueue(durableQueue);
}
protected String getDurableEndpointQueueName(String durableSubscriptionName) {
String queueName = NevadoProviderQueuePrefix.DURABLE_SUBSCRIPTION_PREFIX + durableSubscriptionName;
if (_connection.getClientID() != null)
{
queueName += "_client-" + _connection.getClientID() + "";
}
return queueName;
}
private boolean hasActiveDurableSubscriber(String queueName) throws JMSException {
for(NevadoMessageConsumer consumer : _consumers)
{
if (consumer.isClosed() || consumer.getDestination() == null
|| !(consumer.getDestination() instanceof NevadoTopic))
{
continue;
}
NevadoTopic topic = (NevadoTopic)consumer.getDestination();
if (topic.isDurable() && queueName.equals(topic.getTopicEndpoint().getQueueName()))
{
return true;
}
}
return false;
}
protected void sendMessage(NevadoDestination destination, NevadoMessage message) throws JMSException
{
if (destination == null)
{
throw new NullPointerException("Destination is null");
}
if (destination instanceof NevadoTopic)
{
message.setNevadoProperty(NevadoProperty.ConnectionID, _connection.getConnectionID());
}
if (_overrideJMSDeliveryMode != null) {
message.setJMSDeliveryMode(_overrideJMSDeliveryMode);
}
if (_overrideJMSPriority != null) {
message.setJMSPriority(_overrideJMSPriority);
}
if (_overrideJMSTTL != null) {
message.setJMSExpiration(_overrideJMSTTL > 0 ? System.currentTimeMillis() + _overrideJMSTTL : 0);
}
message.onSend();
if (_transacted)
{
if (!_outgoingTxMessages.containsKey(destination))
{
_outgoingTxMessages.put(destination, new ArrayList());
}
// We aren't calling SQS yet, so we'll need to create our own message ID
if (!message.isDisableMessageID())
{
message.setJMSMessageID(MessageIdUtil.createMessageId());
}
_outgoingTxMessages.get(destination).add(message.copyOf());
}
else
{
_connection.getSQSConnector().sendMessage(destination, message);
}
}
protected NevadoMessage receiveMessage(NevadoDestination destination, long timeoutMs, boolean noLocal)
throws JMSException, InterruptedException {
testBreak();
long startTime = System.currentTimeMillis();
NevadoMessage message = null;
boolean firstPass = true;
long elapsed = 0;
while(firstPass
|| (message == null
&& (timeoutMs < 0 || (elapsed = System.currentTimeMillis() - startTime) < timeoutMs)))
{
firstPass = false;
long adjustedTimeout = timeoutMs < 0 ? timeoutMs : (timeoutMs - elapsed);
message = getUnfilteredMessage(destination, adjustedTimeout);
// Filter expired messages
if (message != null && message.getJMSExpiration() > 0
&& System.currentTimeMillis() > message.getJMSExpiration())
{
message.expire();
_log.info("Skipped expired message (" + message.getJMSMessageID() + ")");
message = null;
}
// Filter noLocal matches
if (message != null && destination instanceof NevadoTopic && noLocal && _connection.getConnectionID()
.equals(message.getNevadoProperty(NevadoProperty.ConnectionID)))
{
deleteMessage(message);
message = null;
}
}
// Set session and destination
return message;
}
private NevadoMessage getUnfilteredMessage(NevadoDestination destination, long timeoutMs)
throws JMSException, InterruptedException {
NevadoMessage message = null;
// First check the holder in case there was a recover or rollback
if (_acknowledgeMode == CLIENT_ACKNOWLEDGE || _transacted)
{
message = _incomingStagedMessages.getNextMessage(destination);
}
// Else grab a message from the queue
if (message == null)
{
message = _connection.getSQSConnector().receiveMessage(_connection, destination, timeoutMs);
// Hold onto messages in a transaction or in CLIENT_ACKNOWLEDGE mode
if (message != null && (_acknowledgeMode == CLIENT_ACKNOWLEDGE || _transacted))
{
_incomingStagedMessages.add(destination, message);
}
}
// If we've got a message decorate it appropriately
if (message != null) {
message.setNevadoSession(this);
message.setNevadoDestination(destination);
if (message.propertyExists(JMSXProperty.JMSXDeliveryCount + "")) {
int redeliveryCount = (Integer)message.getJMSXProperty(JMSXProperty.JMSXDeliveryCount);
++redeliveryCount;
message.setJMSXProperty(JMSXProperty.JMSXDeliveryCount, redeliveryCount);
message.setJMSRedelivered(true);
}
else {
message.setJMSXProperty(JMSXProperty.JMSXDeliveryCount, 1);
}
}
return message;
}
public void acknowledgeMessage(NevadoMessage message) throws JMSException
{
checkClosed();
if (this != message.getNevadoSession())
{
throw new IllegalStateException("Session should only acknowledge its own messages");
}
if (!_transacted) {
if (_acknowledgeMode == CLIENT_ACKNOWLEDGE)
{
_incomingStagedMessages.acknowledgeConsumedMessages();
}
else
{
deleteMessage(message);
}
}
}
public void expireMessage(NevadoMessage message) throws JMSException
{
checkClosed();
if (this != message.getNevadoSession())
{
throw new IllegalStateException("Session should only expire its own messages");
}
deleteMessage(message);
}
protected void deleteMessage(NevadoMessage... messages) throws JMSException
{
for(NevadoMessage message : messages)
{
_connection.getSQSConnector().deleteMessage(message);
}
}
protected void resetMessage(NevadoMessage... messages)
{
for(NevadoMessage message : messages)
{
try {
_connection.getSQSConnector().resetMessage(message);
}
catch(Throwable t) {
// Not worth breaking over, since this is typically called in recovery or commit mode, and
// it will eventually reset on its own. Worst case is that messages will be delayed or mis-sequenced
// in certain cases where the specification does not define specific behavior.
_log.warn("Unable to reset message: " + message, t);
}
}
}
public void setOverrideJMSDeliveryMode(Integer jmsDeliveryMode)
{
_overrideJMSDeliveryMode = jmsDeliveryMode;
}
public void setOverrideJMSTTL(Long jmsTTL)
{
_overrideJMSTTL = jmsTTL;
}
public void setOverrideJMSPriority(Integer jmsPriority)
{
_overrideJMSPriority = jmsPriority;
}
public boolean isClosed() {
return _closed;
}
protected NevadoConnection getConnection()
{
return _connection;
}
protected synchronized void start()
{
_asyncConsumerRunner.start();
}
protected synchronized void stop()
{
try {
_asyncConsumerRunner.stop();
} catch (InterruptedException e) {
String exMessage = "Session threads may not have closed yet: " + e.getMessage();
_log.warn(exMessage, e);
Thread.currentThread().interrupt();
}
}
protected void checkClosed() throws IllegalStateException
{
if (_closed)
{
throw new IllegalStateException("This session has been closed");
}
}
private void checkValidDestination(Destination destination) throws JMSException
{
if (destination instanceof TemporaryQueue || destination instanceof TemporaryTopic)
{
if (!_connection.ownsTemporaryDestination(destination))
{
throw new InvalidDestinationException("Consumers for temporary destinations cannot be created " +
"outside of the connection where the destination was created.");
}
}
}
protected void setBreakSessionForTesting(boolean value)
{
_TESTING_ONLY_break = value;
}
private void testBreak() throws JMSException {
if (_TESTING_ONLY_break)
{
throw new JMSException("SESSION DELIBERATELY THROWING EXCEPTION - EXPECTED BEHAVIOR - FOR TESTING MODE ONLY");
}
}
}