/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License 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 org.apache.qpid.client;
import org.apache.log4j.Logger;
import org.apache.qpid.AMQException;
import org.apache.qpid.AMQUndeliveredException;
import org.apache.qpid.client.failover.FailoverSupport;
import org.apache.qpid.client.message.AbstractJMSMessage;
import org.apache.qpid.client.message.MessageFactoryRegistry;
import org.apache.qpid.client.message.UnprocessedMessage;
import org.apache.qpid.client.protocol.AMQProtocolHandler;
import org.apache.qpid.client.util.FlowControllingBlockingQueue;
import org.apache.qpid.framing.*;
import org.apache.qpid.jms.Session;
import org.apache.qpid.protocol.AMQConstant;
import org.apache.qpid.url.AMQBindingURL;
import org.apache.qpid.url.URLSyntaxException;
import javax.jms.*;
import javax.jms.IllegalStateException;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
public class AMQSession extends Closeable implements Session, QueueSession, TopicSession
{
private static final Logger _logger = Logger.getLogger(AMQSession.class);
public static final int DEFAULT_PREFETCH_HIGH_MARK = 5000;
public static final int DEFAULT_PREFETCH_LOW_MARK = 2500;
private AMQConnection _connection;
private boolean _transacted;
private int _acknowledgeMode;
private int _channelId;
private int _defaultPrefetchHighMark = DEFAULT_PREFETCH_HIGH_MARK;
private int _defaultPrefetchLowMark = DEFAULT_PREFETCH_LOW_MARK;
/**
* Used in the consume method. We generate the consume tag on the client so that we can use the nowait
* feature.
*/
private int _nextTag = 1;
/**
* This queue is bounded and is used to store messages before being dispatched to the consumer
*/
private final FlowControllingBlockingQueue _queue;
private Dispatcher _dispatcher;
private MessageFactoryRegistry _messageFactoryRegistry;
/**
* Set of all producers created by this session
*/
private Map _producers = new ConcurrentHashMap();
/**
* Maps from consumer tag (String) to JMSMessageConsumer instance
*/
private Map _consumers = new ConcurrentHashMap();
/**
* Default value for immediate flag used by producers created by this session is false, i.e. a consumer does not
* need to be attached to a queue
*/
protected static final boolean DEFAULT_IMMEDIATE = false;
/**
* Default value for mandatory flag used by producers created by this sessio is true, i.e. server will not silently
* drop messages where no queue is connected to the exchange for the message
*/
protected static final boolean DEFAULT_MANDATORY = true;
/**
* The counter of the next producer id. This id is generated by the session and used only to allow the
* producer to identify itself to the session when deregistering itself.
* <p/>
* Access to this id does not require to be synchronized since according to the JMS specification only one
* thread of control is allowed to create producers for any given session instance.
*/
private long _nextProducerId;
/**
* Track the 'stopped' state of the dispatcher, a session starts in the stopped state.
*/
private volatile AtomicBoolean _stopped = new AtomicBoolean(true);
/**
* Responsible for decoding a message fragment and passing it to the appropriate message consumer.
*/
private class Dispatcher extends Thread
{
public Dispatcher()
{
super("Dispatcher-Channel-" + _channelId);
}
public void run()
{
UnprocessedMessage message;
_stopped.set(false);
try
{
while (!_stopped.get() && (message = (UnprocessedMessage) _queue.take()) != null)
{
dispatchMessage(message);
}
}
catch (InterruptedException e)
{
;
}
_logger.info("Dispatcher thread terminating for channel " + _channelId);
}
private void dispatchMessage(UnprocessedMessage message)
{
if (message.deliverBody != null)
{
final BasicMessageConsumer consumer = (BasicMessageConsumer) _consumers.get(message.deliverBody.consumerTag);
if (consumer == null)
{
_logger.warn("Received a message from queue " + message.deliverBody.consumerTag + " without a handler - ignoring...");
}
else
{
consumer.notifyMessage(message, _channelId);
}
}
else
{
try
{
// Bounced message is processed here, away from the mina thread
AbstractJMSMessage bouncedMessage = _messageFactoryRegistry.createMessage(0,
false,
message.contentHeader,
message.bodies);
int errorCode = message.bounceBody.replyCode;
String reason = message.bounceBody.replyText;
_logger.debug("Message returned with error code " + errorCode + " (" + reason + ")");
//@TODO should this be moved to an exception handler of sorts. Somewhere errors are converted to correct execeptions.
if (errorCode == AMQConstant.NO_CONSUMERS.getCode())
{
_connection.exceptionReceived(new AMQNoConsumersException("Error: " + reason, bouncedMessage));
}
else
{
if (errorCode == AMQConstant.NO_ROUTE.getCode())
{
_connection.exceptionReceived(new AMQNoRouteException("Error: " + reason, bouncedMessage));
}
else
{
_connection.exceptionReceived(new AMQUndeliveredException(errorCode, "Error: " + reason, bouncedMessage));
}
}
}
catch (Exception e)
{
_logger.error("Caught exception trying to raise undelivered message exception (dump follows) - ignoring...", e);
}
}
}
public void stopDispatcher()
{
_stopped.set(true);
interrupt();
}
}
AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode,
MessageFactoryRegistry messageFactoryRegistry)
{
this(con, channelId, transacted, acknowledgeMode, messageFactoryRegistry, DEFAULT_PREFETCH_HIGH_MARK, DEFAULT_PREFETCH_LOW_MARK);
}
AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode,
MessageFactoryRegistry messageFactoryRegistry, int defaultPrefetch)
{
this(con, channelId, transacted, acknowledgeMode, messageFactoryRegistry, defaultPrefetch, defaultPrefetch);
}
AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode,
MessageFactoryRegistry messageFactoryRegistry, int defaultPrefetchHighMark, int defaultPrefetchLowMark)
{
_connection = con;
_transacted = transacted;
if (transacted)
{
_acknowledgeMode = javax.jms.Session.SESSION_TRANSACTED;
}
else
{
_acknowledgeMode = acknowledgeMode;
}
_channelId = channelId;
_messageFactoryRegistry = messageFactoryRegistry;
_defaultPrefetchHighMark = defaultPrefetchHighMark;
_defaultPrefetchLowMark = defaultPrefetchLowMark;
if (_acknowledgeMode == NO_ACKNOWLEDGE)
{
_queue = new FlowControllingBlockingQueue(_defaultPrefetchHighMark, _defaultPrefetchLowMark,
new FlowControllingBlockingQueue.ThresholdListener()
{
public void aboveThreshold(int currentValue)
{
if (_acknowledgeMode == NO_ACKNOWLEDGE)
{
_logger.warn("Above threshold(" + _defaultPrefetchHighMark + ") so suspending channel. Current value is " + currentValue);
suspendChannel();
}
}
public void underThreshold(int currentValue)
{
if (_acknowledgeMode == NO_ACKNOWLEDGE)
{
_logger.warn("Below threshold(" + _defaultPrefetchLowMark + ") so unsuspending channel. Current value is " + currentValue);
unsuspendChannel();
}
}
});
}
else
{
_queue = new FlowControllingBlockingQueue(_defaultPrefetchHighMark, null);
}
}
AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode)
{
this(con, channelId, transacted, acknowledgeMode, MessageFactoryRegistry.newDefaultRegistry());
}
AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode, int defaultPrefetch)
{
this(con, channelId, transacted, acknowledgeMode, MessageFactoryRegistry.newDefaultRegistry(), defaultPrefetch);
}
AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode, int defaultPrefetchHigh, int defaultPrefetchLow)
{
this(con, channelId, transacted, acknowledgeMode, MessageFactoryRegistry.newDefaultRegistry(), defaultPrefetchHigh, defaultPrefetchLow);
}
AMQConnection getAMQConnection()
{
return _connection;
}
public BytesMessage createBytesMessage() throws JMSException
{
synchronized(_connection.getFailoverMutex())
{
checkNotClosed();
try
{
return (BytesMessage) _messageFactoryRegistry.createMessage("application/octet-stream");
}
catch (AMQException e)
{
throw new JMSException("Unable to create message: " + e);
}
}
}
public MapMessage createMapMessage() throws JMSException
{
synchronized(_connection.getFailoverMutex())
{
checkNotClosed();
try
{
return (MapMessage) _messageFactoryRegistry.createMessage("jms/map-message");
}
catch (AMQException e)
{
throw new JMSException("Unable to create message: " + e);
}
}
}
public javax.jms.Message createMessage() throws JMSException
{
synchronized(_connection.getFailoverMutex())
{
checkNotClosed();
try
{
return (BytesMessage) _messageFactoryRegistry.createMessage("application/octet-stream");
}
catch (AMQException e)
{
throw new JMSException("Unable to create message: " + e);
}
}
}
public ObjectMessage createObjectMessage() throws JMSException
{
synchronized(_connection.getFailoverMutex())
{
checkNotClosed();
try
{
return (ObjectMessage) _messageFactoryRegistry.createMessage("application/java-object-stream");
}
catch (AMQException e)
{
throw new JMSException("Unable to create message: " + e);
}
}
}
public ObjectMessage createObjectMessage(Serializable object) throws JMSException
{
synchronized(_connection.getFailoverMutex())
{
checkNotClosed();
try
{
ObjectMessage msg = (ObjectMessage) _messageFactoryRegistry.createMessage("application/java-object-stream");
msg.setObject(object);
return msg;
}
catch (AMQException e)
{
throw new JMSException("Unable to create message: " + e);
}
}
}
public StreamMessage createStreamMessage() throws JMSException
{
checkNotClosed();
throw new UnsupportedOperationException("Stream messages not supported");
}
public TextMessage createTextMessage() throws JMSException
{
synchronized(_connection.getFailoverMutex())
{
checkNotClosed();
try
{
return (TextMessage) _messageFactoryRegistry.createMessage("text/plain");
}
catch (AMQException e)
{
throw new JMSException("Unable to create text message: " + e);
}
}
}
public TextMessage createTextMessage(String text) throws JMSException
{
synchronized(_connection.getFailoverMutex())
{
checkNotClosed();
try
{
TextMessage msg = (TextMessage) _messageFactoryRegistry.createMessage("text/plain");
msg.setText(text);
return msg;
}
catch (AMQException e)
{
throw new JMSException("Unable to create text message: " + e);
}
}
}
public boolean getTransacted() throws JMSException
{
checkNotClosed();
return _transacted;
}
public int getAcknowledgeMode() throws JMSException
{
checkNotClosed();
return _acknowledgeMode;
}
public void commit() throws JMSException
{
checkTransacted();
try
{
// Acknowledge up to message last delivered (if any) for each consumer.
//need to send ack for messages delivered to consumers so far
for (Iterator i = _consumers.values().iterator(); i.hasNext();)
{
//Sends acknowledgement to server
((BasicMessageConsumer) i.next()).acknowledgeLastDelivered();
}
// Commits outstanding messages sent and outstanding acknowledgements.
_connection.getProtocolHandler().syncWrite(TxCommitBody.createAMQFrame(_channelId), TxCommitOkBody.class);
}
catch (AMQException e)
{
JMSException exception = new JMSException("Failed to commit: " + e.getMessage());
exception.setLinkedException(e);
throw exception;
}
}
public void rollback() throws JMSException
{
checkTransacted();
try
{
_connection.getProtocolHandler().syncWrite(
TxRollbackBody.createAMQFrame(_channelId), TxRollbackOkBody.class);
}
catch (AMQException e)
{
throw(JMSException) (new JMSException("Failed to rollback: " + e).initCause(e));
}
}
public void close() throws JMSException
{
// We must close down all producers and consumers in an orderly fashion. This is the only method
// that can be called from a different thread of control from the one controlling the session
synchronized(_connection.getFailoverMutex())
{
_closed.set(true);
// we pass null since this is not an error case
closeProducersAndConsumers(null);
try
{
_connection.getProtocolHandler().closeSession(this);
final AMQFrame frame = ChannelCloseBody.createAMQFrame(
getChannelId(), AMQConstant.REPLY_SUCCESS.getCode(), "JMS client closing channel", 0, 0);
_connection.getProtocolHandler().syncWrite(frame, ChannelCloseOkBody.class);
// When control resumes at this point, a reply will have been received that
// indicates the broker has closed the channel successfully
}
catch (AMQException e)
{
throw new JMSException("Error closing session: " + e);
}
finally
{
_connection.deregisterSession(_channelId);
}
}
}
/**
* Close all producers or consumers. This is called either in the error case or when closing the session normally.
*
* @param amqe the exception, may be null to indicate no error has occurred
*/
private void closeProducersAndConsumers(AMQException amqe)
{
try
{
closeProducers();
}
catch (JMSException e)
{
_logger.error("Error closing session: " + e, e);
}
try
{
closeConsumers(amqe);
}
catch (JMSException e)
{
_logger.error("Error closing session: " + e, e);
}
}
/**
* Called when the server initiates the closure of the session
* unilaterally.
*
* @param e the exception that caused this session to be closed. Null causes the
*/
public void closed(Throwable e)
{
synchronized(_connection.getFailoverMutex())
{
// An AMQException has an error code and message already and will be passed in when closure occurs as a
// result of a channel close request
_closed.set(true);
AMQException amqe;
if (e instanceof AMQException)
{
amqe = (AMQException) e;
}
else
{
amqe = new AMQException(_logger, "Closing session forcibly", e);
}
_connection.deregisterSession(_channelId);
closeProducersAndConsumers(amqe);
}
}
/**
* Called to mark the session as being closed. Useful when the session needs to be made invalid, e.g. after
* failover when the client has veoted resubscription.
* <p/>
* The caller of this method must already hold the failover mutex.
*/
void markClosed()
{
_closed.set(true);
_connection.deregisterSession(_channelId);
markClosedProducersAndConsumers();
}
private void markClosedProducersAndConsumers()
{
try
{
// no need for a markClosed* method in this case since there is no protocol traffic closing a producer
closeProducers();
}
catch (JMSException e)
{
_logger.error("Error closing session: " + e, e);
}
try
{
markClosedConsumers();
}
catch (JMSException e)
{
_logger.error("Error closing session: " + e, e);
}
}
/**
* Called to close message producers cleanly. This may or may <b>not</b> be as a result of an error. There is
* currently no way of propagating errors to message producers (this is a JMS limitation).
*/
private void closeProducers() throws JMSException
{
// we need to clone the list of producers since the close() method updates the _producers collection
// which would result in a concurrent modification exception
final ArrayList clonedProducers = new ArrayList(_producers.values());
final Iterator it = clonedProducers.iterator();
while (it.hasNext())
{
final BasicMessageProducer prod = (BasicMessageProducer) it.next();
prod.close();
}
// at this point the _producers map is empty
}
/**
* Called to close message consumers cleanly. This may or may <b>not</b> be as a result of an error.
*
* @param error not null if this is a result of an error occurring at the connection level
*/
private void closeConsumers(Throwable error) throws JMSException
{
if (_dispatcher != null)
{
_dispatcher.stopDispatcher();
}
// we need to clone the list of consumers since the close() method updates the _consumers collection
// which would result in a concurrent modification exception
final ArrayList clonedConsumers = new ArrayList(_consumers.values());
final Iterator it = clonedConsumers.iterator();
while (it.hasNext())
{
final BasicMessageConsumer con = (BasicMessageConsumer) it.next();
if (error != null)
{
con.notifyError(error);
}
else
{
con.close();
}
}
// at this point the _consumers map will be empty
}
private void markClosedConsumers() throws JMSException
{
if (_dispatcher != null)
{
_dispatcher.stopDispatcher();
}
// we need to clone the list of consumers since the close() method updates the _consumers collection
// which would result in a concurrent modification exception
final ArrayList clonedConsumers = new ArrayList(_consumers.values());
final Iterator it = clonedConsumers.iterator();
while (it.hasNext())
{
final BasicMessageConsumer con = (BasicMessageConsumer) it.next();
con.markClosed();
}
// at this point the _consumers map will be empty
}
/**
* Asks the broker to resend all unacknowledged messages for the session.
*
* @throws JMSException
*/
public void recover() throws JMSException
{
checkNotClosed();
checkNotTransacted(); // throws IllegalStateException if a transacted session
_connection.getProtocolHandler().writeFrame(BasicRecoverBody.createAMQFrame(_channelId, false));
}
public MessageListener getMessageListener() throws JMSException
{
checkNotClosed();
throw new java.lang.UnsupportedOperationException("MessageListener interface not supported");
}
public void setMessageListener(MessageListener listener) throws JMSException
{
checkNotClosed();
throw new java.lang.UnsupportedOperationException("MessageListener interface not supported");
}
public void run()
{
throw new java.lang.UnsupportedOperationException();
}
public MessageProducer createProducer(Destination destination, boolean mandatory,
boolean immediate, boolean waitUntilSent)
throws JMSException
{
return createProducerImpl(destination, mandatory, immediate, waitUntilSent);
}
public MessageProducer createProducer(Destination destination, boolean mandatory, boolean immediate)
throws JMSException
{
return createProducerImpl(destination, mandatory, immediate);
}
public MessageProducer createProducer(Destination destination, boolean immediate)
throws JMSException
{
return createProducerImpl(destination, DEFAULT_MANDATORY, immediate);
}
public MessageProducer createProducer(Destination destination) throws JMSException
{
return createProducerImpl(destination, DEFAULT_MANDATORY, DEFAULT_IMMEDIATE);
}
private org.apache.qpid.jms.MessageProducer createProducerImpl(Destination destination, boolean mandatory,
boolean immediate)
throws JMSException
{
return createProducerImpl(destination, mandatory, immediate, false);
}
private org.apache.qpid.jms.MessageProducer createProducerImpl(final Destination destination, final boolean mandatory,
final boolean immediate, final boolean waitUntilSent)
throws JMSException
{
return (org.apache.qpid.jms.MessageProducer) new FailoverSupport()
{
public Object operation() throws JMSException
{
checkNotClosed();
return new BasicMessageProducer(_connection, (AMQDestination) destination, _transacted, _channelId,
AMQSession.this, _connection.getProtocolHandler(),
getNextProducerId(), immediate, mandatory, waitUntilSent);
}
}.execute(_connection);
}
/**
* Creates a QueueReceiver
* @param destination
* @return QueueReceiver - a wrapper around our MessageConsumer
* @throws JMSException
*/
public QueueReceiver createQueueReceiver(Destination destination) throws JMSException
{
AMQQueue dest = (AMQQueue) destination;
BasicMessageConsumer consumer = (BasicMessageConsumer) createConsumer(destination);
return new QueueReceiverAdaptor(dest, consumer);
}
/**
* Creates a QueueReceiver using a message selector
* @param destination
* @param messageSelector
* @return QueueReceiver - a wrapper around our MessageConsumer
* @throws JMSException
*/
public QueueReceiver createQueueReceiver(Destination destination, String messageSelector) throws JMSException
{
AMQQueue dest = (AMQQueue) destination;
BasicMessageConsumer consumer = (BasicMessageConsumer)
createConsumer(destination, messageSelector);
return new QueueReceiverAdaptor(dest, consumer);
}
public MessageConsumer createConsumer(Destination destination) throws JMSException
{
return createConsumer(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, false, false, null);
}
public MessageConsumer createConsumer(Destination destination, String messageSelector) throws JMSException
{
return createConsumer(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, false, false, messageSelector);
}
public MessageConsumer createConsumer(Destination destination, String messageSelector, boolean noLocal)
throws JMSException
{
return createConsumer(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, noLocal, false, messageSelector);
}
public MessageConsumer createConsumer(Destination destination,
int prefetch,
boolean noLocal,
boolean exclusive,
String selector) throws JMSException
{
return createConsumer(destination, prefetch, prefetch, noLocal, exclusive, selector, null);
}
public MessageConsumer createConsumer(Destination destination,
int prefetchHigh,
int prefetchLow,
boolean noLocal,
boolean exclusive,
String selector) throws JMSException
{
return createConsumer(destination, prefetchHigh, prefetchLow, noLocal, exclusive, selector, null);
}
public MessageConsumer createConsumer(Destination destination,
int prefetch,
boolean noLocal,
boolean exclusive,
String selector,
FieldTable rawSelector) throws JMSException
{
return createConsumerImpl(destination, prefetch, prefetch, noLocal, exclusive,
selector, rawSelector);
}
public MessageConsumer createConsumer(Destination destination,
int prefetchHigh,
int prefetchLow,
boolean noLocal,
boolean exclusive,
String selector,
FieldTable rawSelector) throws JMSException
{
return createConsumerImpl(destination, prefetchHigh, prefetchLow, noLocal, exclusive,
selector, rawSelector);
}
protected MessageConsumer createConsumerImpl(final Destination destination,
final int prefetchHigh,
final int prefetchLow,
final boolean noLocal,
final boolean exclusive,
final String selector,
final FieldTable rawSelector) throws JMSException
{
return (org.apache.qpid.jms.MessageConsumer) new FailoverSupport()
{
public Object operation() throws JMSException
{
checkNotClosed();
AMQDestination amqd = (AMQDestination) destination;
final AMQProtocolHandler protocolHandler = _connection.getProtocolHandler();
// TODO: construct the rawSelector from the selector string if rawSelector == null
final FieldTable ft = new FieldTable();
//if (rawSelector != null)
// ft.put("headers", rawSelector.getDataAsBytes());
if (rawSelector != null)
{
ft.putAll(rawSelector);
}
BasicMessageConsumer consumer = new BasicMessageConsumer(_channelId, _connection, amqd, selector, noLocal,
_messageFactoryRegistry, AMQSession.this,
protocolHandler, ft, prefetchHigh, prefetchLow, exclusive,
_acknowledgeMode);
try
{
registerConsumer(consumer);
}
catch (AMQException e)
{
JMSException ex = new JMSException("Error registering consumer: " + e);
ex.setLinkedException(e);
throw ex;
}
return consumer;
}
}.execute(_connection);
}
public void declareExchange(String name, String type)
{
declareExchange(name, type, _connection.getProtocolHandler());
}
public void declareExchangeSynch(String name, String type) throws AMQException
{
AMQFrame frame = ExchangeDeclareBody.createAMQFrame(_channelId, 0, name, type, false, false, false, false, false, null);
_connection.getProtocolHandler().syncWrite(frame, ExchangeDeclareOkBody.class);
}
private void declareExchange(AMQDestination amqd, AMQProtocolHandler protocolHandler)
{
declareExchange(amqd.getExchangeName(), amqd.getExchangeClass(), protocolHandler);
}
private void declareExchange(String name, String type, AMQProtocolHandler protocolHandler)
{
AMQFrame exchangeDeclare = ExchangeDeclareBody.createAMQFrame(_channelId, 0, name, type, false, false, false, false, true, null);
protocolHandler.writeFrame(exchangeDeclare);
}
/**
* Declare the queue.
*
* @param amqd
* @param protocolHandler
* @return the queue name. This is useful where the broker is generating a queue name on behalf of the client.
* @throws AMQException
*/
private String declareQueue(AMQDestination amqd, AMQProtocolHandler protocolHandler) throws AMQException
{
// For queues (but not topics) we generate the name in the client rather than the
// server. This allows the name to be reused on failover if required. In general,
// the destination indicates whether it wants a name generated or not.
if (amqd.isNameRequired())
{
amqd.setQueueName(protocolHandler.generateQueueName());
}
AMQFrame queueDeclare = QueueDeclareBody.createAMQFrame(_channelId, 0, amqd.getQueueName(),
false, amqd.isDurable(), amqd.isExclusive(),
amqd.isAutoDelete(), true, null);
protocolHandler.writeFrame(queueDeclare);
return amqd.getQueueName();
}
private void bindQueue(AMQDestination amqd, String queueName, AMQProtocolHandler protocolHandler, FieldTable ft) throws AMQException
{
AMQFrame queueBind = QueueBindBody.createAMQFrame(_channelId, 0,
queueName, amqd.getExchangeName(),
amqd.getRoutingKey(), true, ft);
protocolHandler.writeFrame(queueBind);
}
/**
* Register to consume from the queue.
*
* @param queueName
* @return the consumer tag generated by the broker
*/
private String consumeFromQueue(String queueName, AMQProtocolHandler protocolHandler, int prefetchHigh, int prefetchLow,
boolean noLocal, boolean exclusive, int acknowledgeMode) throws AMQException
{
//fixme prefetch values are not used here. Do we need to have them as parametsrs?
//need to generate a consumer tag on the client so we can exploit the nowait flag
String tag = Integer.toString(_nextTag++);
AMQFrame jmsConsume = BasicConsumeBody.createAMQFrame(_channelId, 0,
queueName, tag, noLocal,
acknowledgeMode == Session.NO_ACKNOWLEDGE,
exclusive, true);
protocolHandler.writeFrame(jmsConsume);
return tag;
}
public Queue createQueue(String queueName) throws JMSException
{
if (queueName.indexOf('/') == -1)
{
return new AMQQueue(queueName);
}
else
{
try
{
return new AMQQueue(new AMQBindingURL(queueName));
}
catch (URLSyntaxException urlse)
{
JMSException jmse = new JMSException(urlse.getReason());
jmse.setLinkedException(urlse);
throw jmse;
}
}
}
/**
* Creates a QueueReceiver wrapping a MessageConsumer
* @param queue
* @return QueueReceiver
* @throws JMSException
*/
public QueueReceiver createReceiver(Queue queue) throws JMSException
{
AMQQueue dest = (AMQQueue) queue;
BasicMessageConsumer consumer = (BasicMessageConsumer) createConsumer(dest);
return new QueueReceiverAdaptor(dest, consumer);
}
/**
* Creates a QueueReceiver wrapping a MessageConsumer using a message selector
* @param queue
* @param messageSelector
* @return QueueReceiver
* @throws JMSException
*/
public QueueReceiver createReceiver(Queue queue, String messageSelector) throws JMSException
{
AMQQueue dest = (AMQQueue) queue;
BasicMessageConsumer consumer = (BasicMessageConsumer)
createConsumer(dest, messageSelector);
return new QueueReceiverAdaptor(dest, consumer);
}
public QueueSender createSender(Queue queue) throws JMSException
{
return (QueueSender) createProducer(queue);
}
public Topic createTopic(String topicName) throws JMSException
{
if (topicName.indexOf('/') == -1)
{
return new AMQTopic(topicName);
}
else
{
try
{
return new AMQTopic(new AMQBindingURL(topicName));
}
catch (URLSyntaxException urlse)
{
JMSException jmse = new JMSException(urlse.getReason());
jmse.setLinkedException(urlse);
throw jmse;
}
}
}
/**
* Creates a non-durable subscriber
* @param topic
* @return TopicSubscriber - a wrapper round our MessageConsumer
* @throws JMSException
*/
public TopicSubscriber createSubscriber(Topic topic) throws JMSException
{
AMQTopic dest = new AMQTopic(topic.getTopicName());
return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest));
}
/**
* Creates a non-durable subscriber with a message selector
* @param topic
* @param messageSelector
* @param noLocal
* @return TopicSubscriber - a wrapper round our MessageConsumer
* @throws JMSException
*/
public TopicSubscriber createSubscriber(Topic topic, String messageSelector, boolean noLocal) throws JMSException
{
AMQTopic dest = new AMQTopic(topic.getTopicName());
return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest, messageSelector, noLocal));
}
/**
* Note, currently this does not handle reuse of the same name with different topics correctly.
* If a name is reused in creating a new subscriber with a different topic/selecto or no-local
* flag then the subcriber will receive messages matching the old subscription AND the new one.
* The spec states that the new one should replace the old one.
* TODO: fix it.
*/
public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException
{
AMQTopic dest = new AMQTopic((AMQTopic) topic, _connection.getClientID(), name);
return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest));
}
/**
* Note, currently this does not handle reuse of the same name with different topics correctly.
*/
public TopicSubscriber createDurableSubscriber(Topic topic, String name, String messageSelector, boolean noLocal)
throws JMSException
{
AMQTopic dest = new AMQTopic((AMQTopic) topic, _connection.getClientID(), name);
BasicMessageConsumer consumer = (BasicMessageConsumer) createConsumer(dest, messageSelector, noLocal);
return new TopicSubscriberAdaptor(dest, consumer);
}
public TopicPublisher createPublisher(Topic topic) throws JMSException
{
return (TopicPublisher) createProducer(topic);
}
public QueueBrowser createBrowser(Queue queue) throws JMSException
{
throw new UnsupportedOperationException("Queue browsing not supported");
}
public QueueBrowser createBrowser(Queue queue, String messageSelector) throws JMSException
{
throw new UnsupportedOperationException("Queue browsing not supported");
}
public TemporaryQueue createTemporaryQueue() throws JMSException
{
return new AMQTemporaryQueue();
}
public TemporaryTopic createTemporaryTopic() throws JMSException
{
return new AMQTemporaryTopic();
}
public void unsubscribe(String name) throws JMSException
{
//send a queue.delete for the subscription
String queue = _connection.getClientID() + ":" + name;
AMQFrame frame = QueueDeleteBody.createAMQFrame(_channelId, 0, queue, false, false, true);
_connection.getProtocolHandler().writeFrame(frame);
}
private void checkTransacted() throws JMSException
{
if (!getTransacted())
{
throw new IllegalStateException("Session is not transacted");
}
}
private void checkNotTransacted() throws JMSException
{
if (getTransacted())
{
throw new IllegalStateException("Session is transacted");
}
}
/**
* Invoked by the MINA IO thread (indirectly) when a message is received from the transport.
* Puts the message onto the queue read by the dispatcher.
*
* @param message the message that has been received
*/
public void messageReceived(UnprocessedMessage message)
{
if (_logger.isDebugEnabled())
{
_logger.debug("Message received in session with channel id " + _channelId);
}
_queue.add(message);
}
/**
* Acknowledge a message or several messages. This method can be called via AbstractJMSMessage or from
* a BasicConsumer. The former where the mode is CLIENT_ACK and the latter where the mode is
* AUTO_ACK or similar.
*
* @param deliveryTag the tag of the last message to be acknowledged
* @param multiple if true will acknowledge all messages up to and including the one specified by the
* delivery tag
*/
public void acknowledgeMessage(long deliveryTag, boolean multiple)
{
final AMQFrame ackFrame = BasicAckBody.createAMQFrame(_channelId, deliveryTag, multiple);
if (_logger.isDebugEnabled())
{
_logger.debug("Sending ack for delivery tag " + deliveryTag + " on channel " + _channelId);
}
_connection.getProtocolHandler().writeFrame(ackFrame);
}
public int getDefaultPrefetch()
{
return _defaultPrefetchHighMark;
}
public int getDefaultPrefetchHigh()
{
return _defaultPrefetchHighMark;
}
public int getDefaultPrefetchLow()
{
return _defaultPrefetchLowMark;
}
public int getChannelId()
{
return _channelId;
}
void start()
{
if (_dispatcher != null)
{
//then we stopped this and are restarting, so signal server to resume delivery
unsuspendChannel();
}
_dispatcher = new Dispatcher();
_dispatcher.setDaemon(true);
_dispatcher.start();
}
void stop()
{
//stop the server delivering messages to this session
suspendChannel();
//stop the dispatcher thread
_stopped.set(true);
}
boolean isStopped()
{
return _stopped.get();
}
/**
* Callers must hold the failover mutex before calling this method.
*
* @param consumer
* @throws AMQException
*/
void registerConsumer(BasicMessageConsumer consumer) throws AMQException
{
AMQDestination amqd = consumer.getDestination();
AMQProtocolHandler protocolHandler = _connection.getProtocolHandler();
declareExchange(amqd, protocolHandler);
String queueName = declareQueue(amqd, protocolHandler);
bindQueue(amqd, queueName, protocolHandler, consumer.getRawSelectorFieldTable());
String consumerTag = consumeFromQueue(queueName, protocolHandler, consumer.getPrefetchHigh(), consumer.getPrefetchLow(),
consumer.isNoLocal(), consumer.isExclusive(), consumer.getAcknowledgeMode());
consumer.setConsumerTag(consumerTag);
_consumers.put(consumerTag, consumer);
}
/**
* Called by the MessageConsumer when closing, to deregister the consumer from the
* map from consumerTag to consumer instance.
*
* @param consumerTag the consumer tag, that was broker-generated
*/
void deregisterConsumer(String consumerTag)
{
_consumers.remove(consumerTag);
}
private void registerProducer(long producerId, MessageProducer producer)
{
_producers.put(new Long(producerId), producer);
}
void deregisterProducer(long producerId)
{
_producers.remove(new Long(producerId));
}
private long getNextProducerId()
{
return ++_nextProducerId;
}
/**
* Resubscribes all producers and consumers. This is called when performing failover.
*
* @throws AMQException
*/
void resubscribe() throws AMQException
{
resubscribeProducers();
resubscribeConsumers();
}
private void resubscribeProducers() throws AMQException
{
ArrayList producers = new ArrayList(_producers.values());
_logger.info(MessageFormat.format("Resubscribing producers = {0} producers.size={1}", producers, producers.size())); // FIXME: remove
for (Iterator it = producers.iterator(); it.hasNext();)
{
BasicMessageProducer producer = (BasicMessageProducer) it.next();
producer.resubscribe();
}
}
private void resubscribeConsumers() throws AMQException
{
ArrayList consumers = new ArrayList(_consumers.values());
_consumers.clear();
for (Iterator it = consumers.iterator(); it.hasNext();)
{
BasicMessageConsumer consumer = (BasicMessageConsumer) it.next();
registerConsumer(consumer);
}
}
private void suspendChannel()
{
_logger.warn("Suspending channel");
AMQFrame channelFlowFrame = ChannelFlowBody.createAMQFrame(_channelId, false);
_connection.getProtocolHandler().writeFrame(channelFlowFrame);
}
private void unsuspendChannel()
{
_logger.warn("Unsuspending channel");
AMQFrame channelFlowFrame = ChannelFlowBody.createAMQFrame(_channelId, true);
_connection.getProtocolHandler().writeFrame(channelFlowFrame);
}
}