summaryrefslogtreecommitdiff
path: root/java/broker/src/main
diff options
context:
space:
mode:
authorStephen Vinoski <vinoski@apache.org>2006-11-18 02:12:32 +0000
committerStephen Vinoski <vinoski@apache.org>2006-11-18 02:12:32 +0000
commit1db5a8a2329ec064d1683294ee1a3d8d233de42d (patch)
treef5ac42c441d4829262d560696cb2cad98fc0434f /java/broker/src/main
parentd386f860a3404ec9735dab2730f8ed683446838c (diff)
downloadqpid-python-1db5a8a2329ec064d1683294ee1a3d8d233de42d.tar.gz
directory moves required for maven merge
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid@476414 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'java/broker/src/main')
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java788
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java25
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/Main.java627
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java67
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java112
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java132
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessage.java51
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java30
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java84
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java105
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java220
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java139
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java66
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java95
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/DestNameExchange.java223
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java226
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java50
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java31
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java31
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java41
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java145
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java271
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java88
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java93
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java39
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java42
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java55
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java61
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java93
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java80
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java49
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java57
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java61
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java54
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java64
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java61
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java68
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java66
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java71
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java118
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java130
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java57
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java82
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java65
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java34
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java97
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java127
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java87
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java61
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java62
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java53
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/jms/JmsConsumer.java110
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java97
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java171
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java52
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java42
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java40
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java39
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java388
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java43
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java38
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/Managable.java34
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java98
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java58
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java42
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/ManagementConfiguration.java30
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java48
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodEvent.java65
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodListener.java55
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java700
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java230
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java53
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java125
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java41
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/HeartbeatConfig.java67
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java141
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java368
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java867
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/AsyncDeliveryConfig.java56
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/ConcurrentDeliveryManager.java348
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java50
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java76
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java112
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java39
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java220
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java35
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java57
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java33
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/Subscription.java32
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.java40
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java191
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.java31
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java183
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/SynchronizedDeliveryManager.java255
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java26
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java200
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java158
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java68
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationManager.java33
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java66
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java43
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java38
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/JCAProvider.java46
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/NullAuthenticationManager.java85
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java133
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/PrincipalDatabase.java45
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java227
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java102
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java42
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java38
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java123
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java59
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java38
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java144
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java59
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java36
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java222
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java48
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java40
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java30
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java145
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java83
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/transport/ConnectorConfiguration.java86
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java695
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java123
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java54
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java126
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/util/ConcurrentLinkedQueueNoSize.java38
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java105
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java109
130 files changed, 15147 insertions, 0 deletions
diff --git a/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java b/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java
new file mode 100644
index 0000000000..a6cb4523cf
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java
@@ -0,0 +1,788 @@
+/*
+ *
+ * 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.server;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQDataBlock;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.server.ack.TxAck;
+import org.apache.qpid.server.ack.UnacknowledgedMessage;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMapImpl;
+import org.apache.qpid.server.exchange.MessageRouter;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.NoConsumersException;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.txn.TxnBuffer;
+import org.apache.qpid.server.txn.TxnOp;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class AMQChannel
+{
+ public static final int DEFAULT_PREFETCH = 5000;
+
+ private static final Logger _log = Logger.getLogger(AMQChannel.class);
+
+ private final int _channelId;
+
+ private boolean _transactional;
+
+ private long _prefetch_HighWaterMark;
+
+ private long _prefetch_LowWaterMark;
+
+ /**
+ * The delivery tag is unique per channel. This is pre-incremented before putting into the deliver frame so that
+ * value of this represents the <b>last</b> tag sent out
+ */
+ private AtomicLong _deliveryTag = new AtomicLong(0);
+
+ /**
+ * A channel has a default queue (the last declared) that is used when no queue name is
+ * explictily set
+ */
+ private AMQQueue _defaultQueue;
+
+ /**
+ * This tag is unique per subscription to a queue. The server returns this in response to a
+ * basic.consume request.
+ */
+ private int _consumerTag;
+
+ /**
+ * The current message - which may be partial in the sense that not all frames have been received yet -
+ * which has been received by this channel. As the frames are received the message gets updated and once all
+ * frames have been received the message can then be routed.
+ */
+ private AMQMessage _currentMessage;
+
+ /**
+ * Maps from consumer tag to queue instance. Allows us to unsubscribe from a queue.
+ */
+ private final Map<String, AMQQueue> _consumerTag2QueueMap = new TreeMap<String, AMQQueue>();
+
+ private final MessageStore _messageStore;
+
+ private final Object _unacknowledgedMessageMapLock = new Object();
+
+ private Map<Long, UnacknowledgedMessage> _unacknowledgedMessageMap = new LinkedHashMap<Long, UnacknowledgedMessage>(DEFAULT_PREFETCH);
+
+ private long _lastDeliveryTag;
+
+ private final AtomicBoolean _suspended = new AtomicBoolean(false);
+
+ private final MessageRouter _exchanges;
+
+ private final TxnBuffer _txnBuffer;
+
+ private TxAck ackOp;
+
+ private final List<AMQDataBlock> _returns = new LinkedList<AMQDataBlock>();
+
+ public AMQChannel(int channelId, MessageStore messageStore, MessageRouter exchanges)
+ throws AMQException
+ {
+ _channelId = channelId;
+ _prefetch_HighWaterMark = DEFAULT_PREFETCH;
+ _prefetch_LowWaterMark = _prefetch_HighWaterMark / 2;
+ _messageStore = messageStore;
+ _exchanges = exchanges;
+ _txnBuffer = new TxnBuffer(_messageStore);
+ }
+
+ public int getChannelId()
+ {
+ return _channelId;
+ }
+
+ public boolean isTransactional()
+ {
+ return _transactional;
+ }
+
+ public void setTransactional(boolean transactional)
+ {
+ _transactional = transactional;
+ }
+
+ public long getPrefetchCount()
+ {
+ return _prefetch_HighWaterMark;
+ }
+
+ public void setPrefetchCount(long prefetchCount)
+ {
+ _prefetch_HighWaterMark = prefetchCount;
+ }
+
+ public long getPrefetchLowMarkCount()
+ {
+ return _prefetch_LowWaterMark;
+ }
+
+ public void setPrefetchLowMarkCount(long prefetchCount)
+ {
+ _prefetch_LowWaterMark = prefetchCount;
+ }
+
+ public long getPrefetchHighMarkCount()
+ {
+ return _prefetch_HighWaterMark;
+ }
+
+ public void setPrefetchHighMarkCount(long prefetchCount)
+ {
+ _prefetch_HighWaterMark = prefetchCount;
+ }
+
+
+ public void setPublishFrame(BasicPublishBody publishBody, AMQProtocolSession publisher) throws AMQException
+ {
+ _currentMessage = new AMQMessage(_messageStore, publishBody);
+ _currentMessage.setPublisher(publisher);
+ }
+
+ public void publishContentHeader(ContentHeaderBody contentHeaderBody)
+ throws AMQException
+ {
+ if (_currentMessage == null)
+ {
+ throw new AMQException("Received content header without previously receiving a BasicDeliver frame");
+ }
+ else
+ {
+ _currentMessage.setContentHeaderBody(contentHeaderBody);
+ // check and route if header says body length is zero
+ if (contentHeaderBody.bodySize == 0)
+ {
+ routeCurrentMessage();
+ }
+ }
+ }
+
+ public void publishContentBody(ContentBody contentBody)
+ throws AMQException
+ {
+ if (_currentMessage == null)
+ {
+ throw new AMQException("Received content body without previously receiving a JmsPublishBody");
+ }
+ if (_currentMessage.getContentHeaderBody() == null)
+ {
+ throw new AMQException("Received content body without previously receiving a content header");
+ }
+
+ _currentMessage.addContentBodyFrame(contentBody);
+ if (_currentMessage.isAllContentReceived())
+ {
+ routeCurrentMessage();
+ }
+ }
+
+ protected void routeCurrentMessage() throws AMQException
+ {
+ if (_transactional)
+ {
+ //don't create a transaction unless needed
+ if (_currentMessage.isPersistent())
+ {
+ _txnBuffer.containsPersistentChanges();
+ }
+
+ //A publication will result in the enlisting of several
+ //TxnOps. The first is an op that will store the message.
+ //Following that (and ordering is important), an op will
+ //be added for every queue onto which the message is
+ //enqueued. Finally a cleanup op will be added to decrement
+ //the reference associated with the routing.
+ Store storeOp = new Store(_currentMessage);
+ _txnBuffer.enlist(storeOp);
+ _currentMessage.setTxnBuffer(_txnBuffer);
+ try
+ {
+ _exchanges.routeContent(_currentMessage);
+ _txnBuffer.enlist(new Cleanup(_currentMessage));
+ }
+ catch (RequiredDeliveryException e)
+ {
+ //Can only be due to the mandatory flag, as no attempt
+ //has yet been made to deliver the message. The
+ //message will thus not have been delivered to any
+ //queue so we can return the message (without killing
+ //the transaction) and for efficiency remove the store
+ //operation from the buffer.
+ _txnBuffer.cancel(storeOp);
+ throw e;
+ }
+ finally
+ {
+ _currentMessage = null;
+ }
+ }
+ else
+ {
+ try
+ {
+ _exchanges.routeContent(_currentMessage);
+ //following check implements the functionality
+ //required by the 'immediate' flag:
+ _currentMessage.checkDeliveredToConsumer();
+ }
+ finally
+ {
+ _currentMessage.decrementReference();
+ _currentMessage = null;
+ }
+ }
+ }
+
+ public long getNextDeliveryTag()
+ {
+ return _deliveryTag.incrementAndGet();
+ }
+
+ public int getNextConsumerTag()
+ {
+ return ++_consumerTag;
+ }
+
+ /**
+ * Subscribe to a queue. We register all subscriptions in the channel so that
+ * if the channel is closed we can clean up all subscriptions, even if the
+ * client does not explicitly unsubscribe from all queues.
+ *
+ * @param tag the tag chosen by the client (if null, server will generate one)
+ * @param queue the queue to subscribe to
+ * @param session the protocol session of the subscriber
+ * @return the consumer tag. This is returned to the subscriber and used in
+ * subsequent unsubscribe requests
+ * @throws ConsumerTagNotUniqueException if the tag is not unique
+ * @throws AMQException if something goes wrong
+ */
+ public String subscribeToQueue(String tag, AMQQueue queue, AMQProtocolSession session, boolean acks) throws AMQException, ConsumerTagNotUniqueException
+ {
+ if (tag == null)
+ {
+ tag = "sgen_" + getNextConsumerTag();
+ }
+ if (_consumerTag2QueueMap.containsKey(tag))
+ {
+ throw new ConsumerTagNotUniqueException();
+ }
+
+ queue.registerProtocolSession(session, _channelId, tag, acks);
+ _consumerTag2QueueMap.put(tag, queue);
+ return tag;
+ }
+
+
+ public void unsubscribeConsumer(AMQProtocolSession session, String consumerTag) throws AMQException
+ {
+ AMQQueue q = _consumerTag2QueueMap.remove(consumerTag);
+ if (q != null)
+ {
+ q.unregisterProtocolSession(session, _channelId, consumerTag);
+ }
+ }
+
+ /**
+ * Called from the protocol session to close this channel and clean up.
+ *
+ * @throws AMQException if there is an error during closure
+ */
+ public void close(AMQProtocolSession session) throws AMQException
+ {
+ if (_transactional)
+ {
+ synchronized(_txnBuffer)
+ {
+ _txnBuffer.rollback();//releases messages
+ }
+ }
+ unsubscribeAllConsumers(session);
+ requeue();
+ }
+
+ private void unsubscribeAllConsumers(AMQProtocolSession session) throws AMQException
+ {
+ _log.info("Unsubscribing all consumers on channel " + toString());
+ for (Map.Entry<String, AMQQueue> me : _consumerTag2QueueMap.entrySet())
+ {
+ me.getValue().unregisterProtocolSession(session, _channelId, me.getKey());
+ }
+ _consumerTag2QueueMap.clear();
+ }
+
+ /**
+ * Add a message to the channel-based list of unacknowledged messages
+ *
+ * @param message
+ * @param deliveryTag
+ * @param queue
+ */
+ public void addUnacknowledgedMessage(AMQMessage message, long deliveryTag, String consumerTag, AMQQueue queue)
+ {
+ synchronized(_unacknowledgedMessageMapLock)
+ {
+ _unacknowledgedMessageMap.put(deliveryTag, new UnacknowledgedMessage(queue, message, consumerTag, deliveryTag));
+ _lastDeliveryTag = deliveryTag;
+ checkSuspension();
+ }
+ }
+
+ /**
+ * Called to attempt re-enqueue all outstanding unacknowledged messages on the channel.
+ * May result in delivery to this same channel or to other subscribers.
+ */
+ public void requeue() throws AMQException
+ {
+ // we must create a new map since all the messages will get a new delivery tag when they are redelivered
+ Map<Long, UnacknowledgedMessage> currentList;
+ synchronized(_unacknowledgedMessageMapLock)
+ {
+ currentList = _unacknowledgedMessageMap;
+ _unacknowledgedMessageMap = new LinkedHashMap<Long, UnacknowledgedMessage>(DEFAULT_PREFETCH);
+ }
+
+ for (UnacknowledgedMessage unacked : currentList.values())
+ {
+ if (unacked.queue != null)
+ {
+ unacked.queue.deliver(unacked.message);
+ }
+ }
+ }
+
+ /**
+ * Called to resend all outstanding unacknowledged messages to this same channel.
+ */
+ public void resend(AMQProtocolSession session)
+ {
+ //messages go to this channel
+ synchronized(_unacknowledgedMessageMapLock)
+ {
+ for (Map.Entry<Long, UnacknowledgedMessage> entry : _unacknowledgedMessageMap.entrySet())
+ {
+ long deliveryTag = entry.getKey();
+ String consumerTag = entry.getValue().consumerTag;
+ AMQMessage msg = entry.getValue().message;
+
+ session.writeFrame(msg.getDataBlock(_channelId, consumerTag, deliveryTag));
+ }
+ }
+ }
+
+ /**
+ * Callback indicating that a queue has been deleted. We must update the structure of unacknowledged
+ * messages to remove the queue reference and also decrement any message reference counts, without
+ * actually removing the item sine we may get an ack for a delivery tag that was generated from the
+ * deleted queue.
+ *
+ * @param queue
+ */
+ public void queueDeleted(AMQQueue queue)
+ {
+ synchronized(_unacknowledgedMessageMapLock)
+ {
+ for (Map.Entry<Long, UnacknowledgedMessage> unacked : _unacknowledgedMessageMap.entrySet())
+ {
+ final UnacknowledgedMessage unackedMsg = unacked.getValue();
+ // we can compare the reference safely in this case
+ if (unackedMsg.queue == queue)
+ {
+ unackedMsg.queue = null;
+ try
+ {
+ unackedMsg.message.decrementReference();
+ }
+ catch (AMQException e)
+ {
+ _log.error("Error decrementing ref count on message " + unackedMsg.message.getMessageId() + ": " +
+ e, e);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Acknowledge one or more messages.
+ *
+ * @param deliveryTag the last delivery tag
+ * @param multiple if true will acknowledge all messages up to an including the delivery tag. if false only
+ * acknowledges the single message specified by the delivery tag
+ * @throws AMQException if the delivery tag is unknown (e.g. not outstanding) on this channel
+ */
+ public void acknowledgeMessage(long deliveryTag, boolean multiple) throws AMQException
+ {
+ if (_transactional)
+ {
+ //check that the tag exists to give early failure
+ if (!multiple || deliveryTag > 0)
+ {
+ checkAck(deliveryTag);
+ }
+ //we use a single txn op for all acks and update this op
+ //as new acks come in. If this is the first ack in the txn
+ //we will need to create and enlist the op.
+ if (ackOp == null)
+ {
+ ackOp = new TxAck(new AckMap());
+ _txnBuffer.enlist(ackOp);
+ }
+ //update the op to include this ack request
+ if (multiple && deliveryTag == 0)
+ {
+ synchronized(_unacknowledgedMessageMapLock)
+ {
+ //if have signalled to ack all, that refers only
+ //to all at this time
+ ackOp.update(_lastDeliveryTag, multiple);
+ }
+ }
+ else
+ {
+ ackOp.update(deliveryTag, multiple);
+ }
+ }
+ else
+ {
+ handleAcknowledgement(deliveryTag, multiple);
+ }
+ }
+
+ private void checkAck(long deliveryTag) throws AMQException
+ {
+ synchronized(_unacknowledgedMessageMapLock)
+ {
+ if (!_unacknowledgedMessageMap.containsKey(deliveryTag))
+ {
+ throw new AMQException("Ack with delivery tag " + deliveryTag + " not known for channel");
+ }
+ }
+ }
+
+ private void handleAcknowledgement(long deliveryTag, boolean multiple) throws AMQException
+ {
+ if (multiple)
+ {
+ LinkedList<UnacknowledgedMessage> acked = new LinkedList<UnacknowledgedMessage>();
+ synchronized(_unacknowledgedMessageMapLock)
+ {
+ if (deliveryTag == 0)
+ {
+ //Spec 2.1.6.11 ... If the multiple field is 1, and the delivery tag is zero, tells the server to acknowledge all outstanding mesages.
+ _log.trace("Multiple ack on delivery tag 0. ACKing all messages. Current count:" + _unacknowledgedMessageMap.size());
+ acked = new LinkedList<UnacknowledgedMessage>(_unacknowledgedMessageMap.values());
+ _unacknowledgedMessageMap.clear();
+ }
+ else
+ {
+ if (!_unacknowledgedMessageMap.containsKey(deliveryTag))
+ {
+ throw new AMQException("Multiple ack on delivery tag " + deliveryTag + " not known for channel");
+ }
+ Iterator<Map.Entry<Long, UnacknowledgedMessage>> i = _unacknowledgedMessageMap.entrySet().iterator();
+
+ while (i.hasNext())
+ {
+
+ Map.Entry<Long, UnacknowledgedMessage> unacked = i.next();
+
+ if (unacked.getKey() > deliveryTag)
+ {
+ //This should not occur now.
+ throw new AMQException("UnacknowledgedMessageMap is out of order:" + unacked.getKey() + " When deliveryTag is:" + deliveryTag + "ES:" + _unacknowledgedMessageMap.entrySet().toString());
+ }
+
+ i.remove();
+
+ acked.add(unacked.getValue());
+ if (unacked.getKey() == deliveryTag)
+ {
+ break;
+ }
+ }
+ }
+ }// synchronized
+
+ if (_log.isTraceEnabled())
+ {
+ _log.trace("Received multiple ack for delivery tag " + deliveryTag + ". Removing " +
+ acked.size() + " items.");
+ }
+
+ for (UnacknowledgedMessage msg : acked)
+ {
+ msg.discard();
+ }
+
+ }
+ else
+ {
+ UnacknowledgedMessage msg;
+ synchronized(_unacknowledgedMessageMapLock)
+ {
+ msg = _unacknowledgedMessageMap.remove(deliveryTag);
+ }
+
+ if (msg == null)
+ {
+ _log.trace("Single ack on delivery tag " + deliveryTag + " not known for channel:" + _channelId);
+ throw new AMQException("Single ack on delivery tag " + deliveryTag + " not known for channel:" + _channelId);
+ }
+ msg.discard();
+ if (_log.isTraceEnabled())
+ {
+ _log.trace("Received non-multiple ack for messaging with delivery tag " + deliveryTag);
+ }
+ }
+
+ checkSuspension();
+ }
+
+ /**
+ * Used only for testing purposes.
+ *
+ * @return the map of unacknowledged messages
+ */
+ public Map<Long, UnacknowledgedMessage> getUnacknowledgedMessageMap()
+ {
+ return _unacknowledgedMessageMap;
+ }
+
+ private void checkSuspension()
+ {
+ boolean suspend;
+ //noinspection SynchronizeOnNonFinalField
+ synchronized(_unacknowledgedMessageMapLock)
+ {
+ suspend = _unacknowledgedMessageMap.size() >= _prefetch_HighWaterMark;
+ }
+ setSuspended(suspend);
+ }
+
+ public void setSuspended(boolean suspended)
+ {
+ boolean isSuspended = _suspended.get();
+
+ if (isSuspended && !suspended)
+ {
+ synchronized(_unacknowledgedMessageMapLock)
+ {
+ // Continue being suspended if we are above the _prefetch_LowWaterMark
+ suspended = _unacknowledgedMessageMap.size() > _prefetch_LowWaterMark;
+ }
+ }
+
+ boolean wasSuspended = _suspended.getAndSet(suspended);
+ if (wasSuspended != suspended)
+ {
+ if (wasSuspended)
+ {
+ _log.debug("Unsuspending channel " + this);
+ //may need to deliver queued messages
+ for (AMQQueue q : _consumerTag2QueueMap.values())
+ {
+ q.deliverAsync();
+ }
+ }
+ else
+ {
+ _log.debug("Suspending channel " + this);
+ }
+ }
+ }
+
+ public boolean isSuspended()
+ {
+ return _suspended.get();
+ }
+
+ public void commit() throws AMQException
+ {
+ if (ackOp != null)
+ {
+ ackOp.consolidate();
+ if (ackOp.checkPersistent())
+ {
+ _txnBuffer.containsPersistentChanges();
+ }
+ ackOp = null;//already enlisted, after commit will reset regardless of outcome
+ }
+
+ _txnBuffer.commit();
+ //TODO: may need to return 'immediate' messages at this point
+ }
+
+ public void rollback() throws AMQException
+ {
+ //need to protect rollback and close from each other...
+ synchronized(_txnBuffer)
+ {
+ _txnBuffer.rollback();
+ }
+ }
+
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(30);
+ sb.append("Channel: id ").append(_channelId).append(", transaction mode: ").append(_transactional);
+ sb.append(", prefetch marks: ").append(_prefetch_LowWaterMark);
+ sb.append("/").append(_prefetch_HighWaterMark);
+ return sb.toString();
+ }
+
+ public void setDefaultQueue(AMQQueue queue)
+ {
+ _defaultQueue = queue;
+ }
+
+ public AMQQueue getDefaultQueue()
+ {
+ return _defaultQueue;
+ }
+
+ public void processReturns(AMQProtocolSession session)
+ {
+ for (AMQDataBlock block : _returns)
+ {
+ session.writeFrame(block);
+ }
+ _returns.clear();
+ }
+
+ //we use this wrapper to ensure we are always using the correct
+ //map instance (its not final unfortunately)
+ private class AckMap implements UnacknowledgedMessageMap
+ {
+ public void collect(long deliveryTag, boolean multiple, List<UnacknowledgedMessage> msgs)
+ {
+ impl().collect(deliveryTag, multiple, msgs);
+ }
+
+ public void remove(List<UnacknowledgedMessage> msgs)
+ {
+ impl().remove(msgs);
+ }
+
+ private UnacknowledgedMessageMap impl()
+ {
+ return new UnacknowledgedMessageMapImpl(_unacknowledgedMessageMapLock, _unacknowledgedMessageMap);
+ }
+ }
+
+ private class Store implements TxnOp
+ {
+ //just use this to do a store of the message during the
+ //prepare phase. Any enqueueing etc is done by TxnOps enlisted
+ //by the queues themselves.
+ private final AMQMessage _msg;
+
+ Store(AMQMessage msg)
+ {
+ _msg = msg;
+ }
+
+ public void prepare() throws AMQException
+ {
+ _msg.storeMessage();
+ //the routers reference can now be released
+ _msg.decrementReference();
+ }
+
+ public void undoPrepare()
+ {
+ }
+
+ public void commit()
+ {
+ }
+
+ public void rollback()
+ {
+ }
+ }
+
+ private class Cleanup implements TxnOp
+ {
+ private final AMQMessage _msg;
+
+ Cleanup(AMQMessage msg)
+ {
+ _msg = msg;
+ }
+
+ public void prepare() throws AMQException
+ {
+ }
+
+ public void undoPrepare()
+ {
+ //don't need to do anything here, if the store's txn failed
+ //when processing prepare then the message was not stored
+ //or enqueued on any queues and can be discarded
+ }
+
+ public void commit()
+ {
+ //The routers reference can now be released. This is done
+ //here to ensure that it happens after the queues that
+ //enqueue it have incremented their counts (which as a
+ //memory only operation is done in the commit phase).
+ try
+ {
+ _msg.decrementReference();
+ }
+ catch (AMQException e)
+ {
+ _log.error("On commiting transaction, failed to cleanup unused message: " + e, e);
+ }
+ try
+ {
+ _msg.checkDeliveredToConsumer();
+ }
+ catch (NoConsumersException e)
+ {
+ //TODO: store this for delivery after the commit-ok
+ _returns.add(e.getReturnMessage(_channelId));
+ }
+ }
+
+ public void rollback()
+ {
+ }
+ }
+
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java b/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java
new file mode 100644
index 0000000000..9a98af5689
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java
@@ -0,0 +1,25 @@
+/*
+ *
+ * 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.server;
+
+public class ConsumerTagNotUniqueException extends Exception
+{
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/Main.java b/java/broker/src/main/java/org/apache/qpid/server/Main.java
new file mode 100644
index 0000000000..d2dacb6140
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/Main.java
@@ -0,0 +1,627 @@
+/*
+ *
+ * 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.server;
+
+import org.apache.qpid.framing.ProtocolVersionList;
+import org.apache.qpid.pool.ReadWriteThreadModel;
+import org.apache.qpid.server.protocol.AMQPFastProtocolHandler;
+import org.apache.qpid.server.protocol.AMQPProtocolProvider;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.server.transport.ConnectorConfiguration;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.management.*;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.url.URLSyntaxException;
+import org.apache.commons.cli.*;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.Logger;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.apache.mina.common.ByteBuffer;
+import org.apache.mina.common.IoAcceptor;
+import org.apache.mina.common.SimpleByteBufferAllocator;
+import org.apache.mina.transport.socket.nio.SocketSessionConfig;
+
+import javax.management.JMException;
+import javax.management.MBeanException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectName;
+import javax.management.MalformedObjectNameException;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.StringTokenizer;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Main entry point for AMQPD.
+ */
+public class Main implements ProtocolVersionList
+{
+ private static final Logger _logger = Logger.getLogger(Main.class);
+
+ private static final String DEFAULT_CONFIG_FILE = "etc/config.xml";
+
+ private static final String DEFAULT_LOG_CONFIG_FILENAME = "log4j.xml";
+
+ protected static class InitException extends Exception
+ {
+ InitException(String msg)
+ {
+ super(msg);
+ }
+ }
+
+ protected final Options options = new Options();
+ protected CommandLine commandLine;
+
+ protected Main(String[] args)
+ {
+ setOptions(options);
+ if (parseCommandline(args))
+ {
+ execute();
+ }
+ }
+
+ protected boolean parseCommandline(String[] args)
+ {
+ try
+ {
+ commandLine = new PosixParser().parse(options, args);
+ return true;
+ }
+ catch (ParseException e)
+ {
+ System.err.println("Error: " + e.getMessage());
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp("Qpid", options, true);
+ return false;
+ }
+ }
+
+ protected void setOptions(Options options)
+ {
+ Option help = new Option("h", "help", false, "print this message");
+ Option version = new Option("v", "version", false, "print the version information and exit");
+ Option configFile = OptionBuilder.withArgName("file").hasArg().withDescription("use given configuration file").
+ withLongOpt("config").create("c");
+ Option port = OptionBuilder.withArgName("port").hasArg().withDescription("listen on the specified port. Overrides any value in the config file").
+ withLongOpt("port").create("p");
+ Option bind = OptionBuilder.withArgName("bind").hasArg().withDescription("bind to the specified address. Overrides any value in the config file").
+ withLongOpt("bind").create("b");
+ Option logconfig = OptionBuilder.withArgName("logconfig").hasArg().withDescription("use the specified log4j xml configuration file. By " +
+ "default looks for a file named " + DEFAULT_LOG_CONFIG_FILENAME + " in the same directory as the configuration file").
+ withLongOpt("logconfig").create("l");
+ Option logwatchconfig = OptionBuilder.withArgName("logwatch").hasArg().withDescription("monitor the log file configuration file for changes. Units are seconds. " +
+ "Zero means do not check for changes.").withLongOpt("logwatch").create("w");
+
+ options.addOption(help);
+ options.addOption(version);
+ options.addOption(configFile);
+ options.addOption(logconfig);
+ options.addOption(logwatchconfig);
+ options.addOption(port);
+ options.addOption(bind);
+ }
+
+ protected void execute()
+ {
+ // note this understands either --help or -h. If an option only has a long name you can use that but if
+ // an option has a short name and a long name you must use the short name here.
+ if (commandLine.hasOption("h"))
+ {
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp("Qpid", options, true);
+ }
+ else if (commandLine.hasOption("v"))
+ {
+ String ver = "Qpid 0.9.0.0";
+ String protocol = "AMQP version(s) [major.minor]: ";
+ for (int i = 0; i < pv.length; i++)
+ {
+ if (i > 0)
+ {
+ protocol += ", ";
+ }
+ protocol += pv[i][PROTOCOL_MAJOR] + "." + pv[i][PROTOCOL_MINOR];
+ }
+ System.out.println(ver + " (" + protocol + ")");
+ }
+ else
+ {
+ try
+ {
+ startup();
+ }
+ catch (InitException e)
+ {
+ System.out.println(e.getMessage());
+ }
+ catch (ConfigurationException e)
+ {
+ System.out.println("Error configuring message broker: " + e);
+ e.printStackTrace();
+ }
+ catch (Exception e)
+ {
+ System.out.println("Error intialising message broker: " + e);
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+ protected void startup() throws InitException, ConfigurationException, Exception
+ {
+ final String QpidHome = System.getProperty("QPID_HOME");
+ final File defaultConfigFile = new File(QpidHome, DEFAULT_CONFIG_FILE);
+ final File configFile = new File(commandLine.getOptionValue("c", defaultConfigFile.getPath()));
+ if (!configFile.exists())
+ {
+ String error = "File " + configFile + " could not be found. Check the file exists and is readable.";
+
+ if (QpidHome == null)
+ {
+ error = error + "\nNote: Qpid_HOME is not set.";
+ }
+
+ throw new InitException(error);
+ }
+ else
+ {
+ System.out.println("Using configuration file " + configFile.getAbsolutePath());
+ }
+
+ String logConfig = commandLine.getOptionValue("l");
+ String logWatchConfig = commandLine.getOptionValue("w", "0");
+ if (logConfig != null)
+ {
+ File logConfigFile = new File(logConfig);
+ configureLogging(logConfigFile, logWatchConfig);
+ }
+ else
+ {
+ File configFileDirectory = configFile.getParentFile();
+ File logConfigFile = new File(configFileDirectory, DEFAULT_LOG_CONFIG_FILENAME);
+ configureLogging(logConfigFile, logWatchConfig);
+ }
+
+ ApplicationRegistry.initialise(new ConfigurationFileApplicationRegistry(configFile));
+
+ _logger.info("Starting Qpid.AMQP broker");
+
+ ConnectorConfiguration connectorConfig = ApplicationRegistry.getInstance().
+ getConfiguredObject(ConnectorConfiguration.class);
+
+ // From old Mina
+ //ByteBuffer.setUseDirectBuffers(connectorConfig.enableDirectBuffers);
+
+ // the MINA default is currently to use the pooled allocator although this may change in future
+ // once more testing of the performance of the simple allocator has been done
+ if (!connectorConfig.enablePooledAllocator)
+ {
+ ByteBuffer.setAllocator(new SimpleByteBufferAllocator());
+ }
+
+ int port = connectorConfig.port;
+
+ String portStr = commandLine.getOptionValue("p");
+ if (portStr != null)
+ {
+ try
+ {
+ port = Integer.parseInt(portStr);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new InitException("Invalid port: " + portStr);
+ }
+ }
+
+ String VIRTUAL_HOSTS = "virtualhosts";
+
+ Object virtualHosts = ApplicationRegistry.getInstance().getConfiguration().getProperty(VIRTUAL_HOSTS);
+
+ if (virtualHosts != null)
+ {
+ if (virtualHosts instanceof Collection)
+ {
+ int totalVHosts = ((Collection) virtualHosts).size();
+ for (int vhost = 0; vhost < totalVHosts; vhost++)
+ {
+ setupVirtualHosts(configFile.getParent(), (String) ((List) virtualHosts).get(vhost));
+ }
+ }
+ else
+ {
+ setupVirtualHosts(configFile.getParent(), (String) virtualHosts);
+ }
+ }
+ bind(port, connectorConfig);
+
+ createAndRegisterBrokerMBean();
+ }
+
+ protected void setupVirtualHosts(String configFileParent, String configFilePath) throws ConfigurationException, AMQException, URLSyntaxException
+ {
+ String configVar = "${conf}";
+
+ if (configFilePath.startsWith(configVar))
+ {
+ configFilePath = configFileParent + configFilePath.substring(configVar.length());
+ }
+
+ if (configFilePath.indexOf(".xml") != -1)
+ {
+ VirtualHostConfiguration vHostConfig = new VirtualHostConfiguration(configFilePath);
+ vHostConfig.performBindings();
+ }
+ else
+ {
+ // the virtualhosts value is a path. Search it for XML files.
+
+ File virtualHostDir = new File(configFilePath);
+
+ String[] fileNames = virtualHostDir.list();
+
+ for (int each = 0; each < fileNames.length; each++)
+ {
+ if (fileNames[each].endsWith(".xml"))
+ {
+ VirtualHostConfiguration vHostConfig = new VirtualHostConfiguration(configFilePath + "/" + fileNames[each]);
+ vHostConfig.performBindings();
+ }
+ }
+ }
+ }
+
+ protected void bind(int port, ConnectorConfiguration connectorConfig)
+ {
+ String bindAddr = commandLine.getOptionValue("b");
+ if (bindAddr == null)
+ {
+ bindAddr = connectorConfig.bindAddress;
+ }
+
+ try
+ {
+ //IoAcceptor acceptor = new SocketAcceptor(connectorConfig.processors);
+ IoAcceptor acceptor = connectorConfig.createAcceptor();
+
+ SocketSessionConfig sc;
+
+ sc = (SocketSessionConfig) acceptor.getSessionConfig();
+
+ sc.setReceiveBufferSize(connectorConfig.socketReceiveBufferSize);
+ sc.setSendBufferSize(connectorConfig.socketWriteBuferSize);
+ sc.setTcpNoDelay(connectorConfig.tcpNoDelay);
+
+ // if we do not use the executor pool threading model we get the default leader follower
+ // implementation provided by MINA
+ if (connectorConfig.enableExecutorPool)
+ {
+ acceptor.setThreadModel(new ReadWriteThreadModel());
+ }
+
+ if (connectorConfig.enableNonSSL)
+ {
+ AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler();
+ InetSocketAddress bindAddress;
+ if (bindAddr.equals("wildcard"))
+ {
+ bindAddress = new InetSocketAddress(port);
+ }
+ else
+ {
+ bindAddress = new InetSocketAddress(InetAddress.getByAddress(parseIP(bindAddr)), port);
+ }
+ acceptor.setLocalAddress(bindAddress);
+ acceptor.setHandler(handler);
+ acceptor.bind();
+ _logger.info("Qpid.AMQP listening on non-SSL address " + bindAddress);
+ }
+
+ if (connectorConfig.enableSSL)
+ {
+ AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler();
+ handler.setUseSSL(true);
+ try
+ {
+ acceptor.setLocalAddress(new InetSocketAddress(connectorConfig.sslPort));
+ acceptor.setHandler(handler);
+ acceptor.bind();
+ _logger.info("Qpid.AMQP listening on SSL port " + connectorConfig.sslPort);
+ }
+ catch (IOException e)
+ {
+ _logger.error("Unable to listen on SSL port: " + e, e);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.error("Unable to bind service to registry: " + e, e);
+ }
+ }
+
+ public static void main(String[] args)
+ {
+
+ new Main(args);
+ }
+
+ private byte[] parseIP(String address) throws Exception
+ {
+ StringTokenizer tokenizer = new StringTokenizer(address, ".");
+ byte[] ip = new byte[4];
+ int index = 0;
+ while (tokenizer.hasMoreTokens())
+ {
+ String token = tokenizer.nextToken();
+ try
+ {
+ ip[index++] = Byte.parseByte(token);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new Exception("Error parsing IP address: " + address, e);
+ }
+ }
+ if (index != 4)
+ {
+ throw new Exception("Invalid IP address: " + address);
+ }
+ return ip;
+ }
+
+ private void configureLogging(File logConfigFile, String logWatchConfig)
+ {
+ int logWatchTime = 0;
+ try
+ {
+ logWatchTime = Integer.parseInt(logWatchConfig);
+ }
+ catch (NumberFormatException e)
+ {
+ System.err.println("Log watch configuration value of " + logWatchConfig + " is invalid. Must be " +
+ "a non-negative integer. Using default of zero (no watching configured");
+ }
+ if (logConfigFile.exists() && logConfigFile.canRead())
+ {
+ System.out.println("Configuring logger using configuration file " + logConfigFile.getAbsolutePath());
+
+ if (logWatchTime > 0)
+ {
+ System.out.println("log file " + logConfigFile.getAbsolutePath() + " will be checked for changes every " +
+ logWatchTime + " seconds");
+ // log4j expects the watch interval in milliseconds
+ DOMConfigurator.configureAndWatch(logConfigFile.getAbsolutePath(), logWatchTime * 1000);
+ }
+ else
+ {
+ DOMConfigurator.configure(logConfigFile.getAbsolutePath());
+ }
+ }
+ else
+ {
+ System.err.println("Logging configuration error: unable to read file " + logConfigFile.getAbsolutePath());
+ System.err.println("Using basic log4j configuration");
+ BasicConfigurator.configure();
+ }
+ }
+
+ private void createAndRegisterBrokerMBean() throws AMQException
+ {
+ try
+ {
+ new AMQBrokerManager().register();
+ }
+ catch (NotCompliantMBeanException ex)
+ {
+ throw new AMQException("Exception occured in creating AMQBrokerManager MBean.");
+ }
+ }
+
+ /**
+ * AMQPBrokerMBean implements the broker management interface and exposes the
+ * Broker level management features like creating and deleting exchanges and queue.
+ */
+ @MBeanDescription("This MBean exposes the broker level management features")
+ private final class AMQBrokerManager extends AMQManagedObject
+ implements ManagedBroker
+ {
+ private final QueueRegistry _queueRegistry;
+ private final ExchangeRegistry _exchangeRegistry;
+ private final ExchangeFactory _exchangeFactory;
+ private final MessageStore _messageStore;
+
+ @MBeanConstructor("Creates the Broker Manager MBean")
+ protected AMQBrokerManager() throws NotCompliantMBeanException
+ {
+ super(ManagedBroker.class, ManagedBroker.TYPE);
+
+ IApplicationRegistry appRegistry = ApplicationRegistry.getInstance();
+ _queueRegistry = appRegistry.getQueueRegistry();
+ _exchangeRegistry = appRegistry.getExchangeRegistry();
+ _exchangeFactory = ApplicationRegistry.getInstance().getExchangeFactory();
+ _messageStore = ApplicationRegistry.getInstance().getMessageStore();
+ }
+
+ public String getObjectInstanceName()
+ {
+ return this.getClass().getName();
+ }
+
+ /**
+ * Creates new exchange and registers it with the registry.
+ *
+ * @param exchangeName
+ * @param type
+ * @param durable
+ * @param autoDelete
+ * @throws JMException
+ */
+ public void createNewExchange(String exchangeName,
+ String type,
+ boolean durable,
+ boolean autoDelete)
+ throws JMException
+ {
+ try
+ {
+ synchronized(_exchangeRegistry)
+ {
+ Exchange exchange = _exchangeRegistry.getExchange(exchangeName);
+
+ if (exchange == null)
+ {
+ exchange = _exchangeFactory.createExchange(exchangeName,
+ type, //eg direct
+ durable,
+ autoDelete,
+ 0); //ticket no
+ _exchangeRegistry.registerExchange(exchange);
+ }
+ else
+ {
+ throw new JMException("The exchange \"" + exchangeName + "\" already exists.");
+ }
+ }
+ }
+ catch (AMQException ex)
+ {
+ _logger.error("Error in creating exchange " + exchangeName, ex);
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+
+ /**
+ * Unregisters the exchange from registry.
+ *
+ * @param exchangeName
+ * @throws JMException
+ */
+ public void unregisterExchange(String exchangeName)
+ throws JMException
+ {
+ boolean inUse = false;
+ // TODO
+ // Check if the exchange is in use.
+ // Check if there are queue-bindings with the exchnage and unregister
+ // when there are no bindings.
+ try
+ {
+ _exchangeRegistry.unregisterExchange(exchangeName, false);
+ }
+ catch (AMQException ex)
+ {
+ _logger.error("Error in unregistering exchange " + exchangeName, ex);
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+
+ /**
+ * Creates a new queue and registers it with the registry and puts it
+ * in persistance storage if durable queue.
+ *
+ * @param queueName
+ * @param durable
+ * @param owner
+ * @param autoDelete
+ * @throws JMException
+ */
+ public void createQueue(String queueName,
+ boolean durable,
+ String owner,
+ boolean autoDelete)
+ throws JMException
+ {
+ AMQQueue queue = _queueRegistry.getQueue(queueName);
+ if (queue == null)
+ {
+ try
+ {
+ queue = new AMQQueue(queueName, durable, owner, autoDelete, _queueRegistry);
+ if (queue.isDurable() && !queue.isAutoDelete())
+ {
+ _messageStore.createQueue(queue);
+ }
+ _queueRegistry.registerQueue(queue);
+ }
+ catch (AMQException ex)
+ {
+ _logger.error("Error in creating queue " + queueName, ex);
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+ else
+ {
+ throw new JMException("The queue \"" + queueName + "\" already exists.");
+ }
+ }
+
+ /**
+ * Deletes the queue from queue registry and persistant storage.
+ *
+ * @param queueName
+ * @throws JMException
+ */
+ public void deleteQueue(String queueName) throws JMException
+ {
+ AMQQueue queue = _queueRegistry.getQueue(queueName);
+ if (queue == null)
+ {
+ throw new JMException("The Queue " + queueName + " is not a registerd queue.");
+ }
+
+ try
+ {
+ queue.delete();
+ _messageStore.removeQueue(queueName);
+
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+
+ public ObjectName getObjectName() throws MalformedObjectNameException
+ {
+ StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN);
+ objectName.append(":type=").append(getType());
+
+ return new ObjectName(objectName.toString());
+ }
+ } // End of MBean class
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java b/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java
new file mode 100644
index 0000000000..74c5366c7d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java
@@ -0,0 +1,67 @@
+/*
+ *
+ * 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.server;
+
+import javax.management.JMException;
+import java.io.IOException;
+
+/**
+ * The managed interface exposed to allow management of channels.
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+public interface ManagedChannel
+{
+ static final String TYPE = "Channel";
+
+ /**
+ * Tells whether the channel is transactional.
+ * @return true if the channel is transactional.
+ * @throws IOException
+ */
+ boolean isTransactional() throws IOException;
+
+ /**
+ * Tells the number of unacknowledged messages in this channel.
+ * @return number of unacknowledged messages.
+ * @throws IOException
+ */
+ int getUnacknowledgedMessageCount() throws IOException;
+
+
+ //********** Operations *****************//
+
+ /**
+ * Commits the transactions if the channel is transactional.
+ * @throws IOException
+ * @throws JMException
+ */
+ void commitTransactions() throws IOException, JMException;
+
+ /**
+ * Rollsback the transactions if the channel is transactional.
+ * @throws IOException
+ * @throws JMException
+ */
+ void rollbackTransactions() throws IOException, JMException;
+
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java b/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java
new file mode 100644
index 0000000000..87691ccaa3
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java
@@ -0,0 +1,112 @@
+/*
+ *
+ * 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.server;
+
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.CompositeAMQDataBlock;
+import org.apache.qpid.framing.BasicReturnBody;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+
+import java.util.List;
+
+/**
+ * Signals that a required delivery could not be made. This could be bacuse of
+ * the immediate flag being set and the queue having no consumers, or the mandatory
+ * flag being set and the exchange having no valid bindings.
+ */
+public abstract class RequiredDeliveryException extends AMQException
+{
+ private final String _message;
+ private final BasicPublishBody _publishBody;
+ private final ContentHeaderBody _contentHeaderBody;
+ private final List<ContentBody> _contentBodies;
+
+ public RequiredDeliveryException(String message, AMQMessage payload)
+ {
+ super(message);
+ _message = message;
+ _publishBody = payload.getPublishBody();
+ _contentHeaderBody = payload.getContentHeaderBody();
+ _contentBodies = payload.getContentBodies();
+ }
+
+ public RequiredDeliveryException(String message,
+ BasicPublishBody publishBody,
+ ContentHeaderBody contentHeaderBody,
+ List<ContentBody> contentBodies)
+ {
+ super(message);
+ _message = message;
+ _publishBody = publishBody;
+ _contentHeaderBody = contentHeaderBody;
+ _contentBodies = contentBodies;
+ }
+
+ public BasicPublishBody getPublishBody()
+ {
+ return _publishBody;
+ }
+
+ public ContentHeaderBody getContentHeaderBody()
+ {
+ return _contentHeaderBody;
+ }
+
+ public List<ContentBody> getContentBodies()
+ {
+ return _contentBodies;
+ }
+
+ public CompositeAMQDataBlock getReturnMessage(int channel)
+ {
+ BasicReturnBody returnBody = new BasicReturnBody();
+ returnBody.exchange = _publishBody.exchange;
+ returnBody.replyCode = getReplyCode();
+ returnBody.replyText = _message;
+ returnBody.routingKey = _publishBody.routingKey;
+
+ AMQFrame[] allFrames = new AMQFrame[2 + _contentBodies.size()];
+
+ AMQFrame returnFrame = new AMQFrame();
+ returnFrame.bodyFrame = returnBody;
+ returnFrame.channel = channel;
+
+ allFrames[0] = returnFrame;
+ allFrames[1] = ContentHeaderBody.createAMQFrame(channel, _contentHeaderBody);
+ for (int i = 2; i < allFrames.length; i++)
+ {
+ allFrames[i] = ContentBody.createAMQFrame(channel, _contentBodies.get(i - 2));
+ }
+
+ return new CompositeAMQDataBlock(allFrames);
+ }
+
+ public int getErrorCode()
+ {
+ return getReplyCode();
+ }
+
+ public abstract int getReplyCode();
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java b/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java
new file mode 100644
index 0000000000..a4cb1f1e71
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java
@@ -0,0 +1,132 @@
+/*
+ *
+ * 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.server.ack;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.txn.TxnOp;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A TxnOp implementation for handling accumulated acks
+ */
+public class TxAck implements TxnOp
+{
+ private final UnacknowledgedMessageMap _map;
+ private final List <UnacknowledgedMessage> _unacked = new LinkedList<UnacknowledgedMessage>();
+ private final List<Long> _individual = new LinkedList<Long>();
+ private long _deliveryTag;
+ private boolean _multiple;
+
+ public TxAck(UnacknowledgedMessageMap map)
+ {
+ _map = map;
+ }
+
+ public void update(long deliveryTag, boolean multiple)
+ {
+ if(!multiple)
+ {
+ //have acked a single message that is not part of
+ //the previously acked region so record
+ //individually
+ _individual.add(deliveryTag);//_multiple && !multiple
+ }
+ else if(deliveryTag > _deliveryTag)
+ {
+ //have simply moved the last acked message on a
+ //bit
+ _deliveryTag = deliveryTag;
+ _multiple = true;
+ }
+ }
+
+ public void consolidate()
+ {
+ //lookup all the unacked messages that have been acked in this transaction
+ if(_multiple)
+ {
+ //get all the unacked messages for the accumulated
+ //multiple acks
+ _map.collect(_deliveryTag, true, _unacked);
+ }
+ //get any unacked messages for individual acks outside the
+ //range covered by multiple acks
+ for(long tag : _individual)
+ {
+ if(_deliveryTag < tag)
+ {
+ _map.collect(tag, false, _unacked);
+ }
+ }
+ }
+
+ public boolean checkPersistent() throws AMQException
+ {
+ //if any of the messages in unacked are persistent the txn
+ //buffer must be marked as persistent:
+ for(UnacknowledgedMessage msg : _unacked)
+ {
+ if(msg.message.isPersistent())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void prepare() throws AMQException
+ {
+ //make persistent changes, i.e. dequeue and decrementReference
+ for(UnacknowledgedMessage msg : _unacked)
+ {
+ msg.discard();
+ }
+ }
+
+ public void undoPrepare()
+ {
+ //decrementReference is annoyingly untransactional (due to
+ //in memory counter) so if we failed in prepare for full
+ //txn, this op will have to compensate by fixing the count
+ //in memory (persistent changes will be rolled back by store)
+ for(UnacknowledgedMessage msg : _unacked)
+ {
+ msg.message.incrementReference();
+ }
+ }
+
+ public void commit()
+ {
+ //remove the unacked messages from the channels map
+ _map.remove(_unacked);
+ }
+
+ public void rollback()
+ {
+ }
+}
+
diff --git a/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessage.java b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessage.java
new file mode 100644
index 0000000000..0eff5a3cca
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessage.java
@@ -0,0 +1,51 @@
+/*
+ *
+ * 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.server.ack;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+
+public class UnacknowledgedMessage
+{
+ public final AMQMessage message;
+ public final String consumerTag;
+ public final long deliveryTag;
+ public AMQQueue queue;
+
+ public UnacknowledgedMessage(AMQQueue queue, AMQMessage message, String consumerTag, long deliveryTag)
+ {
+ this.queue = queue;
+ this.message = message;
+ this.consumerTag = consumerTag;
+ this.deliveryTag = deliveryTag;
+ }
+
+ public void discard() throws AMQException
+ {
+ if (queue != null)
+ {
+ message.dequeue(queue);
+ }
+ message.decrementReference();
+ }
+}
+
diff --git a/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java
new file mode 100644
index 0000000000..b0bbe224e3
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * 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.server.ack;
+
+import java.util.List;
+
+public interface UnacknowledgedMessageMap
+{
+ public void collect(long deliveryTag, boolean multiple, List<UnacknowledgedMessage> msgs);
+ public void remove(List<UnacknowledgedMessage> msgs);
+}
+
diff --git a/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java
new file mode 100644
index 0000000000..eda3233e56
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java
@@ -0,0 +1,84 @@
+/*
+ *
+ * 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.server.ack;
+
+import java.util.List;
+import java.util.Map;
+
+public class UnacknowledgedMessageMapImpl implements UnacknowledgedMessageMap
+{
+ private final Object _lock;
+ private Map<Long, UnacknowledgedMessage> _map;
+
+ public UnacknowledgedMessageMapImpl(Object lock, Map<Long, UnacknowledgedMessage> map)
+ {
+ _lock = lock;
+ _map = map;
+ }
+
+ public void collect(long deliveryTag, boolean multiple, List<UnacknowledgedMessage> msgs)
+ {
+ if (multiple)
+ {
+ collect(deliveryTag, msgs);
+ }
+ else
+ {
+ msgs.add(get(deliveryTag));
+ }
+
+ }
+
+ public void remove(List<UnacknowledgedMessage> msgs)
+ {
+ synchronized(_lock)
+ {
+ for(UnacknowledgedMessage msg : msgs)
+ {
+ _map.remove(msg.deliveryTag);
+ }
+ }
+ }
+
+ private UnacknowledgedMessage get(long key)
+ {
+ synchronized(_lock)
+ {
+ return _map.get(key);
+ }
+ }
+
+ private void collect(long key, List<UnacknowledgedMessage> msgs)
+ {
+ synchronized(_lock)
+ {
+ for(Map.Entry<Long, UnacknowledgedMessage> entry : _map.entrySet())
+ {
+ msgs.add(entry.getValue());
+ if (entry.getKey() == key)
+ {
+ break;
+ }
+ }
+ }
+ }
+}
+
diff --git a/java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java b/java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java
new file mode 100644
index 0000000000..5e3ac03ba7
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java
@@ -0,0 +1,105 @@
+/*
+ *
+ * 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.server.configuration;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.log4j.Logger;
+import org.apache.qpid.configuration.Configured;
+import org.apache.qpid.configuration.PropertyUtils;
+import org.apache.qpid.configuration.PropertyException;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+import java.lang.reflect.Field;
+
+/**
+ * This class contains utilities for populating classes automatically from values pulled from configuration
+ * files.
+ */
+public class Configurator
+{
+ private static final Logger _logger = Logger.getLogger(Configurator.class);
+
+ /**
+ * Configure a given instance using the application configuration. Note that superclasses are <b>not</b>
+ * currently configured but this could easily be added if required.
+ * @param instance the instance to configure
+ */
+ public static void configure(Object instance)
+ {
+ final Configuration config = ApplicationRegistry.getInstance().getConfiguration();
+
+ for (Field f : instance.getClass().getDeclaredFields())
+ {
+ Configured annotation = f.getAnnotation(Configured.class);
+ if (annotation != null)
+ {
+ setValueInField(f, instance, config, annotation);
+ }
+ }
+ }
+
+ private static void setValueInField(Field f, Object instance, Configuration config, Configured annotation)
+ {
+ Class fieldClass = f.getType();
+ String configPath = annotation.path();
+ try
+ {
+ if (fieldClass == String.class)
+ {
+ String val = config.getString(configPath, annotation.defaultValue());
+ val = PropertyUtils.replaceProperties(val);
+ f.set(instance, val);
+ }
+ else if (fieldClass == int.class)
+ {
+ int val = config.getInt(configPath, Integer.parseInt(annotation.defaultValue()));
+ f.setInt(instance, val);
+ }
+ else if (fieldClass == long.class)
+ {
+ long val = config.getLong(configPath, Long.parseLong(annotation.defaultValue()));
+ f.setLong(instance, val);
+ }
+ else if (fieldClass == double.class)
+ {
+ double val = config.getDouble(configPath, Double.parseDouble(annotation.defaultValue()));
+ f.setDouble(instance, val);
+ }
+ else if (fieldClass == boolean.class)
+ {
+ boolean val = config.getBoolean(configPath, Boolean.parseBoolean(annotation.defaultValue()));
+ f.setBoolean(instance, val);
+ }
+ else
+ {
+ _logger.error("Unsupported field type " + fieldClass + " for " + f + " IGNORING configured value");
+ }
+ }
+ catch (PropertyException e)
+ {
+ _logger.error("Unable to expand property: " + e + " INGORING field " + f, e);
+ }
+ catch (IllegalAccessException e)
+ {
+ _logger.error("Unable to access field " + f + " IGNORING configured value");
+ }
+ }
+} \ No newline at end of file
diff --git a/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java b/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java
new file mode 100644
index 0000000000..9ecbf3d31a
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java
@@ -0,0 +1,220 @@
+/*
+ *
+ * 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.server.configuration;
+
+import org.apache.qpid.url.AMQBindingURL;
+import org.apache.qpid.url.URLSyntaxException;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.AMQException;
+import org.apache.log4j.Logger;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.XMLConfiguration;
+
+
+import java.util.Collection;
+
+public class VirtualHostConfiguration
+{
+ private static final Logger _logger = Logger.getLogger(VirtualHostConfiguration.class);
+
+ XMLConfiguration _config;
+
+ private static final String XML_VIRTUALHOST = "virtualhost";
+ private static final String XML_PATH = "path";
+ private static final String XML_BIND = "bind";
+ private static final String XML_VIRTUALHOST_PATH = "virtualhost.path";
+ private static final String XML_VIRTUALHOST_BIND = "virtualhost.bind";
+
+
+ public VirtualHostConfiguration(String configFile) throws ConfigurationException
+ {
+ _logger.info("Loading Config file:" + configFile);
+
+ _config = new XMLConfiguration(configFile);
+
+ if (_config.getProperty(XML_VIRTUALHOST_PATH) == null)
+ {
+ throw new ConfigurationException(
+ "Virtualhost Configuration document does not contain a valid virtualhost.");
+ }
+ }
+
+ public void performBindings() throws AMQException, ConfigurationException, URLSyntaxException
+ {
+ Object prop = _config.getProperty(XML_VIRTUALHOST_PATH);
+
+ if (prop instanceof Collection)
+ {
+ _logger.debug("Number of VirtualHosts: " + ((Collection) prop).size());
+
+ int virtualhosts = ((Collection) prop).size();
+ for (int vhost = 0; vhost < virtualhosts; vhost++)
+ {
+ loadVirtualHost(vhost);
+ }
+ }
+ else
+ {
+ loadVirtualHost(-1);
+ }
+ }
+
+ private void loadVirtualHost(int index) throws AMQException, ConfigurationException, URLSyntaxException
+ {
+ String path = XML_VIRTUALHOST;
+
+ if (index != -1)
+ {
+ path = path + "(" + index + ")";
+ }
+
+ Object prop = _config.getProperty(path + "." + XML_PATH);
+
+ if (prop == null)
+ {
+ prop = _config.getProperty(path + "." + XML_BIND);
+ String error = "Virtual Host not defined for binding";
+
+ if (prop != null)
+ {
+ if (prop instanceof Collection)
+ {
+ error += "s";
+ }
+
+ error += ": " + prop;
+ }
+
+ throw new ConfigurationException(error);
+ }
+
+ _logger.info("VirtualHost:'" + prop + "'");
+
+ prop = _config.getProperty(path + "." + XML_BIND);
+ if (prop instanceof Collection)
+ {
+ int bindings = ((Collection) prop).size();
+ _logger.debug("Number of Bindings: " + bindings);
+ for (int dest = 0; dest < bindings; dest++)
+ {
+ loadBinding(path, dest);
+ }
+ }
+ else
+ {
+ loadBinding(path, -1);
+ }
+ }
+
+ private void loadBinding(String rootpath, int index) throws AMQException, ConfigurationException, URLSyntaxException
+ {
+ String path = rootpath + "." + XML_BIND;
+ if (index != -1)
+ {
+ path = path + "(" + index + ")";
+ }
+
+ String bindingString = _config.getString(path);
+
+ AMQBindingURL binding = new AMQBindingURL(bindingString);
+
+ _logger.debug("Loaded Binding:" + binding);
+
+ try
+ {
+ bind(binding);
+ }
+ catch (AMQException amqe)
+ {
+ _logger.info("Unable to bind url: " + binding);
+ throw amqe;
+ }
+ }
+
+ private void bind(AMQBindingURL binding) throws AMQException, ConfigurationException
+ {
+
+ String queueName = binding.getQueueName();
+
+ // This will occur if the URL is a Topic
+ if (queueName == null)
+ {
+ //todo register valid topic
+ ///queueName = binding.getDestinationName();
+ throw new AMQException("Topics cannot be bound. TODO Register valid topic");
+ }
+
+ //Get references to Broker Registries
+ QueueRegistry queueRegistry = ApplicationRegistry.getInstance().getQueueRegistry();
+ MessageStore messageStore = ApplicationRegistry.getInstance().getMessageStore();
+ ExchangeRegistry exchangeRegistry = ApplicationRegistry.getInstance().getExchangeRegistry();
+
+ synchronized (queueRegistry)
+ {
+ AMQQueue queue = queueRegistry.getQueue(queueName);
+
+ if (queue == null)
+ {
+ _logger.info("Queue '" + binding.getQueueName() + "' does not exists. Creating.");
+
+ queue = new AMQQueue(queueName,
+ Boolean.parseBoolean(binding.getOption(AMQBindingURL.OPTION_DURABLE)),
+ null /* These queues will have no owner */,
+ false /* Therefore autodelete makes no sence */, queueRegistry);
+
+ if (queue.isDurable())
+ {
+ messageStore.createQueue(queue);
+ }
+
+ queueRegistry.registerQueue(queue);
+ }
+ else
+ {
+ _logger.info("Queue '" + binding.getQueueName() + "' already exists not creating.");
+ }
+
+ Exchange defaultExchange = exchangeRegistry.getExchange(binding.getExchangeName());
+ synchronized (defaultExchange)
+ {
+ if (defaultExchange == null)
+ {
+ throw new ConfigurationException("Attempt to bind queue to unknown exchange:" + binding);
+ }
+
+ defaultExchange.registerQueue(queue.getName(), queue, null);
+
+ if (binding.getRoutingKey() == null || binding.getRoutingKey().equals(""))
+ {
+ throw new ConfigurationException("Unknown binding not specified on url:" + binding);
+ }
+
+ queue.bind(binding.getRoutingKey(), defaultExchange);
+ }
+ _logger.info("Queue '" + queue.getName() + "' bound to exchange:" + binding.getExchangeName() + " RK:'" + binding.getRoutingKey() + "'");
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java
new file mode 100644
index 0000000000..69f5e862d6
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java
@@ -0,0 +1,139 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.management.AMQManagedObject;
+import org.apache.qpid.server.management.Managable;
+import org.apache.qpid.server.management.ManagedObject;
+
+import javax.management.NotCompliantMBeanException;
+
+public abstract class AbstractExchange implements Exchange, Managable
+{
+ private String _name;
+
+ protected boolean _durable;
+
+ protected int _ticket;
+
+ protected ExchangeMBean _exchangeMbean;
+
+ /**
+ * Whether the exchange is automatically deleted once all queues have detached from it
+ */
+ protected boolean _autoDelete;
+
+ /**
+ * Abstract MBean class. This has some of the methods implemented from
+ * management intrerface for exchanges. Any implementaion of an
+ * Exchange MBean should extend this class.
+ */
+ protected abstract class ExchangeMBean extends AMQManagedObject implements ManagedExchange
+ {
+ public ExchangeMBean() throws NotCompliantMBeanException
+ {
+ super(ManagedExchange.class, ManagedExchange.TYPE);
+ }
+
+ public String getObjectInstanceName()
+ {
+ return _name;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public Integer getTicketNo()
+ {
+ return _ticket;
+ }
+
+ public boolean isDurable()
+ {
+ return _durable;
+ }
+
+ public boolean isAutoDelete()
+ {
+ return _autoDelete;
+ }
+
+ } // End of MBean class
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ /**
+ * Concrete exchanges must implement this method in order to create the managed representation. This is
+ * called during initialisation (template method pattern).
+ * @return the MBean
+ */
+ protected abstract ExchangeMBean createMBean() throws AMQException;
+
+ public void initialise(String name, boolean durable, int ticket, boolean autoDelete) throws AMQException
+ {
+ _name = name;
+ _durable = durable;
+ _autoDelete = autoDelete;
+ _ticket = ticket;
+ _exchangeMbean = createMBean();
+ _exchangeMbean.register();
+ }
+
+ public boolean isDurable()
+ {
+ return _durable;
+ }
+
+ public boolean isAutoDelete()
+ {
+ return _autoDelete;
+ }
+
+ public int getTicket()
+ {
+ return _ticket;
+ }
+
+ public void close() throws AMQException
+ {
+ if (_exchangeMbean != null)
+ {
+ _exchangeMbean.unregister();
+ }
+ }
+
+ public String toString()
+ {
+ return getClass().getName() + "[" + getName() +"]";
+ }
+
+ public ManagedObject getManagedObject()
+ {
+ return _exchangeMbean;
+ }
+
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java
new file mode 100644
index 0000000000..0c73e0f9f0
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultExchangeFactory implements ExchangeFactory
+{
+ private static final Logger _logger = Logger.getLogger(DefaultExchangeFactory.class);
+
+ private Map<String, Class<? extends Exchange>> _exchangeClassMap = new HashMap<String, Class<? extends Exchange>>();
+
+ public DefaultExchangeFactory()
+ {
+ _exchangeClassMap.put("direct", org.apache.qpid.server.exchange.DestNameExchange.class);
+ _exchangeClassMap.put("topic", org.apache.qpid.server.exchange.DestWildExchange.class);
+ _exchangeClassMap.put("headers", org.apache.qpid.server.exchange.HeadersExchange.class);
+ }
+
+ public Exchange createExchange(String exchange, String type, boolean durable, boolean autoDelete,
+ int ticket)
+ throws AMQException
+ {
+ Class<? extends Exchange> exchClass = _exchangeClassMap.get(type);
+ if (exchClass == null)
+ {
+ throw new AMQException(_logger, "Unknown exchange type: " + type);
+ }
+ try
+ {
+ Exchange e = exchClass.newInstance();
+ e.initialise(exchange, durable, ticket, autoDelete);
+ return e;
+ }
+ catch (InstantiationException e)
+ {
+ throw new AMQException(_logger, "Unable to create exchange: " + e, e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new AMQException(_logger, "Unable to create exchange: " + e, e);
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java
new file mode 100644
index 0000000000..eb9d1acb59
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java
@@ -0,0 +1,95 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.protocol.ExchangeInitialiser;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.log4j.Logger;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public class DefaultExchangeRegistry implements ExchangeRegistry
+{
+ private static final Logger _log = Logger.getLogger(DefaultExchangeRegistry.class);
+
+ /**
+ * Maps from exchange name to exchange instance
+ */
+ private ConcurrentMap<String, Exchange> _exchangeMap = new ConcurrentHashMap<String, Exchange>();
+
+ public DefaultExchangeRegistry(ExchangeFactory exchangeFactory)
+ {
+ //create 'standard' exchanges:
+ try
+ {
+ new ExchangeInitialiser().initialise(exchangeFactory, this);
+ }
+ catch(AMQException e)
+ {
+ _log.error("Failed to initialise exchanges: ", e);
+ }
+ }
+
+ public void registerExchange(Exchange exchange)
+ {
+ _exchangeMap.put(exchange.getName(), exchange);
+ }
+
+ public void unregisterExchange(String name, boolean inUse) throws AMQException
+ {
+ // TODO: check inUse argument
+ Exchange e = _exchangeMap.remove(name);
+ if (e != null)
+ {
+ e.close();
+ }
+ else
+ {
+ throw new AMQException("Unknown exchange " + name);
+ }
+ }
+
+ public Exchange getExchange(String name)
+ {
+ return _exchangeMap.get(name);
+ }
+
+ /**
+ * Routes content through exchanges, delivering it to 1 or more queues.
+ * @param payload
+ * @throws AMQException if something goes wrong delivering data
+ */
+ public void routeContent(AMQMessage payload) throws AMQException
+ {
+ final String exchange = payload.getPublishBody().exchange;
+ final Exchange exch = _exchangeMap.get(exchange);
+ // there is a small window of opportunity for the exchange to be deleted in between
+ // the JmsPublish being received (where the exchange is validated) and the final
+ // content body being received (which triggers this method)
+ if (exch == null)
+ {
+ throw new AMQException("Exchange '" + exchange + "' does not exist");
+ }
+ exch.route(payload);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/DestNameExchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/DestNameExchange.java
new file mode 100644
index 0000000000..085d0aad19
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DestNameExchange.java
@@ -0,0 +1,223 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+import javax.management.JMException;
+import javax.management.MBeanException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.openmbean.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class DestNameExchange extends AbstractExchange
+{
+ private static final Logger _logger = Logger.getLogger(DestNameExchange.class);
+
+ /**
+ * Maps from queue name to queue instances
+ */
+ private final Index _index = new Index();
+
+ /**
+ * MBean class implementing the management interfaces.
+ */
+ @MBeanDescription("Management Bean for Direct Exchange")
+ private final class DestNameExchangeMBean extends ExchangeMBean
+ {
+ private String[] _bindingItemNames = {"BindingKey", "QueueNames"};
+ private String[] _bindingItemDescriptions = {"Binding key", "Queue Names"};
+ private String[] _bindingItemIndexNames = {"BindingKey"};
+ private OpenType[] _bindingItemTypes = new OpenType[2];
+
+ private CompositeType _bindingDataType = null;
+ private TabularType _bindinglistDataType = null;
+ private TabularDataSupport _bindingList = null;
+
+ @MBeanConstructor("Creates an MBean for AMQ direct exchange")
+ public DestNameExchangeMBean() throws NotCompliantMBeanException
+ {
+ super();
+ init();
+ }
+
+ /**
+ * initialises the OpenType objects.
+ */
+ private void init()
+ {
+ try
+ {
+ _bindingItemTypes[0] = SimpleType.STRING;
+ //_bindingItemTypes[1] = ArrayType.getArrayType(SimpleType.STRING);
+ _bindingItemTypes[1] = new ArrayType(1, SimpleType.STRING);
+
+ _bindingDataType = new CompositeType("QueueBinding",
+ "Binding key and bound Queue names",
+ _bindingItemNames,
+ _bindingItemDescriptions,
+ _bindingItemTypes);
+ _bindinglistDataType = new TabularType("Bindings",
+ "List of queue bindings for " + getName() ,
+ _bindingDataType,
+ _bindingItemIndexNames);
+ }
+ catch(OpenDataException ex)
+ {
+ //It should never occur.
+ _logger.error("OpenDataTypes could not be created.", ex);
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public TabularData viewBindings()
+ throws OpenDataException
+ {
+ Map<String, List<AMQQueue>> bindings = _index.getBindingsMap();
+ _bindingList = new TabularDataSupport(_bindinglistDataType);
+
+ for (Map.Entry<String, List<AMQQueue>> entry : bindings.entrySet())
+ {
+ String key = entry.getKey();
+ List<String> queueList = new ArrayList<String>();
+
+ List<AMQQueue> queues = entry.getValue();
+ for (AMQQueue q : queues)
+ {
+ queueList.add(q.getName());
+ }
+
+ Object[] bindingItemValues = {key, queueList.toArray(new String[0])};
+ CompositeData bindingData = new CompositeDataSupport(_bindingDataType,
+ _bindingItemNames,
+ bindingItemValues);
+ _bindingList.put(bindingData);
+ }
+
+ return _bindingList;
+ }
+
+ public void createBinding(String queueName, String binding)
+ throws JMException
+ {
+ AMQQueue queue = ApplicationRegistry.getInstance().getQueueRegistry().getQueue(queueName);
+
+ if (queue == null)
+ {
+ throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange.");
+ }
+
+ try
+ {
+ registerQueue(binding, queue, null);
+ queue.bind(binding, DestNameExchange.this);
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex);
+ }
+ }
+
+ }// End of MBean class
+
+
+ protected ExchangeMBean createMBean() throws AMQException
+ {
+ try
+ {
+ return new DestNameExchangeMBean();
+ }
+ catch (NotCompliantMBeanException ex)
+ {
+ _logger.error("Exception occured in creating the DestNameExchenge", ex);
+ throw new AMQException("Exception occured in creating the DestNameExchenge", ex);
+ }
+ }
+
+ public void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ assert queue != null;
+ assert routingKey != null;
+ if (!_index.add(routingKey, queue))
+ {
+ _logger.debug("Queue " + queue + " is already registered with routing key " + routingKey);
+ }
+ else
+ {
+ _logger.debug("Binding queue " + queue + " with routing key " + routingKey
+ + " to exchange " + this);
+ }
+ }
+
+ public void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException
+ {
+ assert queue != null;
+ assert routingKey != null;
+
+ if (!_index.remove(routingKey, queue))
+ {
+ throw new AMQException("Queue " + queue + " was not registered with exchange " + this.getName() +
+ " with routing key " + routingKey + ". No queue was registered with that routing key");
+ }
+ }
+
+ public void route(AMQMessage payload) throws AMQException
+ {
+ BasicPublishBody publishBody = payload.getPublishBody();
+
+ final String routingKey = publishBody.routingKey;
+ final List<AMQQueue> queues = _index.get(routingKey);
+ if (queues == null || queues.isEmpty())
+ {
+ String msg = "Routing key " + routingKey + " is not known to " + this;
+ if (publishBody.mandatory)
+ {
+ throw new NoRouteException(msg, payload);
+ }
+ else
+ {
+ _logger.warn(msg);
+ }
+ }
+ else
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Publishing message to queue " + queues);
+ }
+
+ for (AMQQueue q : queues)
+ {
+ q.deliver(payload);
+ }
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java
new file mode 100644
index 0000000000..f6abd53076
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java
@@ -0,0 +1,226 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.management.MBeanConstructor;
+
+import javax.management.openmbean.*;
+import javax.management.JMException;
+import javax.management.MBeanException;
+import javax.management.NotCompliantMBeanException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class DestWildExchange extends AbstractExchange
+{
+ private static final Logger _logger = Logger.getLogger(DestWildExchange.class);
+
+ private ConcurrentHashMap<String, List<AMQQueue>> _routingKey2queues = new ConcurrentHashMap<String, List<AMQQueue>>();
+
+ /**
+ * DestWildExchangeMBean class implements the management interface for the
+ * Topic exchanges.
+ */
+ @MBeanDescription("Management Bean for Topic Exchange")
+ private final class DestWildExchangeMBean extends ExchangeMBean
+ {
+ private String[] _bindingItemNames = {"BindingKey", "QueueNames"};
+ private String[] _bindingItemDescriptions = {"Binding key", "Queue Names"};
+ private String[] _bindingItemIndexNames = {"BindingKey"};
+ private OpenType[] _bindingItemTypes = new OpenType[2];
+
+ private CompositeType _bindingDataType = null;
+ private TabularType _bindinglistDataType = null;
+ private TabularDataSupport _bindingList = null;
+
+ @MBeanConstructor("Creates an MBean for AMQ topic exchange")
+ public DestWildExchangeMBean() throws NotCompliantMBeanException
+ {
+ super();
+ init();
+ }
+
+ /**
+ * initialises the OpenType objects.
+ */
+ private void init()
+ {
+ try
+ {
+ _bindingItemTypes[0] = SimpleType.STRING;
+ _bindingItemTypes[1] = new ArrayType(1, SimpleType.STRING);
+
+ _bindingDataType = new CompositeType("QueueBinding",
+ "Binding key and bound Queue names",
+ _bindingItemNames,
+ _bindingItemDescriptions,
+ _bindingItemTypes);
+ _bindinglistDataType = new TabularType("Bindings",
+ "List of queue bindings for " + getName(),
+ _bindingDataType,
+ _bindingItemIndexNames);
+ }
+ catch(OpenDataException ex)
+ {
+ //It should never occur.
+ _logger.error("OpenDataTypes could not be created.", ex);
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public TabularData viewBindings()
+ throws OpenDataException
+ {
+ _bindingList = new TabularDataSupport(_bindinglistDataType);
+
+ for (Map.Entry<String, List<AMQQueue>> entry : _routingKey2queues.entrySet())
+ {
+ String key = entry.getKey();
+ List<String> queueList = new ArrayList<String>();
+
+ List<AMQQueue> queues = entry.getValue();
+ for (AMQQueue q : queues)
+ {
+ queueList.add(q.getName());
+ }
+
+ Object[] bindingItemValues = {key, queueList.toArray(new String[0])};
+ CompositeData bindingData = new CompositeDataSupport(_bindingDataType,
+ _bindingItemNames,
+ bindingItemValues);
+ _bindingList.put(bindingData);
+ }
+
+ return _bindingList;
+ }
+
+ public void createBinding(String queueName, String binding)
+ throws JMException
+ {
+ AMQQueue queue = ApplicationRegistry.getInstance().getQueueRegistry().getQueue(queueName);
+
+ if (queue == null)
+ throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange.");
+
+ try
+ {
+ registerQueue(binding, queue, null);
+ queue.bind(binding, DestWildExchange.this);
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex);
+ }
+ }
+
+ } // End of MBean class
+
+
+ public void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ assert queue != null;
+ assert routingKey != null;
+ // we need to use putIfAbsent, which is an atomic operation, to avoid a race condition
+ List<AMQQueue> queueList = _routingKey2queues.putIfAbsent(routingKey, new CopyOnWriteArrayList<AMQQueue>());
+ // if we got null back, no previous value was associated with the specified routing key hence
+ // we need to read back the new value just put into the map
+ if (queueList == null)
+ {
+ queueList = _routingKey2queues.get(routingKey);
+ }
+ if (!queueList.contains(queue))
+ {
+ queueList.add(queue);
+ }
+ else if(_logger.isDebugEnabled())
+ {
+ _logger.debug("Queue " + queue + " is already registered with routing key " + routingKey);
+ }
+
+ }
+
+ public void route(AMQMessage payload) throws AMQException
+ {
+ BasicPublishBody publishBody = payload.getPublishBody();
+
+ final String routingKey = publishBody.routingKey;
+ List<AMQQueue> queues = _routingKey2queues.get(routingKey);
+ // if we have no registered queues we have nothing to do
+ // TODO: add support for the immediate flag
+ if (queues == null)
+ {
+ //todo Check for valid topic - mritchie
+ return;
+ }
+
+ for (AMQQueue q : queues)
+ {
+ // TODO: modify code generator to add clone() method then clone the deliver body
+ // without this addition we have a race condition - we will be modifying the body
+ // before the encoder has encoded the body for delivery
+ q.deliver(payload);
+ }
+ }
+
+ public void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException
+ {
+ assert queue != null;
+ assert routingKey != null;
+
+ List<AMQQueue> queues = _routingKey2queues.get(routingKey);
+ if (queues == null)
+ {
+ throw new AMQException("Queue " + queue + " was not registered with exchange " + this.getName() +
+ " with routing key " + routingKey + ". No queue was registered with that routing key");
+
+ }
+ boolean removedQ = queues.remove(queue);
+ if (!removedQ)
+ {
+ throw new AMQException("Queue " + queue + " was not registered with exchange " + this.getName() +
+ " with routing key " + routingKey);
+ }
+ }
+
+ protected ExchangeMBean createMBean() throws AMQException
+ {
+ try
+ {
+ return new DestWildExchangeMBean();
+ }
+ catch (NotCompliantMBeanException ex)
+ {
+ _logger.error("Exception occured in creating the DestWildExchenge", ex);
+ throw new AMQException("Exception occured in creating the DestWildExchenge", ex);
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java
new file mode 100644
index 0000000000..787d0eddfd
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java
@@ -0,0 +1,50 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.AMQMessage;
+
+public interface Exchange
+{
+ String getName();
+
+ void initialise(String name, boolean durable, int ticket, boolean autoDelete) throws AMQException;
+
+ boolean isDurable();
+
+ /**
+ * @return true if the exchange will be deleted after all queues have been detached
+ */
+ boolean isAutoDelete();
+
+ int getTicket();
+
+ void close() throws AMQException;
+
+ void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException;
+
+ void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException;
+
+ void route(AMQMessage message) throws AMQException;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java
new file mode 100644
index 0000000000..37ba883bc3
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.qpid.AMQException;
+
+
+public interface ExchangeFactory
+{
+ Exchange createExchange(String exchange, String type, boolean durable, boolean autoDelete,
+ int ticket)
+ throws AMQException;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java
new file mode 100644
index 0000000000..6b2891c573
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.qpid.AMQException;
+
+public class ExchangeInUseException extends AMQException
+{
+ public ExchangeInUseException(String exchangeName)
+ {
+ super("Exchange " + exchangeName + " is currently in use");
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java
new file mode 100644
index 0000000000..dcc50a796a
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java
@@ -0,0 +1,41 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+
+
+public interface ExchangeRegistry extends MessageRouter
+{
+ void registerExchange(Exchange exchange);
+
+ /**
+ * Unregister an exchange
+ * @param name name of the exchange to delete
+ * @param inUse if true, do NOT delete the exchange if it is in use (has queues bound to it)
+ * @throws ExchangeInUseException when the exchange cannot be deleted because it is in use
+ * @throws AMQException
+ */
+ void unregisterExchange(String name, boolean inUse) throws ExchangeInUseException, AMQException;
+
+ Exchange getExchange(String name);
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java
new file mode 100644
index 0000000000..b058211288
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java
@@ -0,0 +1,145 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.log4j.Logger;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Defines binding and matching based on a set of headers.
+ */
+class HeadersBinding
+{
+ private static final Logger _logger = Logger.getLogger(HeadersBinding.class);
+
+ private final Map _mappings = new HashMap();
+ private final Set<Object> required = new HashSet<Object>();
+ private final Set<Map.Entry> matches = new HashSet<Map.Entry>();
+ private boolean matchAny;
+
+ /**
+ * Creates a binding for a set of mappings. Those mappings whose value is
+ * null or the empty string are assumed only to be required headers, with
+ * no constraint on the value. Those with a non-null value are assumed to
+ * define a required match of value.
+ * @param mappings the defined mappings this binding should use
+ */
+ HeadersBinding(Map mappings)
+ {
+ //noinspection unchecked
+ this(mappings == null ? new HashSet<Map.Entry>() : mappings.entrySet());
+ _mappings.putAll(mappings);
+ }
+
+ private HeadersBinding(Set<Map.Entry> entries)
+ {
+ for (Map.Entry e : entries)
+ {
+ if (isSpecial(e.getKey()))
+ {
+ processSpecial((String) e.getKey(), e.getValue());
+ }
+ else if (e.getValue() == null || e.getValue().equals(""))
+ {
+ required.add(e.getKey());
+ }
+ else
+ {
+ matches.add(e);
+ }
+ }
+ }
+
+ protected Map getMappings()
+ {
+ return _mappings;
+ }
+
+ /**
+ * Checks whether the supplied headers match the requirements of this binding
+ * @param headers the headers to check
+ * @return true if the headers define any required keys and match any required
+ * values
+ */
+ public boolean matches(Map headers)
+ {
+ if(headers == null)
+ {
+ return required.isEmpty() && matches.isEmpty();
+ }
+ else
+ {
+ return matchAny ? or(headers) : and(headers);
+ }
+ }
+
+ private boolean and(Map headers)
+ {
+ //need to match all the defined mapping rules:
+ return headers.keySet().containsAll(required)
+ && headers.entrySet().containsAll(matches);
+ }
+
+ private boolean or(Map headers)
+ {
+ //only need to match one mapping rule:
+ return !Collections.disjoint(headers.keySet(), required)
+ || !Collections.disjoint(headers.entrySet(), matches);
+ }
+
+ private void processSpecial(String key, Object value)
+ {
+ if("X-match".equalsIgnoreCase(key))
+ {
+ matchAny = isAny(value);
+ }
+ else
+ {
+ _logger.warn("Ignoring special header: " + key);
+ }
+ }
+
+ private boolean isAny(Object value)
+ {
+ if(value instanceof String)
+ {
+ if("any".equalsIgnoreCase((String) value)) return true;
+ if("all".equalsIgnoreCase((String) value)) return false;
+ }
+ _logger.warn("Ignoring unrecognised match type: " + value);
+ return false;//default to all
+ }
+
+ static boolean isSpecial(Object key)
+ {
+ return key instanceof String && isSpecial((String) key);
+ }
+
+ static boolean isSpecial(String key)
+ {
+ return key.startsWith("X-") || key.startsWith("x-");
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java
new file mode 100644
index 0000000000..961d4ddf4c
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java
@@ -0,0 +1,271 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+import javax.management.JMException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.openmbean.*;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * An exchange that binds queues based on a set of required headers and header values
+ * and routes messages to these queues by matching the headers of the message against
+ * those with which the queues were bound.
+ * <p/>
+ * <pre>
+ * The Headers Exchange
+ *
+ * Routes messages according to the value/presence of fields in the message header table.
+ * (Basic and JMS content has a content header field called "headers" that is a table of
+ * message header fields).
+ *
+ * class = "headers"
+ * routing key is not used
+ *
+ * Has the following binding arguments:
+ *
+ * the X-match field - if "all", does an AND match (used for GRM), if "any", does an OR match.
+ * other fields prefixed with "X-" are ignored (and generate a console warning message).
+ * a field with no value or empty value indicates a match on presence only.
+ * a field with a value indicates match on field presence and specific value.
+ *
+ * Standard instances:
+ *
+ * amq.match - pub/sub on field content/value
+ * </pre>
+ */
+public class HeadersExchange extends AbstractExchange
+{
+ private static final Logger _logger = Logger.getLogger(HeadersExchange.class);
+
+ private final List<Registration> _bindings = new CopyOnWriteArrayList<Registration>();
+
+ /**
+ * HeadersExchangeMBean class implements the management interface for the
+ * Header Exchanges.
+ */
+ @MBeanDescription("Management Bean for Headers Exchange")
+ private final class HeadersExchangeMBean extends ExchangeMBean
+ {
+ private String[] _bindingItemNames = {"Queue", "HeaderBinding"};
+ private String[] _bindingItemDescriptions = {"Queue Name", "Header attribute bindings"};
+ private String[] _bindingItemIndexNames = {"HeaderBinding"};
+ private OpenType[] _bindingItemTypes = new OpenType[2];
+
+ private CompositeType _bindingDataType = null;
+ private TabularType _bindinglistDataType = null;
+ private TabularDataSupport _bindingList = null;
+
+ @MBeanConstructor("Creates an MBean for AMQ Headers exchange")
+ public HeadersExchangeMBean() throws NotCompliantMBeanException
+ {
+ super();
+ init();
+ }
+ /**
+ * initialises the OpenType objects.
+ */
+ private void init()
+ {
+ try
+ {
+ _bindingItemTypes[0] = SimpleType.STRING;
+ _bindingItemTypes[1] = new ArrayType(1, SimpleType.STRING);
+
+ _bindingDataType = new CompositeType("QueueAndHeaderAttributesBinding",
+ "Queue name and header bindings",
+ _bindingItemNames,
+ _bindingItemDescriptions,
+ _bindingItemTypes);
+ _bindinglistDataType = new TabularType("HeaderBindings",
+ "List of queue bindings for " + getName(),
+ _bindingDataType,
+ _bindingItemIndexNames);
+ }
+ catch(OpenDataException ex)
+ {
+ //It should never occur.
+ _logger.error("OpenDataTypes could not be created.", ex);
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public TabularData viewBindings()
+ throws OpenDataException
+ {
+ _bindingList = new TabularDataSupport(_bindinglistDataType);
+ for (Iterator<Registration> itr = _bindings.iterator(); itr.hasNext();)
+ {
+ Registration registration = itr.next();
+ String queueName = registration.queue.getName();
+
+ HeadersBinding headers = registration.binding;
+ Map<Object, Object> headerMappings = headers.getMappings();
+ List<String> mappingList = new ArrayList<String>();
+
+ for (Map.Entry<Object, Object> en : headerMappings.entrySet())
+ {
+ String key = en.getKey().toString();
+ String value = en.getValue().toString();
+
+ mappingList.add(key + "=" + value);
+ }
+
+ Object[] bindingItemValues = {queueName, mappingList.toArray(new String[0])};
+ CompositeData bindingData = new CompositeDataSupport(_bindingDataType,
+ _bindingItemNames,
+ bindingItemValues);
+ _bindingList.put(bindingData);
+ }
+
+ return _bindingList;
+ }
+
+ /**
+ * Creates bindings. Binding pattern is as follows-
+ * <attributename>=<value>,<attributename>=<value>,...
+ * @param queueName
+ * @param binding
+ * @throws JMException
+ */
+ public void createBinding(String queueName, String binding)
+ throws JMException
+ {
+ AMQQueue queue = ApplicationRegistry.getInstance().getQueueRegistry().getQueue(queueName);
+
+ if (queue == null)
+ {
+ throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange.");
+ }
+
+ String[] bindings = binding.split(",");
+ FieldTable fieldTable = new FieldTable();
+ for (int i = 0; i < bindings.length; i++)
+ {
+ String[] keyAndValue = bindings[i].split("=");
+ if (keyAndValue == null || keyAndValue.length < 2)
+ {
+ throw new JMException("Format for headers binding should be \"<attribute1>=<value1>,<attribute2>=<value2>\" ");
+ }
+ fieldTable.put(keyAndValue[0], keyAndValue[1]);
+ }
+
+ _bindings.add(new Registration(new HeadersBinding(fieldTable), queue));
+ }
+
+ } // End of MBean class
+
+ public void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ _logger.debug("Exchange " + getName() + ": Binding " + queue.getName() + " with " + args);
+ _bindings.add(new Registration(new HeadersBinding(args), queue));
+ }
+
+ public void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException
+ {
+ _logger.debug("Exchange " + getName() + ": Unbinding " + queue.getName());
+ _bindings.remove(new Registration(null, queue));
+ }
+
+ public void route(AMQMessage payload) throws AMQException
+ {
+ Map headers = getHeaders(payload.getContentHeaderBody());
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Exchange " + getName() + ": routing message with headers " + headers);
+ }
+ boolean delivered = false;
+ for (Registration e : _bindings)
+ {
+ if (e.binding.matches(headers))
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Exchange " + getName() + ": delivering message with headers " +
+ headers + " to " + e.queue.getName());
+ }
+ e.queue.deliver(payload);
+ delivered = true;
+ }
+ }
+ if (!delivered)
+ {
+ _logger.warn("Exchange " + getName() + ": message not routable.");
+ }
+ }
+
+ protected Map getHeaders(ContentHeaderBody contentHeaderFrame)
+ {
+ //what if the content type is not 'basic'? 'file' and 'stream' content classes also define headers,
+ //but these are not yet implemented.
+ return ((BasicContentHeaderProperties) contentHeaderFrame.properties).getHeaders();
+ }
+
+ protected ExchangeMBean createMBean() throws AMQException
+ {
+ try
+ {
+ return new HeadersExchangeMBean();
+ }
+ catch (NotCompliantMBeanException ex)
+ {
+ _logger.error("Exception occured in creating the HeadersExchangeMBean", ex);
+ throw new AMQException("Exception occured in creating the HeadersExchangeMBean", ex);
+ }
+ }
+
+ private static class Registration
+ {
+ private final HeadersBinding binding;
+ private final AMQQueue queue;
+
+ Registration(HeadersBinding binding, AMQQueue queue)
+ {
+ this.binding = binding;
+ this.queue = queue;
+ }
+
+ public int hashCode()
+ {
+ return queue.hashCode();
+ }
+
+ public boolean equals(Object o)
+ {
+ return o instanceof Registration && ((Registration) o).queue.equals(queue);
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java
new file mode 100644
index 0000000000..fb48729c9e
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java
@@ -0,0 +1,88 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.qpid.server.queue.AMQQueue;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * An index of queues against routing key. Allows multiple queues to be stored
+ * against the same key. Used in the DestNameExchange.
+ */
+class Index
+{
+ private ConcurrentMap<String, List<AMQQueue>> _index
+ = new ConcurrentHashMap<String, List<AMQQueue>>();
+
+ boolean add(String key, AMQQueue queue)
+ {
+ List<AMQQueue> queues = _index.get(key);
+ if(queues == null)
+ {
+ queues = new CopyOnWriteArrayList<AMQQueue>();
+ //next call is atomic, so there is no race to create the list
+ List<AMQQueue> active = _index.putIfAbsent(key, queues);
+ if(active != null)
+ {
+ //someone added the new one in faster than we did, so use theirs
+ queues = active;
+ }
+ }
+ if(queues.contains(queue))
+ {
+ return false;
+ }
+ else
+ {
+ return queues.add(queue);
+ }
+ }
+
+ boolean remove(String key, AMQQueue queue)
+ {
+ List<AMQQueue> queues = _index.get(key);
+ if (queues != null)
+ {
+ boolean removed = queues.remove(queue);
+ if (queues.size() == 0)
+ {
+ _index.remove(key);
+ }
+ return removed;
+ }
+ return false;
+ }
+
+ List<AMQQueue> get(String key)
+ {
+ return _index.get(key);
+ }
+
+ Map<String, List<AMQQueue>> getBindingsMap()
+ {
+ return _index;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java
new file mode 100644
index 0000000000..3c2c105186
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java
@@ -0,0 +1,93 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.qpid.server.management.MBeanAttribute;
+import org.apache.qpid.server.management.MBeanOperation;
+import org.apache.qpid.server.management.MBeanOperationParameter;
+
+import javax.management.openmbean.TabularData;
+import javax.management.JMException;
+import javax.management.MBeanOperationInfo;
+import java.io.IOException;
+
+/**
+ * The management interface exposed to allow management of an Exchange.
+ * @author Robert J. Greig
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+public interface ManagedExchange
+{
+ static final String TYPE = "Exchange";
+
+ /**
+ * Returns the name of the managed exchange.
+ * @return the name of the exchange.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="Name", description="Name of exchange")
+ String getName() throws IOException;
+
+ @MBeanAttribute(name="TicketNo", description="Exchange Ticket No")
+ Integer getTicketNo() throws IOException;
+
+ /**
+ * Tells if the exchange is durable or not.
+ * @return true if the exchange is durable.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="Durable", description="true if Exchange is durable")
+ boolean isDurable() throws IOException;
+
+ /**
+ * Tells if the exchange is set for autodelete or not.
+ * @return true if the exchange is set as autodelete.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="AutoDelete", description="true if Exchange is AutoDelete")
+ boolean isAutoDelete() throws IOException;
+
+ // Operations
+
+ /**
+ * Returns all the bindings this exchange has with the queues.
+ * @return the bindings with the exchange.
+ * @throws IOException
+ * @throws JMException
+ */
+ @MBeanOperation(name="viewBindings", description="view the queue bindings for this exchange")
+ TabularData viewBindings() throws IOException, JMException;
+
+ /**
+ * Creates new binding with the given queue and binding.
+ * @param queueName
+ * @param binding
+ * @throws JMException
+ */
+ @MBeanOperation(name="createBinding",
+ description="create a new binding with this exchange",
+ impact= MBeanOperationInfo.ACTION)
+ void createBinding(@MBeanOperationParameter(name="queue name", description="queue name") String queueName,
+ @MBeanOperationParameter(name="binding", description="queue binding")String binding)
+ throws JMException;
+
+} \ No newline at end of file
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java
new file mode 100644
index 0000000000..70b80f65da
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java
@@ -0,0 +1,39 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.AMQException;
+
+/**
+ * Separated out from the ExchangeRegistry interface to allow components
+ * that use only this part to have a dependency with a reduced footprint.
+ *
+ */
+public interface MessageRouter
+{
+ /**
+ * Routes content through exchanges, delivering it to 1 or more queues.
+ * @param message the message to be routed
+ * @throws org.apache.qpid.AMQException if something goes wrong delivering data
+ */
+ void routeContent(AMQMessage message) throws AMQException;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java
new file mode 100644
index 0000000000..fb0a330263
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * 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.server.exchange;
+
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.protocol.AMQConstant;
+
+/**
+ * Thrown by an exchange if there is no way to route a message with the
+ * mandatory flag set.
+ */
+public class NoRouteException extends RequiredDeliveryException
+{
+ public NoRouteException(String msg, AMQMessage message)
+ {
+ super(msg, message);
+ }
+
+ public int getReplyCode()
+ {
+ return AMQConstant.NO_ROUTE.getCode();
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java
new file mode 100644
index 0000000000..b2b7a21296
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java
@@ -0,0 +1,55 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicAckBody;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.AMQChannel;
+
+public class BasicAckMethodHandler implements StateAwareMethodListener<BasicAckBody>
+{
+ private static final BasicAckMethodHandler _instance = new BasicAckMethodHandler();
+
+ public static BasicAckMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private BasicAckMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<BasicAckBody> evt) throws AMQException
+ {
+ BasicAckBody body = evt.getMethod();
+ final AMQChannel channel = protocolSession.getChannel(evt.getChannelId());
+ // this method throws an AMQException if the delivery tag is not known
+ channel.acknowledgeMessage(body.deliveryTag, body.multiple);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java
new file mode 100644
index 0000000000..673556cbec
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.BasicCancelBody;
+import org.apache.qpid.framing.BasicCancelOkBody;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.AMQException;
+
+public class BasicCancelMethodHandler implements StateAwareMethodListener<BasicCancelBody>
+{
+ private static final BasicCancelMethodHandler _instance = new BasicCancelMethodHandler();
+
+ public static BasicCancelMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private BasicCancelMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<BasicCancelBody> evt) throws AMQException
+ {
+ final AMQChannel channel = protocolSession.getChannel(evt.getChannelId());
+ final BasicCancelBody body = evt.getMethod();
+ channel.unsubscribeConsumer(protocolSession, body.consumerTag);
+ if(!body.nowait)
+ {
+ final AMQFrame responseFrame = BasicCancelOkBody.createAMQFrame(evt.getChannelId(), body.consumerTag);
+ protocolSession.writeFrame(responseFrame);
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java
new file mode 100644
index 0000000000..d4c94061a0
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java
@@ -0,0 +1,93 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.framing.BasicConsumeBody;
+import org.apache.qpid.framing.BasicConsumeOkBody;
+import org.apache.qpid.framing.ConnectionCloseBody;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.ConsumerTagNotUniqueException;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.log4j.Logger;
+
+public class BasicConsumeMethodHandler implements StateAwareMethodListener<BasicConsumeBody>
+{
+ private static final Logger _log = Logger.getLogger(BasicConsumeMethodHandler.class);
+
+ private static final BasicConsumeMethodHandler _instance = new BasicConsumeMethodHandler();
+
+ public static BasicConsumeMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private BasicConsumeMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession session,
+ AMQMethodEvent<BasicConsumeBody> evt) throws AMQException
+ {
+ BasicConsumeBody body = evt.getMethod();
+ final int channelId = evt.getChannelId();
+
+ AMQChannel channel = session.getChannel(channelId);
+ if (channel == null)
+ {
+ _log.error("Channel " + channelId + " not found");
+ // TODO: either alert or error that the
+ }
+ else
+ {
+ AMQQueue queue = body.queue == null ? channel.getDefaultQueue() : queueRegistry.getQueue(body.queue);
+
+ if(queue == null)
+ {
+ _log.info("No queue for '" + body.queue + "'");
+ }
+ try
+ {
+ String consumerTag = channel.subscribeToQueue(body.consumerTag, queue, session, !body.noAck);
+ if(!body.nowait)
+ {
+ session.writeFrame(BasicConsumeOkBody.createAMQFrame(channelId, consumerTag));
+ }
+
+ //now allow queue to start async processing of any backlog of messages
+ queue.deliverAsync();
+ }
+ catch(ConsumerTagNotUniqueException e)
+ {
+ String msg = "Non-unique consumer tag, '" + body.consumerTag + "'";
+ session.writeFrame(ConnectionCloseBody.createAMQFrame(channelId, AMQConstant.NOT_ALLOWED.getCode(), msg, BasicConsumeBody.CLASS_ID, BasicConsumeBody.METHOD_ID));
+ }
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java
new file mode 100644
index 0000000000..efdbe7aae4
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java
@@ -0,0 +1,80 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.framing.ChannelCloseBody;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+public class BasicPublishMethodHandler implements StateAwareMethodListener<BasicPublishBody>
+{
+ private static final BasicPublishMethodHandler _instance = new BasicPublishMethodHandler();
+
+ public static BasicPublishMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private BasicPublishMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<BasicPublishBody> evt) throws AMQException
+ {
+ final BasicPublishBody body = evt.getMethod();
+
+ // TODO: check the delivery tag field details - is it unique across the broker or per subscriber?
+ if (body.exchange == null)
+ {
+ body.exchange = "amq.direct";
+ }
+ Exchange e = exchangeRegistry.getExchange(body.exchange);
+ // if the exchange does not exist we raise a channel exception
+ if (e == null)
+ {
+ protocolSession.closeChannel(evt.getChannelId());
+ // TODO: modify code gen to make getClazz and getMethod public methods rather than protected
+ // then we can remove the hardcoded 0,0
+ AMQFrame cf = ChannelCloseBody.createAMQFrame(evt.getChannelId(), 500, "Unknown exchange name", 0, 0);
+ protocolSession.writeFrame(cf);
+ }
+ else
+ {
+ // The partially populated BasicDeliver frame plus the received route body
+ // is stored in the channel. Once the final body frame has been received
+ // it is routed to the exchange.
+ AMQChannel channel = protocolSession.getChannel(evt.getChannelId());
+ channel.setPublishFrame(body, protocolSession);
+ }
+ }
+}
+
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java
new file mode 100644
index 0000000000..1357ff16b9
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.BasicQosBody;
+import org.apache.qpid.framing.BasicQosOkBody;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.AMQException;
+
+public class BasicQosHandler implements StateAwareMethodListener<BasicQosBody>
+{
+ private static final BasicQosHandler _instance = new BasicQosHandler();
+
+ public static BasicQosHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQStateManager stateMgr, QueueRegistry queues, ExchangeRegistry exchanges,
+ AMQProtocolSession session, AMQMethodEvent<BasicQosBody> evt) throws AMQException
+ {
+ session.getChannel(evt.getChannelId()).setPrefetchCount(evt.getMethod().prefetchCount);
+ session.writeFrame(new AMQFrame(evt.getChannelId(), new BasicQosOkBody()));
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java
new file mode 100644
index 0000000000..85e802d10d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java
@@ -0,0 +1,57 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.framing.BasicRecoverBody;
+import org.apache.qpid.AMQException;
+import org.apache.log4j.Logger;
+
+public class BasicRecoverMethodHandler implements StateAwareMethodListener<BasicRecoverBody>
+{
+ private static final Logger _logger = Logger.getLogger(BasicRecoverMethodHandler.class);
+
+ private static final BasicRecoverMethodHandler _instance = new BasicRecoverMethodHandler();
+
+ public static BasicRecoverMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<BasicRecoverBody> evt) throws AMQException
+ {
+ _logger.debug("Recover received on protocol session " + protocolSession + " and channel " + evt.getChannelId());
+ AMQChannel channel = protocolSession.getChannel(evt.getChannelId());
+ if (channel == null)
+ {
+ throw new AMQException("Unknown channel " + evt.getChannelId());
+ }
+ channel.resend(protocolSession);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java
new file mode 100644
index 0000000000..0efe12b137
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ChannelCloseBody;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ChannelCloseOkBody;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+public class ChannelCloseHandler implements StateAwareMethodListener<ChannelCloseBody>
+{
+ private static final Logger _logger = Logger.getLogger(ChannelCloseHandler.class);
+
+ private static ChannelCloseHandler _instance = new ChannelCloseHandler();
+
+ public static ChannelCloseHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ChannelCloseHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<ChannelCloseBody> evt) throws AMQException
+ {
+ ChannelCloseBody body = evt.getMethod();
+ _logger.info("Received channel close for id " + evt.getChannelId() + " citing class " + body.classId +
+ " and method " + body.methodId);
+ protocolSession.closeChannel(evt.getChannelId());
+ AMQFrame response = ChannelCloseOkBody.createAMQFrame(evt.getChannelId());
+ protocolSession.writeFrame(response);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java
new file mode 100644
index 0000000000..fd6714de3a
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java
@@ -0,0 +1,54 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ChannelCloseOkBody;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.log4j.Logger;
+
+public class ChannelCloseOkHandler implements StateAwareMethodListener<ChannelCloseOkBody>
+{
+ private static final Logger _logger = Logger.getLogger(ChannelCloseOkHandler.class);
+
+ private static ChannelCloseOkHandler _instance = new ChannelCloseOkHandler();
+
+ public static ChannelCloseOkHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ChannelCloseOkHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<ChannelCloseOkBody> evt) throws AMQException
+ {
+ _logger.info("Received channel-close-ok for channel-id " + evt.getChannelId());
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java
new file mode 100644
index 0000000000..8417ada15f
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java
@@ -0,0 +1,64 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ChannelFlowBody;
+import org.apache.qpid.framing.ChannelFlowOkBody;
+import org.apache.qpid.framing.ChannelCloseBody;
+import org.apache.qpid.AMQException;
+
+public class ChannelFlowHandler implements StateAwareMethodListener<ChannelFlowBody>
+{
+ private static final Logger _logger = Logger.getLogger(ChannelFlowHandler.class);
+
+ private static ChannelFlowHandler _instance = new ChannelFlowHandler();
+
+ public static ChannelFlowHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ChannelFlowHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<ChannelFlowBody> evt) throws AMQException
+ {
+ ChannelFlowBody body = evt.getMethod();
+
+ AMQChannel channel = protocolSession.getChannel(evt.getChannelId());
+ channel.setSuspended(!body.active);
+ _logger.debug("Channel.Flow for channel " + evt.getChannelId() + ", active=" + body.active);
+
+ AMQFrame response = ChannelFlowOkBody.createAMQFrame(evt.getChannelId(), body.active);
+ protocolSession.writeFrame(response);
+ }}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java
new file mode 100644
index 0000000000..4cccc774ba
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ChannelOpenBody;
+import org.apache.qpid.framing.ChannelOpenOkBody;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+public class ChannelOpenHandler implements StateAwareMethodListener<ChannelOpenBody>
+{
+ private static ChannelOpenHandler _instance = new ChannelOpenHandler();
+
+ public static ChannelOpenHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ChannelOpenHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<ChannelOpenBody> evt) throws AMQException
+ {
+ IApplicationRegistry registry = ApplicationRegistry.getInstance();
+ final AMQChannel channel = new AMQChannel(evt.getChannelId(), registry.getMessageStore(),
+ exchangeRegistry);
+ protocolSession.addChannel(channel);
+ AMQFrame response = ChannelOpenOkBody.createAMQFrame(evt.getChannelId());
+ protocolSession.writeFrame(response);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java
new file mode 100644
index 0000000000..7bdb1942d0
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java
@@ -0,0 +1,68 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.framing.ConnectionCloseBody;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ConnectionCloseOkBody;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.AMQException;
+import org.apache.log4j.Logger;
+
+public class ConnectionCloseMethodHandler implements StateAwareMethodListener<ConnectionCloseBody>
+{
+ private static final Logger _logger = Logger.getLogger(ConnectionCloseMethodHandler.class);
+
+ private static ConnectionCloseMethodHandler _instance = new ConnectionCloseMethodHandler();
+
+ public static ConnectionCloseMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ConnectionCloseMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<ConnectionCloseBody> evt) throws AMQException
+ {
+ final ConnectionCloseBody body = evt.getMethod();
+ _logger.info("ConnectionClose received with reply code/reply text " + body.replyCode + "/" +
+ body.replyText + " for " + protocolSession);
+ try
+ {
+ protocolSession.closeSession();
+ }
+ catch (Exception e)
+ {
+ _logger.error("Error closing protocol session: " + e, e);
+ }
+ final AMQFrame response = ConnectionCloseOkBody.createAMQFrame(evt.getChannelId());
+ protocolSession.writeFrame(response);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java
new file mode 100644
index 0000000000..cc9277593b
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ConnectionCloseOkBody;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQState;
+import org.apache.log4j.Logger;
+
+public class ConnectionCloseOkMethodHandler implements StateAwareMethodListener<ConnectionCloseOkBody>
+{
+ private static final Logger _logger = Logger.getLogger(ConnectionCloseOkMethodHandler.class);
+
+ private static ConnectionCloseOkMethodHandler _instance = new ConnectionCloseOkMethodHandler();
+
+ public static ConnectionCloseOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ConnectionCloseOkMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<ConnectionCloseOkBody> evt) throws AMQException
+ {
+ //todo should this not do more than just log the method?
+ _logger.info("Received Connection-close-ok");
+
+ try
+ {
+ stateManager.changeState(AMQState.CONNECTION_CLOSED);
+ protocolSession.closeSession();
+ }
+ catch (Exception e)
+ {
+ _logger.error("Error closing protocol session: " + e, e);
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java
new file mode 100644
index 0000000000..bfcc50e1f8
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java
@@ -0,0 +1,71 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ConnectionOpenBody;
+import org.apache.qpid.framing.ConnectionOpenOkBody;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQState;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+public class ConnectionOpenMethodHandler implements StateAwareMethodListener<ConnectionOpenBody>
+{
+ private static ConnectionOpenMethodHandler _instance = new ConnectionOpenMethodHandler();
+
+ public static ConnectionOpenMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ConnectionOpenMethodHandler()
+ {
+ }
+
+ private static String generateClientID()
+ {
+ return Long.toString(System.currentTimeMillis());
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<ConnectionOpenBody> evt) throws AMQException
+ {
+ ConnectionOpenBody body = evt.getMethod();
+ String contextKey = body.virtualHost;
+
+ //todo //FIXME The virtual host must be validated by the server for the connection to open-ok
+ // See Spec (0.8.2). Section 3.1.2 Virtual Hosts
+ if (contextKey == null)
+ {
+ contextKey = generateClientID();
+ }
+ protocolSession.setContextKey(contextKey);
+ AMQFrame response = ConnectionOpenOkBody.createAMQFrame((short)0, contextKey);
+ stateManager.changeState(AMQState.CONNECTION_OPEN);
+ protocolSession.writeFrame(response);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java
new file mode 100644
index 0000000000..c858d25e2d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java
@@ -0,0 +1,118 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQChannelException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.HeartbeatConfig;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQState;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.security.auth.AuthenticationManager;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.log4j.Logger;
+
+import javax.security.sasl.SaslServer;
+import javax.security.sasl.SaslException;
+
+public class ConnectionSecureOkMethodHandler implements StateAwareMethodListener<ConnectionSecureOkBody>
+{
+ private static final Logger _logger = Logger.getLogger(ConnectionSecureOkMethodHandler.class);
+
+ private static ConnectionSecureOkMethodHandler _instance = new ConnectionSecureOkMethodHandler();
+
+ public static ConnectionSecureOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ConnectionSecureOkMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<ConnectionSecureOkBody> evt) throws AMQException
+ {
+ ConnectionSecureOkBody body = evt.getMethod();
+
+ AuthenticationManager authMgr = ApplicationRegistry.getInstance().getAuthenticationManager();
+ SaslServer ss = protocolSession.getSaslServer();
+ if (ss == null)
+ {
+ throw new AMQException("No SASL context set up in session");
+ }
+
+ AuthenticationResult authResult = authMgr.authenticate(ss, body.response);
+ switch (authResult.status)
+ {
+ case ERROR:
+ // Can't do this as we violate protocol. Need to send Close
+ // throw new AMQException(AMQConstant.NOT_ALLOWED.getCode(), AMQConstant.NOT_ALLOWED.getName());
+ _logger.info("Authentication failed");
+ stateManager.changeState(AMQState.CONNECTION_CLOSING);
+ AMQFrame close = ConnectionCloseBody.createAMQFrame(0, AMQConstant.NOT_ALLOWED.getCode(),
+ AMQConstant.NOT_ALLOWED.getName(),
+ ConnectionCloseBody.CLASS_ID,
+ ConnectionCloseBody.METHOD_ID);
+ protocolSession.writeFrame(close);
+ disposeSaslServer(protocolSession);
+ break;
+ case SUCCESS:
+ _logger.info("Connected as: " + ss.getAuthorizationID());
+ stateManager.changeState(AMQState.CONNECTION_NOT_TUNED);
+ AMQFrame tune = ConnectionTuneBody.createAMQFrame(0, Integer.MAX_VALUE,
+ ConnectionStartOkMethodHandler.getConfiguredFrameSize(),
+ HeartbeatConfig.getInstance().getDelay());
+ protocolSession.writeFrame(tune);
+ disposeSaslServer(protocolSession);
+ break;
+ case CONTINUE:
+ stateManager.changeState(AMQState.CONNECTION_NOT_AUTH);
+ AMQFrame challenge = ConnectionSecureBody.createAMQFrame(0, authResult.challenge);
+ protocolSession.writeFrame(challenge);
+ }
+ }
+
+ private void disposeSaslServer(AMQProtocolSession ps)
+ {
+ SaslServer ss = ps.getSaslServer();
+ if (ss != null)
+ {
+ ps.setSaslServer(null);
+ try
+ {
+ ss.dispose();
+ }
+ catch (SaslException e)
+ {
+ _logger.error("Error disposing of Sasl server: " + e);
+ }
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java
new file mode 100644
index 0000000000..00ae547683
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java
@@ -0,0 +1,130 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.commons.configuration.Configuration;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ConnectionSecureBody;
+import org.apache.qpid.framing.ConnectionStartOkBody;
+import org.apache.qpid.framing.ConnectionTuneBody;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.HeartbeatConfig;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.auth.AuthenticationManager;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+import org.apache.qpid.server.state.AMQState;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+
+public class ConnectionStartOkMethodHandler implements StateAwareMethodListener<ConnectionStartOkBody>
+{
+ private static final Logger _logger = Logger.getLogger(ConnectionStartOkMethodHandler.class);
+
+ private static ConnectionStartOkMethodHandler _instance = new ConnectionStartOkMethodHandler();
+
+ private static final int DEFAULT_FRAME_SIZE = 65536;
+
+ public static StateAwareMethodListener<ConnectionStartOkBody> getInstance()
+ {
+ return _instance;
+ }
+
+ private ConnectionStartOkMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<ConnectionStartOkBody> evt) throws AMQException
+ {
+ final ConnectionStartOkBody body = evt.getMethod();
+ _logger.info("SASL Mechanism selected: " + body.mechanism);
+ _logger.info("Locale selected: " + body.locale);
+
+ AuthenticationManager authMgr = ApplicationRegistry.getInstance().getAuthenticationManager();
+
+ SaslServer ss = null;
+ try
+ {
+ ss = authMgr.createSaslServer(body.mechanism, protocolSession.getLocalFQDN());
+ protocolSession.setSaslServer(ss);
+
+ AuthenticationResult authResult = authMgr.authenticate(ss, body.response);
+
+ switch (authResult.status)
+ {
+ case ERROR:
+ throw new AMQException("Authentication failed");
+ case SUCCESS:
+ _logger.info("Connected as: " + ss.getAuthorizationID());
+ stateManager.changeState(AMQState.CONNECTION_NOT_TUNED);
+ AMQFrame tune = ConnectionTuneBody.createAMQFrame(0, Integer.MAX_VALUE, getConfiguredFrameSize(),
+ HeartbeatConfig.getInstance().getDelay());
+ protocolSession.writeFrame(tune);
+ break;
+ case CONTINUE:
+ stateManager.changeState(AMQState.CONNECTION_NOT_AUTH);
+ AMQFrame challenge = ConnectionSecureBody.createAMQFrame(0, authResult.challenge);
+ protocolSession.writeFrame(challenge);
+ }
+ }
+ catch (SaslException e)
+ {
+ disposeSaslServer(protocolSession);
+ throw new AMQException("SASL error: " + e, e);
+ }
+ }
+
+ private void disposeSaslServer(AMQProtocolSession ps)
+ {
+ SaslServer ss = ps.getSaslServer();
+ if (ss != null)
+ {
+ ps.setSaslServer(null);
+ try
+ {
+ ss.dispose();
+ }
+ catch (SaslException e)
+ {
+ _logger.error("Error disposing of Sasl server: " + e);
+ }
+ }
+ }
+
+ static int getConfiguredFrameSize()
+ {
+ final Configuration config = ApplicationRegistry.getInstance().getConfiguration();
+ final int framesize = config.getInt("advanced.framesize", DEFAULT_FRAME_SIZE);
+ _logger.info("Framesize set to " + framesize);
+ return framesize;
+ }
+}
+
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java
new file mode 100644
index 0000000000..f0b4e0a515
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java
@@ -0,0 +1,57 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ConnectionTuneOkBody;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQState;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+public class ConnectionTuneOkMethodHandler implements StateAwareMethodListener<ConnectionTuneOkBody>
+{
+ private static final Logger _logger = Logger.getLogger(ConnectionTuneOkMethodHandler.class);
+
+ private static ConnectionTuneOkMethodHandler _instance = new ConnectionTuneOkMethodHandler();
+
+ public static ConnectionTuneOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<ConnectionTuneOkBody> evt) throws AMQException
+ {
+ ConnectionTuneOkBody body = evt.getMethod();
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug(body);
+ }
+ stateManager.changeState(AMQState.CONNECTION_NOT_OPENED);
+ protocolSession.initHeartbeats(body.heartbeat);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java
new file mode 100644
index 0000000000..b7c75e290a
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java
@@ -0,0 +1,82 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ExchangeDeclareBody;
+import org.apache.qpid.framing.ExchangeDeclareOkBody;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+public class ExchangeDeclareHandler implements StateAwareMethodListener<ExchangeDeclareBody>
+{
+ private static final Logger _logger = Logger.getLogger(ExchangeDeclareHandler.class);
+
+ private static final ExchangeDeclareHandler _instance = new ExchangeDeclareHandler();
+
+ public static ExchangeDeclareHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private final ExchangeFactory exchangeFactory;
+
+ private ExchangeDeclareHandler()
+ {
+ exchangeFactory = ApplicationRegistry.getInstance().getExchangeFactory();
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<ExchangeDeclareBody> evt) throws AMQException
+ {
+ final ExchangeDeclareBody body = evt.getMethod();
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Request to declare exchange of type " + body.type + " with name " + body.exchange);
+ }
+ synchronized(exchangeRegistry)
+ {
+ Exchange exchange = exchangeRegistry.getExchange(body.exchange);
+
+ if (exchange == null)
+ {
+ exchange = exchangeFactory.createExchange(body.exchange, body.type, body.durable,
+ body.passive, body.ticket);
+ exchangeRegistry.registerExchange(exchange);
+ }
+ }
+ if(!body.nowait)
+ {
+ AMQFrame response = ExchangeDeclareOkBody.createAMQFrame(evt.getChannelId());
+ protocolSession.writeFrame(response);
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java
new file mode 100644
index 0000000000..93ef902190
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ExchangeDeleteBody;
+import org.apache.qpid.framing.ExchangeDeleteOkBody;
+import org.apache.qpid.server.exchange.ExchangeInUseException;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+public class ExchangeDeleteHandler implements StateAwareMethodListener<ExchangeDeleteBody>
+{
+ private static final ExchangeDeleteHandler _instance = new ExchangeDeleteHandler();
+
+ public static ExchangeDeleteHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ExchangeDeleteHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<ExchangeDeleteBody> evt) throws AMQException
+ {
+ ExchangeDeleteBody body = evt.getMethod();
+ try
+ {
+ exchangeRegistry.unregisterExchange(body.exchange, body.ifUnused);
+ AMQFrame response = ExchangeDeleteOkBody.createAMQFrame(evt.getChannelId());
+ protocolSession.writeFrame(response);
+ }
+ catch (ExchangeInUseException e)
+ {
+ // TODO: sort out consistent channel close mechanism that does all clean up etc.
+ }
+
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java b/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java
new file mode 100644
index 0000000000..ac516b6133
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * 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.server.handler;
+
+import java.util.concurrent.Executor;
+
+/**
+ * An executor that executes the task on the current thread.
+ */
+public class OnCurrentThreadExecutor implements Executor
+{
+ public void execute(Runnable command)
+ {
+ command.run();
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java
new file mode 100644
index 0000000000..cf9e40a660
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java
@@ -0,0 +1,97 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.QueueBindBody;
+import org.apache.qpid.framing.QueueBindOkBody;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+public class QueueBindHandler implements StateAwareMethodListener<QueueBindBody>
+{
+ private static final Logger _log = Logger.getLogger(QueueBindHandler.class);
+
+ private static final QueueBindHandler _instance = new QueueBindHandler();
+
+ public static QueueBindHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private QueueBindHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<QueueBindBody> evt) throws AMQException
+ {
+ final QueueBindBody body = evt.getMethod();
+ final AMQQueue queue;
+ if (body.queue == null)
+ {
+ queue = protocolSession.getChannel(evt.getChannelId()).getDefaultQueue();
+ if (queue == null)
+ {
+ throw new AMQException("No default queue defined on channel and queue was null");
+ }
+ if (body.routingKey == null)
+ {
+ body.routingKey = queue.getName();
+ }
+ }
+ else
+ {
+ queue = queueRegistry.getQueue(body.queue);
+ }
+
+ if (queue == null)
+ {
+ throw body.getChannelException(AMQConstant.NOT_FOUND.getCode(), "Queue " + body.queue + " does not exist.");
+ }
+ final Exchange exch = exchangeRegistry.getExchange(body.exchange);
+ if (exch == null)
+ {
+ throw body.getChannelException(AMQConstant.NOT_FOUND.getCode(), "Exchange " + body.exchange + " does not exist.");
+ }
+ exch.registerQueue(body.routingKey, queue, body.arguments);
+ queue.bind(body.routingKey, exch);
+ if (_log.isInfoEnabled())
+ {
+ _log.info("Binding queue " + queue + " to exchange " + exch + " with routing key " + body.routingKey);
+ }
+ if (!body.nowait)
+ {
+ final AMQFrame response = QueueBindOkBody.createAMQFrame(evt.getChannelId());
+ protocolSession.writeFrame(response);
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java
new file mode 100644
index 0000000000..b7004de2a9
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java
@@ -0,0 +1,127 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.configuration.Configured;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.QueueDeclareBody;
+import org.apache.qpid.framing.QueueDeclareOkBody;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.configuration.Configurator;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+import java.text.MessageFormat;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class QueueDeclareHandler implements StateAwareMethodListener<QueueDeclareBody>
+{
+ private static final Logger _log = Logger.getLogger(QueueDeclareHandler.class);
+
+ private static final QueueDeclareHandler _instance = new QueueDeclareHandler();
+
+ public static QueueDeclareHandler getInstance()
+ {
+ return _instance;
+ }
+
+ @Configured(path = "queue.auto_register", defaultValue = "false")
+ public boolean autoRegister;
+
+ private final AtomicInteger _counter = new AtomicInteger();
+
+ private final MessageStore _store;
+
+ protected QueueDeclareHandler()
+ {
+ Configurator.configure(this);
+ _store = ApplicationRegistry.getInstance().getMessageStore();
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<QueueDeclareBody> evt) throws AMQException
+ {
+ QueueDeclareBody body = evt.getMethod();
+
+ // if we aren't given a queue name, we create one which we return to the client
+ if (body.queue == null)
+ {
+ body.queue = createName();
+ }
+ //TODO: do we need to check that the queue already exists with exactly the same "configuration"?
+
+ synchronized (queueRegistry)
+ {
+ AMQQueue queue;
+ if ((queue = queueRegistry.getQueue(body.queue)) == null)
+ {
+ queue = createQueue(body, queueRegistry, protocolSession);
+ if (queue.isDurable() && !queue.isAutoDelete())
+ {
+ _store.createQueue(queue);
+ }
+ queueRegistry.registerQueue(queue);
+ if (autoRegister)
+ {
+ Exchange defaultExchange = exchangeRegistry.getExchange("amq.direct");
+ defaultExchange.registerQueue(body.queue, queue, null);
+ queue.bind(body.queue, defaultExchange);
+ _log.info("Queue " + body.queue + " bound to default exchange");
+ }
+ }
+ //set this as the default queue on the channel:
+ protocolSession.getChannel(evt.getChannelId()).setDefaultQueue(queue);
+ }
+ if (!body.nowait)
+ {
+ AMQFrame response = QueueDeclareOkBody.createAMQFrame(evt.getChannelId(), body.queue, 0L, 0L);
+ _log.info("Queue " + body.queue + " declared successfully");
+ protocolSession.writeFrame(response);
+ }
+ }
+
+ protected String createName()
+ {
+ return "tmp_" + pad(_counter.incrementAndGet());
+ }
+
+ protected static String pad(int value)
+ {
+ return MessageFormat.format("{0,number,0000000000000}", value);
+ }
+
+ protected AMQQueue createQueue(QueueDeclareBody body, QueueRegistry registry, AMQProtocolSession session)
+ throws AMQException
+ {
+ String owner = body.exclusive ? session.getContextKey() : null;
+ return new AMQQueue(body.queue, body.durable, owner, body.autoDelete, registry);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java
new file mode 100644
index 0000000000..0dbc54f29b
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java
@@ -0,0 +1,87 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.framing.QueueDeleteBody;
+import org.apache.qpid.framing.QueueDeleteOkBody;
+import org.apache.qpid.AMQException;
+
+public class QueueDeleteHandler implements StateAwareMethodListener<QueueDeleteBody>
+{
+ private static final QueueDeleteHandler _instance = new QueueDeleteHandler();
+
+ public static QueueDeleteHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private final boolean _failIfNotFound;
+ private final MessageStore _store;
+
+ public QueueDeleteHandler()
+ {
+ this(true);
+ }
+
+ public QueueDeleteHandler(boolean failIfNotFound)
+ {
+ _failIfNotFound = failIfNotFound;
+ _store = ApplicationRegistry.getInstance().getMessageStore();
+
+ }
+
+ public void methodReceived(AMQStateManager stateMgr, QueueRegistry queues, ExchangeRegistry exchanges, AMQProtocolSession session, AMQMethodEvent<QueueDeleteBody> evt) throws AMQException
+ {
+ QueueDeleteBody body = evt.getMethod();
+ AMQQueue queue;
+ if(body.queue == null)
+ {
+ queue = session.getChannel(evt.getChannelId()).getDefaultQueue();
+ }
+ else
+ {
+ queue = queues.getQueue(body.queue);
+ }
+
+ if(queue == null)
+ {
+ if(_failIfNotFound)
+ {
+ throw body.getChannelException(404, "Queue " + body.queue + " does not exist.");
+ }
+ }
+ else
+ {
+ int purged = queue.delete(body.ifUnused, body.ifEmpty);
+ _store.removeQueue(queue.getName());
+ session.writeFrame(QueueDeleteOkBody.createAMQFrame(evt.getChannelId(), purged));
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java
new file mode 100644
index 0000000000..ac864cab6c
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.TxCommitBody;
+import org.apache.qpid.framing.TxCommitOkBody;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+public class TxCommitHandler implements StateAwareMethodListener<TxCommitBody>
+{
+ private static TxCommitHandler _instance = new TxCommitHandler();
+
+ public static TxCommitHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private TxCommitHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<TxCommitBody> evt) throws AMQException
+ {
+
+ try{
+ AMQChannel channel = protocolSession.getChannel(evt.getChannelId());
+ channel.commit();
+ protocolSession.writeFrame(TxCommitOkBody.createAMQFrame(evt.getChannelId()));
+ channel.processReturns(protocolSession);
+ }catch(AMQException e){
+ throw evt.getMethod().getChannelException(e.getErrorCode(), "Failed to commit: " + e.getMessage());
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java
new file mode 100644
index 0000000000..475f6ecacf
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.TxRollbackBody;
+import org.apache.qpid.framing.TxRollbackOkBody;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.AMQChannel;
+
+public class TxRollbackHandler implements StateAwareMethodListener<TxRollbackBody>
+{
+ private static TxRollbackHandler _instance = new TxRollbackHandler();
+
+ public static TxRollbackHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private TxRollbackHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<TxRollbackBody> evt) throws AMQException
+ {
+ try{
+ AMQChannel channel = protocolSession.getChannel(evt.getChannelId());
+ channel.rollback();
+ protocolSession.writeFrame(TxRollbackOkBody.createAMQFrame(evt.getChannelId()));
+ //Now resend all the unacknowledged messages back to the original subscribers.
+ //(Must be done after the TxnRollback-ok response).
+ channel.resend(protocolSession);
+ }catch(AMQException e){
+ throw evt.getMethod().getChannelException(e.getErrorCode(), "Failed to rollback: " + e.getMessage());
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java
new file mode 100644
index 0000000000..c30bc7d66f
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * 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.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.TxSelectBody;
+import org.apache.qpid.framing.TxSelectOkBody;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+public class TxSelectHandler implements StateAwareMethodListener<TxSelectBody>
+{
+ private static TxSelectHandler _instance = new TxSelectHandler();
+
+ public static TxSelectHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private TxSelectHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<TxSelectBody> evt) throws AMQException
+ {
+ protocolSession.getChannel(evt.getChannelId()).setTransactional(true);
+ protocolSession.writeFrame(TxSelectOkBody.createAMQFrame(evt.getChannelId()));
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/jms/JmsConsumer.java b/java/broker/src/main/java/org/apache/qpid/server/jms/JmsConsumer.java
new file mode 100644
index 0000000000..41786e84ba
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/jms/JmsConsumer.java
@@ -0,0 +1,110 @@
+/*
+ *
+ * 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.server.jms;
+
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.AMQException;
+
+public class JmsConsumer
+{
+ private int _prefetchValue;
+
+ private PrefetchUnits _prefetchUnits;
+
+ private boolean _noLocal;
+
+ private boolean _autoAck;
+
+ private boolean _exclusive;
+
+ private AMQProtocolSession _protocolSession;
+
+ public enum PrefetchUnits
+ {
+ OCTETS,
+ MESSAGES
+ }
+
+ public int getPrefetchValue()
+ {
+ return _prefetchValue;
+ }
+
+ public void setPrefetchValue(int prefetchValue)
+ {
+ _prefetchValue = prefetchValue;
+ }
+
+ public PrefetchUnits getPrefetchUnits()
+ {
+ return _prefetchUnits;
+ }
+
+ public void setPrefetchUnits(PrefetchUnits prefetchUnits)
+ {
+ _prefetchUnits = prefetchUnits;
+ }
+
+ public boolean isNoLocal()
+ {
+ return _noLocal;
+ }
+
+ public void setNoLocal(boolean noLocal)
+ {
+ _noLocal = noLocal;
+ }
+
+ public boolean isAutoAck()
+ {
+ return _autoAck;
+ }
+
+ public void setAutoAck(boolean autoAck)
+ {
+ _autoAck = autoAck;
+ }
+
+ public boolean isExclusive()
+ {
+ return _exclusive;
+ }
+
+ public void setExclusive(boolean exclusive)
+ {
+ _exclusive = exclusive;
+ }
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _protocolSession;
+ }
+
+ public void setProtocolSession(AMQProtocolSession protocolSession)
+ {
+ _protocolSession = protocolSession;
+ }
+
+ public void deliverMessage() throws AMQException
+ {
+
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java b/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java
new file mode 100644
index 0000000000..a2c2bd62a2
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java
@@ -0,0 +1,97 @@
+/*
+ *
+ * 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.server.management;
+
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.NotCompliantMBeanException;
+import javax.management.NotificationBroadcaster;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+
+/**
+ * This class provides additinal feature of Notification Broadcaster to the
+ * DefaultManagedObject.
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+public abstract class AMQManagedObject extends DefaultManagedObject
+ implements NotificationBroadcaster
+{
+ /**
+ * broadcaster support class
+ */
+ protected NotificationBroadcasterSupport _broadcaster = new NotificationBroadcasterSupport();
+
+ /**
+ * sequence number for notifications
+ */
+ protected long _notificationSequenceNumber = 0;
+
+ protected MBeanInfo _mbeanInfo;
+
+ protected AMQManagedObject(Class<?> managementInterface, String typeName)
+ throws NotCompliantMBeanException
+ {
+ super(managementInterface, typeName);
+ buildMBeanInfo();
+ }
+
+ @Override
+ public MBeanInfo getMBeanInfo()
+ {
+ return _mbeanInfo;
+ }
+
+ private void buildMBeanInfo() throws NotCompliantMBeanException
+ {
+ _mbeanInfo = new MBeanInfo(this.getClass().getName(),
+ MBeanIntrospector.getMBeanDescription(this.getClass()),
+ MBeanIntrospector.getMBeanAttributesInfo(getManagementInterface()),
+ MBeanIntrospector.getMBeanConstructorsInfo(this.getClass()),
+ MBeanIntrospector.getMBeanOperationsInfo(getManagementInterface()),
+ this.getNotificationInfo());
+ }
+
+
+
+ // notification broadcaster implementation
+
+ public void addNotificationListener(NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ {
+ _broadcaster.addNotificationListener(listener, filter, handback);
+ }
+
+ public void removeNotificationListener(NotificationListener listener)
+ throws ListenerNotFoundException
+ {
+ _broadcaster.removeNotificationListener(listener);
+ }
+
+ public MBeanNotificationInfo[] getNotificationInfo()
+ {
+ return null;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java b/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java
new file mode 100644
index 0000000000..311eb8add9
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java
@@ -0,0 +1,171 @@
+/*
+ *
+ * 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.server.management;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+import javax.management.JMException;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectName;
+import javax.management.StandardMBean;
+
+/**
+ * Provides implementation of the boilerplate ManagedObject interface. Most managed objects should find it useful
+ * to extend this class rather than implementing ManagedObject from scratch.
+ *
+ */
+public abstract class DefaultManagedObject extends StandardMBean implements ManagedObject
+{
+ private Class<?> _managementInterface;
+
+ private String _typeName;
+
+ protected DefaultManagedObject(Class<?> managementInterface, String typeName)
+ throws NotCompliantMBeanException
+ {
+ super(managementInterface);
+ _managementInterface = managementInterface;
+ _typeName = typeName;
+ }
+
+ public String getType()
+ {
+ return _typeName;
+ }
+
+ public Class<?> getManagementInterface()
+ {
+ return _managementInterface;
+ }
+
+ public ManagedObject getParentObject()
+ {
+ return null;
+ }
+
+ public void register() throws AMQException
+ {
+ try
+ {
+ ApplicationRegistry.getInstance().getManagedObjectRegistry().registerObject(this);
+ }
+ catch (JMException e)
+ {
+ throw new AMQException("Error registering managed object " + this + ": " + e, e);
+ }
+ }
+
+ public void unregister() throws AMQException
+ {
+ try
+ {
+ ApplicationRegistry.getInstance().getManagedObjectRegistry().unregisterObject(this);
+ }
+ catch (JMException e)
+ {
+ throw new AMQException("Error unregistering managed object: " + this + ": " + e, e);
+ }
+ }
+
+ public String toString()
+ {
+ return getObjectInstanceName() + "[" + getType() + "]";
+ }
+
+ /**
+ * Created the ObjectName as per the JMX Specs
+ * @return ObjectName
+ * @throws MalformedObjectNameException
+ */
+ public ObjectName getObjectName()
+ throws MalformedObjectNameException
+ {
+ String name = getObjectInstanceName();
+ StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN);
+
+ objectName.append(":type=");
+ objectName.append(getHierarchicalType(this));
+
+ objectName.append(",");
+ objectName.append(getHierarchicalName(this));
+ objectName.append("name=").append(name);
+
+ return new ObjectName(objectName.toString());
+ }
+
+ private String getHierarchicalType(ManagedObject obj)
+ {
+ String parentType = null;
+ if (obj.getParentObject() != null)
+ {
+ parentType = getHierarchicalType(obj.getParentObject()).toString();
+ return parentType + "." + obj.getType();
+ }
+ else
+ return obj.getType();
+ }
+
+ private String getHierarchicalName(ManagedObject obj)
+ {
+ String parentName = null;
+ if (obj.getParentObject() != null)
+ {
+ parentName = obj.getParentObject().getType() + "=" +
+ obj.getParentObject().getObjectInstanceName() + ","+
+ getHierarchicalName(obj.getParentObject());
+
+ return parentName;
+ }
+ else
+ return "";
+ }
+
+ protected static StringBuffer jmxEncode(StringBuffer jmxName, int attrPos)
+ {
+ for (int i = attrPos; i < jmxName.length(); i++)
+ {
+ if (jmxName.charAt(i) == ',')
+ {
+ jmxName.setCharAt(i, ';');
+ }
+ else if (jmxName.charAt(i) == ':')
+ {
+ jmxName.setCharAt(i, '-');
+ }
+ else if (jmxName.charAt(i) == '?' ||
+ jmxName.charAt(i) == '*' ||
+ jmxName.charAt(i) == '\\')
+ {
+ jmxName.insert(i, '\\');
+ i++;
+ }
+ else if (jmxName.charAt(i) == '\n')
+ {
+ jmxName.insert(i, '\\');
+ i++;
+ jmxName.setCharAt(i, 'n');
+ }
+ }
+ return jmxName;
+ }
+} \ No newline at end of file
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java
new file mode 100644
index 0000000000..140dbc31bb
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * 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.server.management;
+
+import org.apache.log4j.Logger;
+
+import javax.management.JMException;
+import javax.management.MBeanServer;
+import java.lang.management.ManagementFactory;
+
+public class JMXManagedObjectRegistry implements ManagedObjectRegistry
+{
+ private static final Logger _log = Logger.getLogger(JMXManagedObjectRegistry.class);
+
+ private final MBeanServer _mbeanServer;
+
+ public JMXManagedObjectRegistry()
+ {
+ _log.info("Initialising managed object registry using platform MBean server");
+ // we use the platform MBean server currently but this must be changed or at least be configuurable
+ _mbeanServer = ManagementFactory.getPlatformMBeanServer();
+ }
+
+ public void registerObject(ManagedObject managedObject) throws JMException
+ {
+ _mbeanServer.registerMBean(managedObject, managedObject.getObjectName());
+ }
+
+ public void unregisterObject(ManagedObject managedObject) throws JMException
+ {
+ _mbeanServer.unregisterMBean(managedObject.getObjectName());
+ }
+
+} \ No newline at end of file
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java
new file mode 100644
index 0000000000..a2e387a9e0
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java
@@ -0,0 +1,42 @@
+package org.apache.qpid.server.management;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Target;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+/*
+ *
+ * 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.
+ *
+ */
+
+/**
+ * Annotation for MBean attributes. This should be used with getter or setter
+ * methods of attributes.
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Inherited
+public @interface MBeanAttribute
+{
+ String name();
+ String description();
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java
new file mode 100644
index 0000000000..c2cafcd387
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java
@@ -0,0 +1,40 @@
+package org.apache.qpid.server.management;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Target;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+/*
+ *
+ * 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.
+ *
+ */
+
+/**
+ * Annotation for MBean constructors.
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.CONSTRUCTOR)
+@Inherited
+public @interface MBeanConstructor
+{
+ String value();
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java
new file mode 100644
index 0000000000..e25ceeab5c
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java
@@ -0,0 +1,39 @@
+package org.apache.qpid.server.management;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Target;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+/*
+ *
+ * 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.
+ *
+ */
+
+/**
+ * Annotation for MBean class.
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface MBeanDescription {
+ String value();
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java
new file mode 100644
index 0000000000..b8235a0808
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java
@@ -0,0 +1,388 @@
+/*
+ *
+ * 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.server.management;
+
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.NotCompliantMBeanException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is a utility class to introspect the MBean class and the management
+ * interface class for various purposes.
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+class MBeanIntrospector {
+
+ private static final String _defaultAttributeDescription = "Management attribute";
+ private static final String _defaultOerationDescription = "Management operation";
+ private static final String _defaultConstructorDescription = "MBean constructor";
+ private static final String _defaultMbeanDescription = "Management interface of the MBean";
+
+ /**
+ * Introspects the management interface class for MBean attributes.
+ * @param interfaceClass
+ * @return MBeanAttributeInfo[]
+ * @throws NotCompliantMBeanException
+ */
+ static MBeanAttributeInfo[] getMBeanAttributesInfo(Class interfaceClass)
+ throws NotCompliantMBeanException
+ {
+ List<MBeanAttributeInfo> attributesList = new ArrayList<MBeanAttributeInfo>();
+
+ /**
+ * Using reflection, all methods of the managemetn interface will be analysed,
+ * and MBeanInfo will be created.
+ */
+ for (Method method : interfaceClass.getMethods())
+ {
+ int argCount = method.getParameterTypes().length;
+ String name = method.getName();
+ Class<?> resultType = method.getReturnType();
+ MBeanAttributeInfo attributeInfo = null;
+
+ if (isAttributeGetterMethod(method))
+ {
+ String desc = getAttributeDescription(method);
+ attributeInfo = new MBeanAttributeInfo(name.substring(3),
+ resultType.getName(),
+ desc,
+ true,
+ false,
+ false);
+ int index = getIndexIfAlreadyExists(attributeInfo, attributesList);
+ if (index == -1)
+ {
+ attributesList.add(attributeInfo);
+ }
+ else
+ {
+ attributeInfo = new MBeanAttributeInfo(name.substring(3),
+ resultType.getName(),
+ desc,
+ true,
+ true,
+ false);
+ attributesList.set(index, attributeInfo);
+ }
+ }
+ else if (isAttributeSetterMethod(method))
+ {
+ String desc = getAttributeDescription(method);
+ attributeInfo = new MBeanAttributeInfo(name.substring(3),
+ method.getParameterTypes()[0].getName(),
+ desc,
+ false,
+ true,
+ false);
+ int index = getIndexIfAlreadyExists(attributeInfo, attributesList);
+ if (index == -1)
+ {
+ attributesList.add(attributeInfo);
+ }
+ else
+ {
+ attributeInfo = new MBeanAttributeInfo(name.substring(3),
+ method.getParameterTypes()[0].getName(),
+ desc,
+ true,
+ true,
+ false);
+ attributesList.set(index, attributeInfo);
+ }
+ }
+ else if (isAttributeBoolean(method))
+ {
+ attributeInfo = new MBeanAttributeInfo(name.substring(2),
+ resultType.getName(),
+ getAttributeDescription(method),
+ true,
+ false,
+ true);
+ attributesList.add(attributeInfo);
+ }
+ }
+
+ return attributesList.toArray(new MBeanAttributeInfo[0]);
+ }
+
+ /**
+ * Introspects the management interface class for management operations.
+ * @param interfaceClass
+ * @return MBeanOperationInfo[]
+ */
+ static MBeanOperationInfo[] getMBeanOperationsInfo(Class interfaceClass)
+ {
+ List<MBeanOperationInfo> operationsList = new ArrayList<MBeanOperationInfo>();
+
+ for (Method method : interfaceClass.getMethods())
+ {
+ if (!isAttributeGetterMethod(method) &&
+ !isAttributeSetterMethod(method) &&
+ !isAttributeBoolean(method))
+ {
+ operationsList.add(getOperationInfo(method));
+ }
+ }
+
+ return operationsList.toArray(new MBeanOperationInfo[0]);
+ }
+
+ /**
+ * Checks if the method is an attribute getter method.
+ * @param method
+ * @return true if the method is an attribute getter method.
+ */
+ private static boolean isAttributeGetterMethod(Method method)
+ {
+ if (!(method.getName().equals("get")) &&
+ method.getName().startsWith("get") &&
+ method.getParameterTypes().length == 0 &&
+ !method.getReturnType().equals(void.class))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if the method is an attribute setter method.
+ * @param method
+ * @return true if the method is an attribute setter method.
+ */
+ private static boolean isAttributeSetterMethod(Method method)
+ {
+ if (!(method.getName().equals("set")) &&
+ method.getName().startsWith("set") &&
+ method.getParameterTypes().length == 1 &&
+ method.getReturnType().equals(void.class))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if the attribute is a boolean and the method is a isX kind og method.
+ * @param method
+ * @return true if the method is an attribute isX type of method
+ */
+ private static boolean isAttributeBoolean(Method method)
+ {
+ if (!(method.getName().equals("is")) &&
+ method.getName().startsWith("is") &&
+ method.getParameterTypes().length == 0 &&
+ method.getReturnType().equals(boolean.class))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Helper method to retrieve the attribute index from the list of attributes.
+ * @param attribute
+ * @param list
+ * @return attribute index no. -1 if attribtue doesn't exist
+ * @throws NotCompliantMBeanException
+ */
+ private static int getIndexIfAlreadyExists(MBeanAttributeInfo attribute,
+ List<MBeanAttributeInfo> list)
+ throws NotCompliantMBeanException
+ {
+ String exceptionMsg = "Conflicting attribute methods for attribute " + attribute.getName();
+
+ for (MBeanAttributeInfo memberAttribute : list)
+ {
+ if (attribute.getName().equals(memberAttribute.getName()))
+ {
+ if (!attribute.getType().equals(memberAttribute.getType()))
+ {
+ throw new NotCompliantMBeanException(exceptionMsg);
+ }
+ if (attribute.isReadable() && memberAttribute.isReadable())
+ {
+ if (attribute.isIs() != memberAttribute.isIs())
+ {
+ throw new NotCompliantMBeanException(exceptionMsg);
+ }
+ }
+
+ return list.indexOf(memberAttribute);
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Retrieves the attribute description from annotation
+ * @param attributeMethod
+ * @return attribute description
+ */
+ private static String getAttributeDescription(Method attributeMethod)
+ {
+ MBeanAttribute anno = attributeMethod.getAnnotation(MBeanAttribute.class);
+ if (anno != null)
+ {
+ return anno.description();
+ }
+ return _defaultAttributeDescription;
+ }
+
+ /**
+ * Introspects the method to retrieve the operation information.
+ * @param operation
+ * @return MBeanOperationInfo
+ */
+ private static MBeanOperationInfo getOperationInfo(Method operation)
+ {
+ MBeanOperationInfo operationInfo = null;
+ Class<?> returnType = operation.getReturnType();
+
+ MBeanParameterInfo[] paramsInfo = getParametersInfo(operation.getParameterAnnotations(),
+ operation.getParameterTypes());
+
+ String operationDesc = _defaultOerationDescription;
+ int impact = MBeanOperationInfo.UNKNOWN;
+
+ if (operation.getAnnotation(MBeanOperation.class) != null)
+ {
+ operationDesc = operation.getAnnotation(MBeanOperation.class).description();
+ impact = operation.getAnnotation(MBeanOperation.class).impact();
+ }
+ operationInfo = new MBeanOperationInfo(operation.getName(),
+ operationDesc,
+ paramsInfo,
+ returnType.getName(),
+ impact);
+
+ return operationInfo;
+ }
+
+ /**
+ * Constructs the parameter info.
+ * @param paramsAnno
+ * @param paramTypes
+ * @return MBeanParameterInfo[]
+ */
+ private static MBeanParameterInfo[] getParametersInfo(Annotation[][] paramsAnno,
+ Class<?>[] paramTypes)
+ {
+ int noOfParams = paramsAnno.length;
+
+ MBeanParameterInfo[] paramsInfo = new MBeanParameterInfo[noOfParams];
+
+ for (int i = 0; i < noOfParams; i++)
+ {
+ MBeanParameterInfo paramInfo = null;
+ String type = paramTypes[i].getName();
+ for (Annotation anno : paramsAnno[i])
+ {
+ String name,desc;
+ if (MBeanOperationParameter.class.isInstance(anno))
+ {
+ name = MBeanOperationParameter.class.cast(anno).name();
+ desc = MBeanOperationParameter.class.cast(anno).description();
+ paramInfo = new MBeanParameterInfo(name, type, desc);
+ }
+ }
+
+
+ if (paramInfo == null)
+ {
+ paramInfo = new MBeanParameterInfo("p " + (i + 1), type, "parameter " + (i + 1));
+ }
+ if (paramInfo != null)
+ paramsInfo[i] = paramInfo;
+ }
+
+ return paramsInfo;
+ }
+
+ /**
+ * Introspects the MBean class for constructors
+ * @param implClass
+ * @return MBeanConstructorInfo[]
+ */
+ static MBeanConstructorInfo[] getMBeanConstructorsInfo(Class implClass)
+ {
+ List<MBeanConstructorInfo> constructors = new ArrayList<MBeanConstructorInfo>();
+
+ for (Constructor cons : implClass.getConstructors())
+ {
+ MBeanConstructorInfo constructorInfo = getMBeanConstructorInfo(cons);
+ //MBeanConstructorInfo constructorInfo = new MBeanConstructorInfo("desc", cons);
+ if (constructorInfo != null)
+ constructors.add(constructorInfo);
+ }
+
+ return constructors.toArray(new MBeanConstructorInfo[0]);
+ }
+
+ /**
+ * Retrieves the constructor info from given constructor.
+ * @param cons
+ * @return MBeanConstructorInfo
+ */
+ private static MBeanConstructorInfo getMBeanConstructorInfo(Constructor cons)
+ {
+ String desc = null;
+ Annotation anno = cons.getAnnotation(MBeanConstructor.class);
+ if (anno != null && MBeanConstructor.class.isInstance(anno))
+ {
+ desc = MBeanConstructor.class.cast(anno).value();
+ }
+
+ //MBeanParameterInfo[] paramsInfo = getParametersInfo(cons.getParameterAnnotations(),
+ // cons.getParameterTypes());
+
+ return new MBeanConstructorInfo(cons.getName(),
+ desc != null ? _defaultConstructorDescription : desc ,
+ null);
+ }
+
+ /**
+ * Retrieves the description from the annotations of given class
+ * @param annotatedClass
+ * @return class description
+ */
+ static String getMBeanDescription(Class annotatedClass)
+ {
+ Annotation anno = annotatedClass.getAnnotation(MBeanDescription.class);
+ if (anno != null && MBeanDescription.class.isInstance(anno))
+ {
+ return MBeanDescription.class.cast(anno).value();
+ }
+ return _defaultMbeanDescription;
+ }
+
+} \ No newline at end of file
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java
new file mode 100644
index 0000000000..ebeccadf70
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java
@@ -0,0 +1,43 @@
+package org.apache.qpid.server.management;
+
+import javax.management.MBeanOperationInfo;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Target;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+/*
+ *
+ * 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.
+ *
+ */
+
+/**
+ * Annotation for MBean operations.
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Inherited
+public @interface MBeanOperation
+{
+ String name();
+ String description();
+ int impact() default MBeanOperationInfo.INFO;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java
new file mode 100644
index 0000000000..adb3c651df
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java
@@ -0,0 +1,38 @@
+package org.apache.qpid.server.management;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+/*
+ *
+ * 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.
+ *
+ */
+
+/**
+ * Annotation for MBean operation parameters.
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface MBeanOperationParameter {
+ String name();
+ String description();
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java b/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java
new file mode 100644
index 0000000000..166a2a376d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * 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.server.management;
+
+/**
+ * Any object that can return a related MBean should implement this interface.
+ *
+ * This enables other classes to get the managed object, which in turn is useful when
+ * constructing relationships between managed objects without having to maintain
+ * separate data structures containing MBeans.
+ *
+ */
+public interface Managable
+{
+ ManagedObject getManagedObject();
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java
new file mode 100644
index 0000000000..266fb62fd8
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java
@@ -0,0 +1,98 @@
+/*
+ *
+ * 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.server.management;
+
+import javax.management.JMException;
+import javax.management.MBeanOperationInfo;
+import java.io.IOException;
+
+/**
+ * The ManagedBroker is the management interface to expose management
+ * features of the Broker.
+ *
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+public interface ManagedBroker
+{
+ static final String TYPE = "BrokerManager";
+
+ /**
+ * Creates a new Exchange.
+ * @param name
+ * @param type
+ * @param durable
+ * @param passive
+ * @throws IOException
+ * @throws JMException
+ */
+ @MBeanOperation(name="createNewExchange", description="Creates a new Exchange",
+ impact= MBeanOperationInfo.ACTION)
+ void createNewExchange(@MBeanOperationParameter(name="name", description="Name of the new exchange")String name,
+ @MBeanOperationParameter(name="excahnge type", description="Type of the exchange")String type,
+ @MBeanOperationParameter(name="durable", description="true if the Exchang should be durable")boolean durable,
+ @MBeanOperationParameter(name="passive", description="true of the Exchange should be passive")boolean passive)
+ throws IOException, JMException;
+
+ /**
+ * unregisters all the channels, queuebindings etc and unregisters
+ * this exchange from managed objects.
+ * @param exchange
+ * @throws IOException
+ * @throws JMException
+ */
+ @MBeanOperation(name="unregisterExchange",
+ description="Unregisters all the related channels and queuebindings of this exchange",
+ impact= MBeanOperationInfo.ACTION)
+ void unregisterExchange(@MBeanOperationParameter(name="exchange name", description="Name of the exchange")String exchange)
+ throws IOException, JMException;
+
+ /**
+ * Create a new Queue on the Broker server
+ * @param queueName
+ * @param durable
+ * @param owner
+ * @param autoDelete
+ * @throws IOException
+ * @throws JMException
+ */
+ @MBeanOperation(name="createQueue", description="Create a new Queue on the Broker server",
+ impact= MBeanOperationInfo.ACTION)
+ void createQueue(@MBeanOperationParameter(name="queue name", description="Name of the new queue")String queueName,
+ @MBeanOperationParameter(name="durable", description="true if the queue should be durable")boolean durable,
+ @MBeanOperationParameter(name="owner", description="Owner name")String owner,
+ @MBeanOperationParameter(name="autoDelete", description="true if the queue should be auto delete") boolean autoDelete)
+ throws IOException, JMException;
+
+ /**
+ * Unregisters the Queue bindings, removes the subscriptions and unregisters
+ * from the managed objects.
+ * @param queueName
+ * @throws IOException
+ * @throws JMException
+ */
+ @MBeanOperation(name="deleteQueue",
+ description="Unregisters the Queue bindings, removes the subscriptions and deletes the queue",
+ impact= MBeanOperationInfo.ACTION)
+ void deleteQueue(@MBeanOperationParameter(name="queue name", description="Name of the queue")String queueName)
+ throws IOException, JMException;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java
new file mode 100644
index 0000000000..f512c2dea8
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * 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.server.management;
+
+import org.apache.qpid.AMQException;
+
+import javax.management.ObjectName;
+import javax.management.MalformedObjectNameException;
+
+/**
+ * This should be implemented by all Managable objects.
+ */
+public interface ManagedObject
+{
+ static final String DOMAIN = "org.apache.qpid";
+
+ /**
+ * @return the name that uniquely identifies this object instance. It must be
+ * unique only among objects of this type at this level in the hierarchy so
+ * the uniqueness should not be too difficult to ensure.
+ */
+ String getObjectInstanceName();
+
+ String getType();
+
+ Class<?> getManagementInterface();
+
+ ManagedObject getParentObject();
+
+ void register() throws AMQException;
+
+ void unregister() throws AMQException;
+
+ /**
+ * Returns the ObjectName required for the mbeanserver registration.
+ * @return ObjectName
+ * @throws MalformedObjectNameException
+ */
+ ObjectName getObjectName() throws MalformedObjectNameException;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java
new file mode 100644
index 0000000000..32298f05e3
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * 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.server.management;
+
+import javax.management.JMException;
+
+/**
+ * Handles the registration (and unregistration and so on) of managed objects.
+ *
+ * Managed objects are responsible for exposting attributes, operations and notifications. They will expose
+ * these outside the JVM therefore it is important not to use implementation objects directly as managed objects.
+ * Instead, creating inner classes and exposing those is an effective way of exposing internal state in a
+ * controlled way.
+ *
+ * Although we do not explictly use them while targetting Java 5, the enhanced MXBean approach in Java 6 will
+ * be the obvious choice for managed objects.
+ *
+ */
+public interface ManagedObjectRegistry
+{
+ void registerObject(ManagedObject managedObject) throws JMException;
+
+ void unregisterObject(ManagedObject managedObject) throws JMException;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/ManagementConfiguration.java b/java/broker/src/main/java/org/apache/qpid/server/management/ManagementConfiguration.java
new file mode 100644
index 0000000000..042f626e8b
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagementConfiguration.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * 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.server.management;
+
+import org.apache.qpid.configuration.Configured;
+
+public class ManagementConfiguration
+{
+ @Configured(path = "management.enabled",
+ defaultValue = "true")
+ public boolean enabled;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java
new file mode 100644
index 0000000000..121c992c4f
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java
@@ -0,0 +1,48 @@
+/*
+ *
+ * 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.server.management;
+
+import org.apache.log4j.Logger;
+
+import javax.management.JMException;
+
+/**
+ * This managed object registry does not actually register MBeans. This can be used in tests when management is
+ * not required or when management has been disabled.
+ *
+ */
+public class NoopManagedObjectRegistry implements ManagedObjectRegistry
+{
+ private static final Logger _log = Logger.getLogger(NoopManagedObjectRegistry.class);
+
+ public NoopManagedObjectRegistry()
+ {
+ _log.info("Management is disabled");
+ }
+
+ public void registerObject(ManagedObject managedObject) throws JMException
+ {
+ }
+
+ public void unregisterObject(ManagedObject managedObject) throws JMException
+ {
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodEvent.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodEvent.java
new file mode 100644
index 0000000000..3d12828900
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodEvent.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * 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.server.protocol;
+
+import org.apache.qpid.framing.AMQMethodBody;
+
+/**
+ * An event that is passed to AMQMethodListeners describing a particular method.
+ * It supplies the:
+ * <ul><li>channel id</li>
+ * <li>protocol method</li>
+ * to listeners. This means that listeners do not need to be stateful.
+ *
+ * In the StateAwareMethodListener, other useful objects such as the protocol session
+ * are made available.
+ *
+ */
+public class AMQMethodEvent<M extends AMQMethodBody>
+{
+ private final M _method;
+
+ private final int _channelId;
+
+ public AMQMethodEvent(int channelId, M method)
+ {
+ _channelId = channelId;
+ _method = method;
+ }
+
+ public M getMethod()
+ {
+ return _method;
+ }
+
+ public int getChannelId()
+ {
+ return _channelId;
+ }
+
+ public String toString()
+ {
+ StringBuilder buf = new StringBuilder("Method event: ");
+ buf.append("\nChannel id: ").append(_channelId);
+ buf.append("\nMethod: ").append(_method);
+ return buf.toString();
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodListener.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodListener.java
new file mode 100644
index 0000000000..d2062d3c17
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodListener.java
@@ -0,0 +1,55 @@
+/*
+ *
+ * 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.server.protocol;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.framing.AMQMethodBody;
+
+/**
+ * Interface that allows classes to register for interest in protocol method frames.
+ *
+ */
+public interface AMQMethodListener
+{
+ /**
+ * Invoked when a method frame has been received
+ * @param evt the event that contains the method and channel
+ * @param protocolSession the protocol session associated with the event
+ * @return true if the handler has processed the method frame, false otherwise. Note
+ * that this does not prohibit the method event being delivered to subsequent listeners
+ * but can be used to determine if nobody has dealt with an incoming method frame.
+ * @throws AMQException if an error has occurred. This exception will be delivered
+ * to all registered listeners using the error() method (see below) allowing them to
+ * perform cleanup if necessary.
+ */
+ <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt,
+ AMQProtocolSession protocolSession,
+ QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry) throws AMQException;
+
+ /**
+ * Callback when an error has occurred. Allows listeners to clean up.
+ * @param e
+ */
+ void error(AMQException e);
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java
new file mode 100644
index 0000000000..966af77d64
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java
@@ -0,0 +1,700 @@
+/*
+ *
+ * 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.server.protocol;
+
+import org.apache.log4j.Logger;
+import org.apache.mina.common.IdleStatus;
+import org.apache.mina.common.IoSession;
+import org.apache.mina.transport.vmpipe.VmPipeAddress;
+import org.apache.qpid.AMQChannelException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.codec.AMQDecoder;
+import org.apache.qpid.framing.AMQDataBlock;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.framing.ConnectionStartBody;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.HeartbeatBody;
+import org.apache.qpid.framing.ProtocolInitiation;
+import org.apache.qpid.framing.ProtocolVersionList;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.management.AMQManagedObject;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.management.Managable;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+
+import javax.management.JMException;
+import javax.management.MBeanException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.NotCompliantMBeanException;
+import javax.management.Notification;
+import javax.management.monitor.MonitorNotification;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+import javax.security.sasl.SaslServer;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+public class AMQMinaProtocolSession implements AMQProtocolSession,
+ ProtocolVersionList,
+ Managable
+{
+ private static final Logger _logger = Logger.getLogger(AMQProtocolSession.class);
+
+ private final IoSession _minaProtocolSession;
+
+ private String _contextKey;
+
+ private final Map<Integer, AMQChannel> _channelMap = new HashMap<Integer, AMQChannel>();
+
+ private final CopyOnWriteArraySet<AMQMethodListener> _frameListeners = new CopyOnWriteArraySet<AMQMethodListener>();
+
+ private final AMQStateManager _stateManager;
+
+ private final QueueRegistry _queueRegistry;
+
+ private final ExchangeRegistry _exchangeRegistry;
+
+ private AMQCodecFactory _codecFactory;
+
+ private ManagedAMQProtocolSession _managedObject;
+
+ private SaslServer _saslServer;
+
+ private Object _lastReceived;
+
+ private Object _lastSent;
+
+ private boolean _closed;
+
+ private long _maxNoOfChannels = 1000;
+
+ /* AMQP Version for this session */
+
+ private byte _major;
+ private byte _minor;
+
+ public ManagedObject getManagedObject()
+ {
+ return _managedObject;
+ }
+
+ /**
+ * This class implements the management interface (is an MBean). In order to
+ * make more attributes, operations and notifications available over JMX simply
+ * augment the ManagedConnection interface and add the appropriate implementation here.
+ */
+ @MBeanDescription("Management Bean for an AMQ Broker Connection")
+ private final class ManagedAMQProtocolSession extends AMQManagedObject
+ implements ManagedConnection
+ {
+ private String _name = null;
+ /**
+ * Represents the channel attributes sent with channel data.
+ */
+ private String[] _channelAtttibuteNames = { "ChannelId",
+ "Transactional",
+ "DefaultQueue",
+ "UnacknowledgedMessageCount"};
+ private String[] _channelAttributeDescriptions = { "Channel Identifier",
+ "is Channel Transactional?",
+ "Default Queue Name",
+ "Unacknowledged Message Count"};
+ private OpenType[] _channelAttributeTypes = { SimpleType.INTEGER,
+ SimpleType.BOOLEAN,
+ SimpleType.STRING,
+ SimpleType.INTEGER};
+
+ private String[] _indexNames = { "ChannelId" }; //Channels in the list will be indexed according to channelId.
+ private CompositeType _channelType = null; // represents the data type for channel data
+ private TabularType _channelsType = null; // Datatype for list of channelsType
+ private TabularDataSupport _channelsList = null;
+
+ @MBeanConstructor("Creates an MBean exposing an AMQ Broker Connection")
+ public ManagedAMQProtocolSession() throws NotCompliantMBeanException
+ {
+ super(ManagedConnection.class, ManagedConnection.TYPE);
+ init();
+ }
+
+ /**
+ * initialises the CompositeTypes and TabularType attributes.
+ */
+ private void init()
+ {
+ String remote = getRemoteAddress();
+ remote = "anonymous".equals(remote) ? remote + hashCode() : remote;
+ _name = jmxEncode(new StringBuffer(remote), 0).toString();
+
+ try
+ {
+ _channelType = new CompositeType("channel",
+ "Channel Details",
+ _channelAtttibuteNames,
+ _channelAttributeDescriptions,
+ _channelAttributeTypes);
+
+ _channelsType = new TabularType("channelsType",
+ "List of available channels",
+ _channelType,
+ _indexNames);
+ }
+ catch(OpenDataException ex)
+ {
+ // It should never occur.
+ _logger.error("OpenDataTypes could not be created.", ex);
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public Date getLastIoTime()
+ {
+ return new Date(_minaProtocolSession.getLastIoTime());
+ }
+
+ public String getRemoteAddress()
+ {
+ return _minaProtocolSession.getRemoteAddress().toString();
+ }
+
+ public Long getWrittenBytes()
+ {
+ return _minaProtocolSession.getWrittenBytes();
+ }
+
+ public Long getReadBytes()
+ {
+ return _minaProtocolSession.getReadBytes();
+ }
+
+ public Long getMaximumNumberOfAllowedChannels()
+ {
+ return _maxNoOfChannels;
+ }
+
+ public void setMaximumNumberOfAllowedChannels(Long value)
+ {
+ _maxNoOfChannels = value;
+ }
+
+ public String getObjectInstanceName()
+ {
+ return _name;
+ }
+
+ public void commitTransactions(int channelId) throws JMException
+ {
+ try
+ {
+ AMQChannel channel = _channelMap.get(channelId);
+ if (channel == null)
+ {
+ throw new JMException("The channel (channel Id = " + channelId + ") does not exist");
+ }
+ if (channel.isTransactional())
+ {
+ channel.commit();
+ }
+ }
+ catch(AMQException ex)
+ {
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+
+ public void rollbackTransactions(int channelId) throws JMException
+ {
+ try
+ {
+ AMQChannel channel = _channelMap.get(channelId);
+ if (channel == null)
+ {
+ throw new JMException("The channel (channel Id = " + channelId + ") does not exist");
+ }
+ if (channel.isTransactional())
+ {
+ channel.rollback();
+ }
+ }
+ catch(AMQException ex)
+ {
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+
+ /**
+ * Creates the list of channels in tabular form from the _channelMap.
+ * @return list of channels in tabular form.
+ * @throws OpenDataException
+ */
+ public TabularData getChannels() throws OpenDataException
+ {
+ _channelsList = new TabularDataSupport(_channelsType);
+
+ for (Map.Entry<Integer, AMQChannel> entry : _channelMap.entrySet())
+ {
+ AMQChannel channel = entry.getValue();
+ Object[] itemValues = {channel.getChannelId(),
+ channel.isTransactional(),
+ (channel.getDefaultQueue() != null) ? channel.getDefaultQueue().getName() : null,
+ channel.getUnacknowledgedMessageMap().size()};
+
+ CompositeData channelData = new CompositeDataSupport(_channelType,
+ _channelAtttibuteNames,
+ itemValues);
+
+ _channelsList.put(channelData);
+ }
+
+ return _channelsList;
+ }
+
+ public void closeChannel(int id)
+ throws Exception
+ {
+ try
+ {
+ AMQMinaProtocolSession.this.closeChannel(id);
+ }
+ catch (AMQException ex)
+ {
+ throw new Exception(ex.toString());
+ }
+ }
+
+ public void closeConnection()
+ throws Exception
+ {
+ try
+ {
+ AMQMinaProtocolSession.this.closeSession();
+ }
+ catch (AMQException ex)
+ {
+ throw new Exception(ex.toString());
+ }
+ }
+
+ @Override
+ public MBeanNotificationInfo[] getNotificationInfo()
+ {
+ String[] notificationTypes = new String[]
+ {MonitorNotification.THRESHOLD_VALUE_EXCEEDED};
+ String name = MonitorNotification.class.getName();
+ String description = "An attribute of this MBean has reached threshold value";
+ MBeanNotificationInfo info1 = new MBeanNotificationInfo(notificationTypes,
+ name,
+ description);
+
+ return new MBeanNotificationInfo[] {info1};
+ }
+
+ private void checkForNotification()
+ {
+ int channelsCount = _channelMap.size();
+ if (channelsCount >= getMaximumNumberOfAllowedChannels())
+ {
+ Notification n = new Notification(
+ MonitorNotification.THRESHOLD_VALUE_EXCEEDED,
+ this,
+ ++_notificationSequenceNumber,
+ System.currentTimeMillis(),
+ "ChannelsCount = " + channelsCount + ", ChannelsCount has reached the threshold value");
+
+ _broadcaster.sendNotification(n);
+ }
+ }
+
+ } // End of MBean class
+
+ public AMQMinaProtocolSession(IoSession session, QueueRegistry queueRegistry, ExchangeRegistry exchangeRegistry,
+ AMQCodecFactory codecFactory)
+ throws AMQException
+ {
+ this(session, queueRegistry, exchangeRegistry, codecFactory, new AMQStateManager());
+ }
+
+ public AMQMinaProtocolSession(IoSession session, QueueRegistry queueRegistry, ExchangeRegistry exchangeRegistry,
+ AMQCodecFactory codecFactory, AMQStateManager stateManager)
+ throws AMQException
+ {
+ _stateManager = stateManager;
+ _minaProtocolSession = session;
+ session.setAttachment(this);
+ _frameListeners.add(_stateManager);
+ _queueRegistry = queueRegistry;
+ _exchangeRegistry = exchangeRegistry;
+ _codecFactory = codecFactory;
+ _managedObject = createMBean();
+ _managedObject.register();
+ }
+
+ private ManagedAMQProtocolSession createMBean() throws AMQException
+ {
+ try
+ {
+ return new ManagedAMQProtocolSession();
+ }
+ catch(NotCompliantMBeanException ex)
+ {
+ _logger.error("AMQProtocolSession MBean creation has failed.", ex);
+ throw new AMQException("AMQProtocolSession MBean creation has failed.", ex);
+ }
+ }
+
+ public static AMQProtocolSession getAMQProtocolSession(IoSession minaProtocolSession)
+ {
+ return (AMQProtocolSession) minaProtocolSession.getAttachment();
+ }
+
+ public void dataBlockReceived(AMQDataBlock message)
+ throws Exception
+ {
+ _lastReceived = message;
+ if (message instanceof ProtocolInitiation)
+ {
+ ProtocolInitiation pi = (ProtocolInitiation) message;
+ // this ensures the codec never checks for a PI message again
+ ((AMQDecoder)_codecFactory.getDecoder()).setExpectProtocolInitiation(false);
+ try {
+ pi.checkVersion(this); // Fails if not correct
+ // This sets the protocol version (and hence framing classes) for this session.
+ _major = pi.protocolMajor;
+ _minor = pi.protocolMinor;
+ String mechanisms = ApplicationRegistry.getInstance().getAuthenticationManager().getMechanisms();
+ String locales = "en_US";
+ AMQFrame response = ConnectionStartBody.createAMQFrame((short)0, pi.protocolMajor, pi.protocolMinor, null,
+ mechanisms.getBytes(), locales.getBytes());
+ _minaProtocolSession.write(response);
+ } catch (AMQException e) {
+ _logger.error("Received incorrect protocol initiation", e);
+ /* Find last protocol version in protocol version list. Make sure last protocol version
+ listed in the build file (build-module.xml) is the latest version which will be used
+ here. */
+ int i = pv.length - 1;
+ _minaProtocolSession.write(new ProtocolInitiation(pv[i][PROTOCOL_MAJOR], pv[i][PROTOCOL_MINOR]));
+ // TODO: Close connection (but how to wait until message is sent?)
+ }
+ }
+ else
+ {
+ AMQFrame frame = (AMQFrame) message;
+
+ if (frame.bodyFrame instanceof AMQMethodBody)
+ {
+ methodFrameReceived(frame);
+ }
+ else
+ {
+ try
+ {
+ contentFrameReceived(frame);
+ }
+ catch (RequiredDeliveryException e)
+ {
+ //need to return the message:
+ _logger.info("Returning message to " + this + " channel " + frame.channel
+ + ": " + e.getMessage());
+ writeFrame(e.getReturnMessage(frame.channel));
+ }
+ }
+ }
+ }
+
+ private void methodFrameReceived(AMQFrame frame)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Method frame received: " + frame);
+ }
+ final AMQMethodEvent<AMQMethodBody> evt = new AMQMethodEvent<AMQMethodBody>(frame.channel,
+ (AMQMethodBody)frame.bodyFrame);
+ try
+ {
+ boolean wasAnyoneInterested = false;
+ for (AMQMethodListener listener : _frameListeners)
+ {
+ wasAnyoneInterested = listener.methodReceived(evt, this, _queueRegistry, _exchangeRegistry) ||
+ wasAnyoneInterested;
+ }
+ if (!wasAnyoneInterested)
+ {
+ throw new AMQException("AMQMethodEvent " + evt + " was not processed by any listener.");
+ }
+ }
+ catch (AMQChannelException e)
+ {
+ _logger.error("Closing channel due to: " + e.getMessage());
+ writeFrame(e.getCloseFrame(frame.channel));
+ }
+ catch (AMQException e)
+ {
+ for (AMQMethodListener listener : _frameListeners)
+ {
+ listener.error(e);
+ }
+ _minaProtocolSession.close();
+ }
+ }
+
+ private void contentFrameReceived(AMQFrame frame) throws AMQException
+ {
+ if (frame.bodyFrame instanceof ContentHeaderBody)
+ {
+ contentHeaderReceived(frame);
+ }
+ else if (frame.bodyFrame instanceof ContentBody)
+ {
+ contentBodyReceived(frame);
+ }
+ else if (frame.bodyFrame instanceof HeartbeatBody)
+ {
+ _logger.debug("Received heartbeat from client");
+ }
+ else
+ {
+ _logger.warn("Unrecognised frame " + frame.getClass().getName());
+ }
+ }
+
+ private void contentHeaderReceived(AMQFrame frame) throws AMQException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Content header frame received: " + frame);
+ }
+ getChannel(frame.channel).publishContentHeader((ContentHeaderBody)frame.bodyFrame);
+ }
+
+ private void contentBodyReceived(AMQFrame frame) throws AMQException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Content body frame received: " + frame);
+ }
+ getChannel(frame.channel).publishContentBody((ContentBody)frame.bodyFrame);
+ }
+
+ /**
+ * Convenience method that writes a frame to the protocol session. Equivalent
+ * to calling getProtocolSession().write().
+ *
+ * @param frame the frame to write
+ */
+ public void writeFrame(AMQDataBlock frame)
+ {
+ _lastSent = frame;
+ _minaProtocolSession.write(frame);
+ }
+
+ public String getContextKey()
+ {
+ return _contextKey;
+ }
+
+ public void setContextKey(String contextKey)
+ {
+ _contextKey = contextKey;
+ }
+
+ public AMQChannel getChannel(int channelId) throws AMQException
+ {
+ return _channelMap.get(channelId);
+ }
+
+ public void addChannel(AMQChannel channel)
+ {
+ _channelMap.put(channel.getChannelId(), channel);
+ _managedObject.checkForNotification();
+ }
+
+ /**
+ * Close a specific channel. This will remove any resources used by the channel, including:
+ * <ul><li>any queue subscriptions (this may in turn remove queues if they are auto delete</li>
+ * </ul>
+ * @param channelId id of the channel to close
+ * @throws AMQException if an error occurs closing the channel
+ * @throws IllegalArgumentException if the channel id is not valid
+ */
+ public void closeChannel(int channelId) throws AMQException
+ {
+ final AMQChannel channel = _channelMap.get(channelId);
+ if (channel == null)
+ {
+ throw new IllegalArgumentException("Unknown channel id");
+ }
+ else
+ {
+ try
+ {
+ channel.close(this);
+ }
+ finally
+ {
+ _channelMap.remove(channelId);
+ }
+ }
+ }
+
+ /**
+ * In our current implementation this is used by the clustering code.
+ * @param channelId
+ */
+ public void removeChannel(int channelId)
+ {
+ _channelMap.remove(channelId);
+ }
+
+ /**
+ * Initialise heartbeats on the session.
+ * @param delay delay in seconds (not ms)
+ */
+ public void initHeartbeats(int delay)
+ {
+ if(delay > 0)
+ {
+ _minaProtocolSession.setIdleTime(IdleStatus.WRITER_IDLE, delay);
+ _minaProtocolSession.setIdleTime(IdleStatus.READER_IDLE, HeartbeatConfig.getInstance().getTimeout(delay));
+ }
+ }
+
+ /**
+ * Closes all channels that were opened by this protocol session. This frees up all resources
+ * used by the channel.
+ * @throws AMQException if an error occurs while closing any channel
+ */
+ private void closeAllChannels() throws AMQException
+ {
+ for (AMQChannel channel : _channelMap.values())
+ {
+ channel.close(this);
+ }
+ }
+
+ /**
+ * This must be called when the session is _closed in order to free up any resources
+ * managed by the session.
+ */
+ public void closeSession() throws AMQException
+ {
+ if(!_closed)
+ {
+ _closed = true;
+ closeAllChannels();
+ if (_managedObject != null)
+ {
+ _managedObject.unregister();
+ }
+ }
+ }
+
+ public String toString()
+ {
+ return "AMQProtocolSession(" + _minaProtocolSession.getRemoteAddress() + ")";
+ }
+
+ public String dump()
+ {
+ return this + " last_sent=" + _lastSent + " last_received=" + _lastReceived;
+ }
+
+ /**
+ * @return an object that can be used to identity
+ */
+ public Object getKey()
+ {
+ return _minaProtocolSession.getRemoteAddress();
+ }
+
+ /**
+ * Get the fully qualified domain name of the local address to which this session is bound. Since some servers
+ * may be bound to multiple addresses this could vary depending on the acceptor this session was created from.
+ *
+ * @return a String FQDN
+ */
+ public String getLocalFQDN()
+ {
+ SocketAddress address = _minaProtocolSession.getLocalAddress();
+ // we use the vmpipe address in some tests hence the need for this rather ugly test. The host
+ // information is used by SASL primary.
+ if (address instanceof InetSocketAddress)
+ {
+ return ((InetSocketAddress)address).getHostName();
+ }
+ else if (address instanceof VmPipeAddress)
+ {
+ return "vmpipe:" + ((VmPipeAddress)address).getPort();
+ }
+ else
+ {
+ throw new IllegalArgumentException("Unsupported socket address class: " + address);
+ }
+ }
+
+ public SaslServer getSaslServer()
+ {
+ return _saslServer;
+ }
+
+ public void setSaslServer(SaslServer saslServer)
+ {
+ _saslServer = saslServer;
+ }
+
+ /**
+ * Convenience methods for managing AMQP version.
+ * NOTE: Both major and minor will be set to 0 prior to protocol initiation.
+ */
+
+ public byte getAmqpMajor()
+ {
+ return _major;
+ }
+
+ public byte getAmqpMinor()
+ {
+ return _minor;
+ }
+
+ public boolean amqpVersionEquals(byte major, byte minor)
+ {
+ return _major == major && _minor == minor;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java
new file mode 100644
index 0000000000..18980f440b
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java
@@ -0,0 +1,230 @@
+/*
+ *
+ * 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.server.protocol;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.server.transport.ConnectorConfiguration;
+import org.apache.qpid.ssl.BogusSSLContextFactory;
+import org.apache.log4j.Logger;
+import org.apache.mina.common.ByteBuffer;
+import org.apache.mina.common.IdleStatus;
+import org.apache.mina.common.IoHandlerAdapter;
+import org.apache.mina.common.IoSession;
+import org.apache.mina.filter.SSLFilter;
+import org.apache.mina.filter.codec.ProtocolCodecFilter;
+import org.apache.mina.util.SessionUtil;
+
+import java.io.IOException;
+
+
+/**
+ * The protocol handler handles "protocol events" for all connections. The state
+ * associated with an individual connection is accessed through the protocol session.
+ *
+ * We delegate all frame (message) processing to the AMQProtocolSession which wraps
+ * the state for the connection.
+ *
+ */
+public class AMQPFastProtocolHandler extends IoHandlerAdapter implements ProtocolVersionList
+{
+ private static final Logger _logger = Logger.getLogger(AMQPFastProtocolHandler.class);
+
+ /**
+ * The registry of all queues. This is passed to frame listeners when frame
+ * events occur.
+ */
+ private final QueueRegistry _queueRegistry;
+
+ /**
+ * The registry of all exchanges. This is passed to frame listeners when frame
+ * events occur.
+ */
+ private final ExchangeRegistry _exchangeRegistry;
+
+ private boolean _useSSL;
+
+ public AMQPFastProtocolHandler(Integer applicationRegistryInstance)
+ {
+ IApplicationRegistry registry = ApplicationRegistry.getInstance(applicationRegistryInstance);
+
+ _queueRegistry = registry.getQueueRegistry();
+ _exchangeRegistry = registry.getExchangeRegistry();
+ _logger.debug("AMQPFastProtocolHandler created");
+ }
+
+ public AMQPFastProtocolHandler(QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry)
+ {
+ _queueRegistry = queueRegistry;
+ _exchangeRegistry = exchangeRegistry;
+
+ _logger.debug("AMQPFastProtocolHandler created");
+ }
+
+ protected AMQPFastProtocolHandler(AMQPFastProtocolHandler handler)
+ {
+ this(handler._queueRegistry, handler._exchangeRegistry);
+ }
+
+ public void sessionCreated(IoSession protocolSession) throws Exception
+ {
+ SessionUtil.initialize(protocolSession);
+ final AMQCodecFactory codecFactory = new AMQCodecFactory(true);
+
+ createSession(protocolSession, _queueRegistry, _exchangeRegistry, codecFactory);
+ _logger.info("Protocol session created");
+
+ final ProtocolCodecFilter pcf = new ProtocolCodecFilter(codecFactory);
+
+ ConnectorConfiguration connectorConfig = ApplicationRegistry.getInstance().
+ getConfiguredObject(ConnectorConfiguration.class);
+ if (connectorConfig.enableExecutorPool)
+ {
+ if (_useSSL)
+ {
+ protocolSession.getFilterChain().addAfter("AsynchronousReadFilter", "sslFilter",
+ new SSLFilter(BogusSSLContextFactory.getInstance(true)));
+ }
+ protocolSession.getFilterChain().addBefore("AsynchronousWriteFilter", "protocolFilter", pcf);
+ }
+ else
+ {
+ protocolSession.getFilterChain().addLast("protocolFilter", pcf);
+ }
+ }
+
+ /**
+ * Separated into its own, protected, method to allow easier reuse
+ */
+ protected void createSession(IoSession session, QueueRegistry queues, ExchangeRegistry exchanges, AMQCodecFactory codec) throws AMQException
+ {
+ new AMQMinaProtocolSession(session, queues, exchanges, codec);
+ }
+
+ public void sessionOpened(IoSession protocolSession) throws Exception
+ {
+ _logger.info("Session opened");
+ }
+
+ public void sessionClosed(IoSession protocolSession) throws Exception
+ {
+ _logger.info("Protocol Session closed");
+ final AMQProtocolSession amqProtocolSession = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession);
+ amqProtocolSession.closeSession();
+ }
+
+ public void sessionIdle(IoSession session, IdleStatus status) throws Exception
+ {
+ _logger.debug("Protocol Session [" + this + "] idle: " + status);
+ if(IdleStatus.WRITER_IDLE.equals(status))
+ {
+ //write heartbeat frame:
+ session.write(HeartbeatBody.FRAME);
+ }
+ else if(IdleStatus.READER_IDLE.equals(status))
+ {
+ //failover:
+ throw new IOException("Timed out while waiting for heartbeat from peer.");
+ }
+
+ }
+
+ public void exceptionCaught(IoSession protocolSession, Throwable throwable) throws Exception
+ {
+ AMQProtocolSession session = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession);
+ if (throwable instanceof AMQProtocolHeaderException)
+ {
+ /* Find last protocol version in protocol version list. Make sure last protocol version
+ listed in the build file (build-module.xml) is the latest version which will be returned
+ here. */
+ int i = pv.length - 1;
+ protocolSession.write(new ProtocolInitiation(pv[i][PROTOCOL_MAJOR], pv[i][PROTOCOL_MINOR]));
+ protocolSession.close();
+ _logger.error("Error in protocol initiation " + session + ": " + throwable.getMessage(), throwable);
+ }
+ else if(throwable instanceof IOException)
+ {
+ _logger.error("IOException caught in" + session + ", session closed implictly: " + throwable, throwable);
+ }
+ else
+ {
+ protocolSession.write(ConnectionCloseBody.createAMQFrame(0, 200, throwable.getMessage(), 0, 0));
+ _logger.error("Exception caught in" + session + ", closing session explictly: " + throwable, throwable);
+ protocolSession.close();
+ }
+ }
+
+ /**
+ * Invoked when a message is received on a particular protocol session. Note that a
+ * protocol session is directly tied to a particular physical connection.
+ * @param protocolSession the protocol session that received the message
+ * @param message the message itself (i.e. a decoded frame)
+ * @throws Exception if the message cannot be processed
+ */
+ public void messageReceived(IoSession protocolSession, Object message) throws Exception
+ {
+ final AMQProtocolSession amqProtocolSession = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession);
+
+ if (message instanceof AMQDataBlock)
+ {
+ amqProtocolSession.dataBlockReceived((AMQDataBlock) message);
+ }
+ else if (message instanceof ByteBuffer)
+ {
+ throw new IllegalStateException("Handed undecoded ByteBuffer buf = " + message);
+ }
+ else
+ {
+ throw new IllegalStateException("Handed unhandled message. message.class = " + message.getClass() + " message = " + message);
+ }
+ }
+
+ /**
+ * Called after a message has been sent out on a particular protocol session
+ * @param protocolSession the protocol session (i.e. connection) on which this
+ * message was sent
+ * @param object the message (frame) that was encoded and sent
+ * @throws Exception if we want to indicate an error
+ */
+ public void messageSent(IoSession protocolSession, Object object) throws Exception
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Message sent: " + object);
+ }
+ }
+
+ public boolean isUseSSL()
+ {
+ return _useSSL;
+ }
+
+ public void setUseSSL(boolean useSSL)
+ {
+ _useSSL = useSSL;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java
new file mode 100644
index 0000000000..ff1316f704
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * 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.server.protocol;
+
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+
+/**
+ * The protocol provide's role is to encapsulate the initialisation of the protocol handler.
+ *
+ * The protocol handler (see AMQPFastProtocolHandler class) handles protocol events
+ * such as connection closing or a frame being received. It can either do this directly
+ * or pass off to the protocol session in the cases where state information is required to
+ * deal with the event.
+ *
+ */
+public class AMQPProtocolProvider
+{
+ /**
+ * Handler for protocol events
+ */
+ private AMQPFastProtocolHandler _handler;
+
+ public AMQPProtocolProvider()
+ {
+ IApplicationRegistry registry = ApplicationRegistry.getInstance();
+ _handler = new AMQPFastProtocolHandler(registry.getQueueRegistry(),
+ registry.getExchangeRegistry());
+ }
+
+ public AMQPFastProtocolHandler getHandler()
+ {
+ return _handler;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java
new file mode 100644
index 0000000000..acaf6b0d9b
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java
@@ -0,0 +1,125 @@
+/*
+ *
+ * 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.server.protocol;
+
+import org.apache.qpid.framing.AMQDataBlock;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.AMQException;
+
+import javax.security.sasl.SaslServer;
+
+
+public interface AMQProtocolSession
+{
+ /**
+ * Called when a protocol data block is received
+ * @param message the data block that has been received
+ * @throws Exception if processing the datablock fails
+ */
+ void dataBlockReceived(AMQDataBlock message) throws Exception;
+
+ /**
+ * Write a datablock, encoding where necessary (e.g. into a sequence of bytes)
+ * @param frame the frame to be encoded and written
+ */
+ void writeFrame(AMQDataBlock frame);
+
+ /**
+ * Get the context key associated with this session. Context key is described
+ * in the AMQ protocol specification (RFC 6).
+ * @return the context key
+ */
+ String getContextKey();
+
+ /**
+ * Set the context key associated with this session. Context key is described
+ * in the AMQ protocol specification (RFC 6).
+ * @param contextKey the context key
+ */
+ void setContextKey(String contextKey);
+
+ /**
+ * Get the channel for this session associated with the specified id. A channel
+ * id is unique per connection (i.e. per session).
+ * @param channelId the channel id which must be valid
+ * @return null if no channel exists, the channel otherwise
+ */
+ AMQChannel getChannel(int channelId) throws AMQException;
+
+ /**
+ * Associate a channel with this session.
+ * @param channel the channel to associate with this session. It is an error to
+ * associate the same channel with more than one session but this is not validated.
+ */
+ void addChannel(AMQChannel channel);
+
+ /**
+ * Close a specific channel. This will remove any resources used by the channel, including:
+ * <ul><li>any queue subscriptions (this may in turn remove queues if they are auto delete</li>
+ * </ul>
+ * @param channelId id of the channel to close
+ * @throws org.apache.qpid.AMQException if an error occurs closing the channel
+ * @throws IllegalArgumentException if the channel id is not valid
+ */
+ void closeChannel(int channelId) throws AMQException;
+
+ /**
+ * Remove a channel from the session but do not close it.
+ * @param channelId
+ */
+ void removeChannel(int channelId);
+
+ /**
+ * Initialise heartbeats on the session.
+ * @param delay delay in seconds (not ms)
+ */
+ void initHeartbeats(int delay);
+
+ /**
+ * This must be called when the session is _closed in order to free up any resources
+ * managed by the session.
+ */
+ void closeSession() throws AMQException;
+
+ /**
+ * @return a key that uniquely identifies this session
+ */
+ Object getKey();
+
+ /**
+ * Get the fully qualified domain name of the local address to which this session is bound. Since some servers
+ * may be bound to multiple addresses this could vary depending on the acceptor this session was created from.
+ *
+ * @return a String FQDN
+ */
+ String getLocalFQDN();
+
+ /**
+ * @return the sasl server that can perform authentication for this session.
+ */
+ SaslServer getSaslServer();
+
+ /**
+ * Set the sasl server that is to perform authentication for this session.
+ * @param saslServer
+ */
+ void setSaslServer(SaslServer saslServer);
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java
new file mode 100644
index 0000000000..d3ec70456f
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java
@@ -0,0 +1,41 @@
+/*
+ *
+ * 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.server.protocol;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+
+public class ExchangeInitialiser
+{
+ public void initialise(ExchangeFactory factory, ExchangeRegistry registry) throws AMQException{
+ define(registry, factory, ExchangeDefaults.DIRECT_EXCHANGE_NAME, ExchangeDefaults.DIRECT_EXCHANGE_CLASS);
+ define(registry, factory, ExchangeDefaults.TOPIC_EXCHANGE_NAME, ExchangeDefaults.TOPIC_EXCHANGE_CLASS);
+ define(registry, factory, ExchangeDefaults.HEADERS_EXCHANGE_NAME, ExchangeDefaults.HEADERS_EXCHANGE_CLASS);
+ }
+
+ private void define(ExchangeRegistry r, ExchangeFactory f,
+ String name, String type) throws AMQException
+ {
+ r.registerExchange(f.createExchange(name, type, true, false, 0));
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/HeartbeatConfig.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/HeartbeatConfig.java
new file mode 100644
index 0000000000..310deaaf55
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/HeartbeatConfig.java
@@ -0,0 +1,67 @@
+/*
+ *
+ * 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.server.protocol;
+
+import org.apache.qpid.configuration.Configured;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+public class HeartbeatConfig
+{
+ @Configured(path = "heartbeat.delay", defaultValue = "5")
+ public int delay = 5;//in secs
+ @Configured(path = "heartbeat.timeoutFactor", defaultValue = "2.0")
+ public double timeoutFactor = 2;
+
+ public double getTimeoutFactor()
+ {
+ return timeoutFactor;
+ }
+
+ public void setTimeoutFactor(double timeoutFactor)
+ {
+ this.timeoutFactor = timeoutFactor;
+ }
+
+ public int getDelay()
+ {
+ return delay;
+ }
+
+ public void setDelay(int delay)
+ {
+ this.delay = delay;
+ }
+
+ int getTimeout(int writeDelay)
+ {
+ return (int) (timeoutFactor * writeDelay);
+ }
+
+ public static HeartbeatConfig getInstance()
+ {
+ return ApplicationRegistry.getInstance().getConfiguredObject(HeartbeatConfig.class);
+ }
+
+ public String toString()
+ {
+ return "HeartBeatConfig{delay = " + delay + " timeoutFactor = " + timeoutFactor + "}";
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java
new file mode 100644
index 0000000000..889acd0142
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java
@@ -0,0 +1,141 @@
+/*
+ *
+ * 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.server.protocol;
+
+import org.apache.qpid.server.management.MBeanOperationParameter;
+import org.apache.qpid.server.management.MBeanAttribute;
+import org.apache.qpid.server.management.MBeanOperation;
+
+import javax.management.openmbean.TabularData;
+import javax.management.JMException;
+import javax.management.MBeanOperationInfo;
+import java.util.Date;
+import java.io.IOException;
+
+/**
+ * The management interface exposed to allow management of Connections.
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+public interface ManagedConnection
+{
+ static final String TYPE = "Connection";
+
+ /**
+ * channel details of all the channels opened for this connection.
+ * @return general channel details
+ * @throws IOException
+ * @throws JMException
+ */
+ @MBeanAttribute(name="Channels",
+ description="channel details of all the channels opened for this connection")
+ TabularData getChannels() throws IOException, JMException;
+
+ /**
+ * Tells the last time, the IO operation was done.
+ * @return last IO time.
+ */
+ @MBeanAttribute(name="LastIOTime",
+ description="The last time, the IO operation was done")
+ Date getLastIoTime();
+
+ /**
+ * Tells the remote address of this connection.
+ * @return remote address
+ */
+ @MBeanAttribute(name="RemoteAddress",
+ description="The remote address of this connection")
+ String getRemoteAddress();
+
+ /**
+ * Tells the total number of bytes written till now.
+ * @return number of bytes written.
+ */
+ @MBeanAttribute(name="WrittenBytes",
+ description="The total number of bytes written till now")
+ Long getWrittenBytes();
+
+ /**
+ * Tells the total number of bytes read till now.
+ * @return number of bytes read.
+ */
+ @MBeanAttribute(name="ReadBytes",
+ description="The total number of bytes read till now")
+ Long getReadBytes();
+
+ /**
+ * Tells the maximum number of channels that can be opened using
+ * this connection. This is useful in setting notifications or
+ * taking required action is there are more channels being created.
+ * @return maximum number of channels allowed to be created.
+ */
+ Long getMaximumNumberOfAllowedChannels();
+
+ /**
+ * Sets the maximum number of channels allowed to be created using
+ * this connection.
+ * @param value
+ */
+ @MBeanAttribute(name="MaximumNumberOfAllowedChannels",
+ description="The maximum number of channels that can be opened using this connection")
+ void setMaximumNumberOfAllowedChannels(Long value);
+
+ //********** Operations *****************//
+
+ /**
+ * Closes all the related channels and unregisters this connection from managed objects.
+ */
+ @MBeanOperation(name="closeConnection",
+ description="Closes this connection and all related channels",
+ impact= MBeanOperationInfo.ACTION)
+ void closeConnection() throws Exception;
+
+ /**
+ * Unsubscribes the consumers and unregisters the channel from managed objects.
+ */
+ @MBeanOperation(name="closeChannel",
+ description="Closes the channel with given channeld and" +
+ "connected consumers will be unsubscribed",
+ impact= MBeanOperationInfo.ACTION)
+ void closeChannel(@MBeanOperationParameter(name="channel Id", description="channel Id")int channelId)
+ throws Exception;
+
+ /**
+ * Commits the transactions if the channel is transactional.
+ * @param channelId
+ * @throws JMException
+ */
+ @MBeanOperation(name="commitTransaction",
+ description="Commits the transactions for given channelID, if the channel is transactional",
+ impact= MBeanOperationInfo.ACTION)
+ void commitTransactions(@MBeanOperationParameter(name="channel Id", description="channel Id")int channelId) throws JMException;
+
+ /**
+ * Rollsback the transactions if the channel is transactional.
+ * @param channelId
+ * @throws JMException
+ */
+ @MBeanOperation(name="rollbackTransactions",
+ description="Rollsback the transactions for given channelId, if the channel is transactional",
+ impact= MBeanOperationInfo.ACTION)
+ void rollbackTransactions(@MBeanOperationParameter(name="channel Id", description="channel Id")int channelId) throws JMException;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java
new file mode 100644
index 0000000000..8b6db5b53f
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java
@@ -0,0 +1,368 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.txn.TxnBuffer;
+import org.apache.qpid.AMQException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Combines the information that make up a deliverable message into a more manageable form.
+ */
+public class AMQMessage
+{
+ private final Set<Object> _tokens = new HashSet<Object>();
+
+ private AMQProtocolSession _publisher;
+
+ private final BasicPublishBody _publishBody;
+
+ private ContentHeaderBody _contentHeaderBody;
+
+ private List<ContentBody> _contentBodies;
+
+ private boolean _redelivered;
+
+ private final long _messageId;
+
+ private final AtomicInteger _referenceCount = new AtomicInteger(1);
+
+ /**
+ * Keeps a track of how many bytes we have received in body frames
+ */
+ private long _bodyLengthReceived = 0;
+
+ /**
+ * The message store in which this message is contained.
+ */
+ private transient final MessageStore _store;
+
+ /**
+ * For non transactional publishes, a message can be stored as
+ * soon as it is complete. For transactional messages it doesnt
+ * need to be stored until the transaction is committed.
+ */
+ private boolean _storeWhenComplete;
+
+ /**
+ * TxnBuffer for transactionally published messages
+ */
+ private TxnBuffer _txnBuffer;
+
+ /**
+ * Flag to indicate whether message has been delivered to a
+ * consumer. Used in implementing return functionality for
+ * messages published with the 'immediate' flag.
+ */
+ private boolean _deliveredToConsumer;
+
+
+ public AMQMessage(MessageStore messageStore, BasicPublishBody publishBody)
+ {
+ this(messageStore, publishBody, true);
+ }
+
+ public AMQMessage(MessageStore messageStore, BasicPublishBody publishBody, boolean storeWhenComplete)
+ {
+ _messageId = messageStore.getNewMessageId();
+ _publishBody = publishBody;
+ _store = messageStore;
+ _contentBodies = new LinkedList<ContentBody>();
+ _storeWhenComplete = storeWhenComplete;
+ }
+
+ public AMQMessage(MessageStore store, long messageId, BasicPublishBody publishBody,
+ ContentHeaderBody contentHeaderBody, List<ContentBody> contentBodies)
+ throws AMQException
+
+ {
+ _publishBody = publishBody;
+ _contentHeaderBody = contentHeaderBody;
+ _contentBodies = contentBodies;
+ _messageId = messageId;
+ _store = store;
+ storeMessage();
+ }
+
+ public AMQMessage(MessageStore store, BasicPublishBody publishBody,
+ ContentHeaderBody contentHeaderBody, List<ContentBody> contentBodies)
+ throws AMQException
+ {
+ this(store, store.getNewMessageId(), publishBody, contentHeaderBody, contentBodies);
+ }
+
+ protected AMQMessage(AMQMessage msg) throws AMQException
+ {
+ this(msg._store, msg._messageId, msg._publishBody, msg._contentHeaderBody, msg._contentBodies);
+ }
+
+ public void storeMessage() throws AMQException
+ {
+ if (isPersistent())
+ {
+ _store.put(this);
+ }
+ }
+
+ public CompositeAMQDataBlock getDataBlock(ByteBuffer encodedDeliverBody, int channel)
+ {
+ AMQFrame[] allFrames = new AMQFrame[1 + _contentBodies.size()];
+
+ allFrames[0] = ContentHeaderBody.createAMQFrame(channel, _contentHeaderBody);
+ for (int i = 1; i < allFrames.length; i++)
+ {
+ allFrames[i] = ContentBody.createAMQFrame(channel, _contentBodies.get(i - 1));
+ }
+ return new CompositeAMQDataBlock(encodedDeliverBody, allFrames);
+ }
+
+ public CompositeAMQDataBlock getDataBlock(int channel, String consumerTag, long deliveryTag)
+ {
+ AMQFrame[] allFrames = new AMQFrame[2 + _contentBodies.size()];
+
+ allFrames[0] = BasicDeliverBody.createAMQFrame(channel, consumerTag, deliveryTag, _redelivered,
+ getExchangeName(), getRoutingKey());
+ allFrames[1] = ContentHeaderBody.createAMQFrame(channel, _contentHeaderBody);
+ for (int i = 2; i < allFrames.length; i++)
+ {
+ allFrames[i] = ContentBody.createAMQFrame(channel, _contentBodies.get(i - 2));
+ }
+ return new CompositeAMQDataBlock(allFrames);
+ }
+
+ public List<AMQBody> getPayload()
+ {
+ List<AMQBody> payload = new ArrayList<AMQBody>(2 + _contentBodies.size());
+ payload.add(_publishBody);
+ payload.add(_contentHeaderBody);
+ payload.addAll(_contentBodies);
+ return payload;
+ }
+
+ public BasicPublishBody getPublishBody()
+ {
+ return _publishBody;
+ }
+
+ public ContentHeaderBody getContentHeaderBody()
+ {
+ return _contentHeaderBody;
+ }
+
+ public void setContentHeaderBody(ContentHeaderBody contentHeaderBody) throws AMQException
+ {
+ _contentHeaderBody = contentHeaderBody;
+ if (_storeWhenComplete && isAllContentReceived())
+ {
+ storeMessage();
+ }
+ }
+
+ public List<ContentBody> getContentBodies()
+ {
+ return _contentBodies;
+ }
+
+ public void setContentBodies(List<ContentBody> contentBodies)
+ {
+ _contentBodies = contentBodies;
+ }
+
+ public void addContentBodyFrame(ContentBody contentBody) throws AMQException
+ {
+ _contentBodies.add(contentBody);
+ _bodyLengthReceived += contentBody.getSize();
+ if (_storeWhenComplete && isAllContentReceived())
+ {
+ storeMessage();
+ }
+ }
+
+ public boolean isAllContentReceived()
+ {
+ return _bodyLengthReceived == _contentHeaderBody.bodySize;
+ }
+
+ public boolean isRedelivered()
+ {
+ return _redelivered;
+ }
+
+ String getExchangeName()
+ {
+ return _publishBody.exchange;
+ }
+
+ String getRoutingKey()
+ {
+ return _publishBody.routingKey;
+ }
+
+ boolean isImmediate()
+ {
+ return _publishBody.immediate;
+ }
+
+ NoConsumersException getNoConsumersException(String queue)
+ {
+ return new NoConsumersException(queue, _publishBody, _contentHeaderBody, _contentBodies);
+ }
+
+ void setRedelivered(boolean redelivered)
+ {
+ _redelivered = redelivered;
+ }
+
+ public long getMessageId()
+ {
+ return _messageId;
+ }
+
+ /**
+ * Threadsafe. Increment the reference count on the message.
+ */
+ public void incrementReference()
+ {
+ _referenceCount.incrementAndGet();
+ }
+
+ /**
+ * Threadsafe. This will decrement the reference count and when it reaches zero will remove the message from the
+ * message store.
+ */
+ public void decrementReference() throws MessageCleanupException
+ {
+ // note that the operation of decrementing the reference count and then removing the message does not
+ // have to be atomic since the ref count starts at 1 and the exchange itself decrements that after
+ // the message has been passed to all queues. i.e. we are
+ // not relying on the all the increments having taken place before the delivery manager decrements.
+ if (_referenceCount.decrementAndGet() == 0)
+ {
+ try
+ {
+ _store.removeMessage(_messageId);
+ }
+ catch(AMQException e)
+ {
+ //to maintain consistency, we revert the count
+ incrementReference();
+ throw new MessageCleanupException(_messageId, e);
+ }
+ }
+ }
+
+ public void setPublisher(AMQProtocolSession publisher)
+ {
+ _publisher = publisher;
+ }
+
+ public AMQProtocolSession getPublisher()
+ {
+ return _publisher;
+ }
+
+ public boolean checkToken(Object token)
+ {
+ if(_tokens.contains(token))
+ {
+ return true;
+ }
+ else
+ {
+ _tokens.add(token);
+ return false;
+ }
+ }
+
+ public void enqueue(AMQQueue queue) throws AMQException
+ {
+ //if the message is not persistent or the queue is not durable
+ //we will not need to recover the association and so do not
+ //need to record it
+ if(isPersistent() && queue.isDurable())
+ {
+ _store.enqueueMessage(queue.getName(), _messageId);
+ }
+ }
+
+ public void dequeue(AMQQueue queue) throws AMQException
+ {
+ //only record associations where both queue and message will survive
+ //a restart, so only need to remove association if this is the case
+ if(isPersistent() && queue.isDurable())
+ {
+ _store.dequeueMessage(queue.getName(), _messageId);
+ }
+ }
+
+ public boolean isPersistent() throws AMQException
+ {
+ if(_contentHeaderBody == null)
+ {
+ throw new AMQException("Cannot determine delivery mode of message. Content header not found.");
+ }
+
+ //todo remove literal values to a constant file such as AMQConstants in common
+ return _contentHeaderBody.properties instanceof BasicContentHeaderProperties
+ &&((BasicContentHeaderProperties) _contentHeaderBody.properties).getDeliveryMode() == 2;
+ }
+
+ public void setTxnBuffer(TxnBuffer buffer)
+ {
+ _txnBuffer = buffer;
+ }
+
+ public TxnBuffer getTxnBuffer()
+ {
+ return _txnBuffer;
+ }
+
+ /**
+ * Called to enforce the 'immediate' flag.
+ * @throws NoConsumersException if the message is marked for
+ * immediate delivery but has not been marked as delivered to a
+ * consumer
+ */
+ public void checkDeliveredToConsumer() throws NoConsumersException{
+ if(isImmediate() && !_deliveredToConsumer)
+ {
+ throw new NoConsumersException(_publishBody, _contentHeaderBody, _contentBodies);
+ }
+ }
+
+ /**
+ * Called when this message is delivered to a consumer. (used to
+ * implement the 'immediate' flag functionality).
+ */
+ public void setDeliveredToConsumer(){
+ _deliveredToConsumer = true;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java
new file mode 100644
index 0000000000..f2f46d43dd
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java
@@ -0,0 +1,867 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.log4j.Logger;
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.management.AMQManagedObject;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.management.Managable;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.txn.TxnBuffer;
+import org.apache.qpid.server.txn.TxnOp;
+
+import javax.management.JMException;
+import javax.management.MBeanException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.NotCompliantMBeanException;
+import javax.management.Notification;
+import javax.management.monitor.MonitorNotification;
+import javax.management.openmbean.*;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * This is an AMQ Queue, and should not be confused with a JMS queue or any other abstraction like
+ * that. It is described fully in RFC 006.
+ */
+public class AMQQueue implements Managable
+{
+ private static final Logger _logger = Logger.getLogger(AMQQueue.class);
+
+ private final String _name;
+
+ /**
+ * null means shared
+ */
+ private final String _owner;
+
+ private final boolean _durable;
+
+ /**
+ * If true, this queue is deleted when the last subscriber is removed
+ */
+ private final boolean _autoDelete;
+
+ /**
+ * Holds subscribers to the queue.
+ */
+ private final SubscriptionSet _subscribers;
+
+ private final SubscriptionFactory _subscriptionFactory;
+
+ /**
+ * Manages message delivery.
+ */
+ private final DeliveryManager _deliveryMgr;
+
+ /**
+ * The queue registry with which this queue is registered.
+ */
+ private final QueueRegistry _queueRegistry;
+
+ /**
+ * Used to track bindings to exchanges so that on deletion they can easily
+ * be cancelled.
+ */
+ private final ExchangeBindings _bindings = new ExchangeBindings(this);
+
+ /**
+ * Executor on which asynchronous delivery will be carriedout where required
+ */
+ private final Executor _asyncDelivery;
+
+ private final AMQQueueMBean _managedObject;
+
+ /**
+ * max allowed size of a single message(in KBytes).
+ */
+ private long _maxAllowedMessageSize = 10000; // 10 MB
+
+ /**
+ * max allowed number of messages on a queue.
+ */
+ private Integer _maxAllowedMessageCount = 10000;
+
+ /**
+ * max allowed size in KBytes for all the messages combined together in a queue.
+ */
+ private long _queueDepth = 10000000; // 10 GB
+
+ /**
+ * total messages received by the queue since startup.
+ */
+ private long _totalMessagesReceived = 0;
+
+ /**
+ * MBean class for AMQQueue. It implements all the management features exposed
+ * for an AMQQueue.
+ */
+ @MBeanDescription("Management Interface for AMQQueue")
+ private final class AMQQueueMBean extends AMQManagedObject implements ManagedQueue
+ {
+ private String _queueName = null;
+
+ // AMQ message attribute names
+ private String[] _msgAttributeNames = {"MessageId",
+ "Header",
+ "Size",
+ "Redelivered"
+ };
+ // AMQ Message attribute descriptions.
+ private String[] _msgAttributeDescriptions = {"Message Id",
+ "Header",
+ "Message size in bytes",
+ "Redelivered"
+ };
+
+ private OpenType[] _msgAttributeTypes = new OpenType[4]; // AMQ message attribute types.
+ private String[] _msgAttributeIndex = {"MessageId"}; // Messages will be indexed according to the messageId.
+ private CompositeType _messageDataType = null; // Composite type for representing AMQ Message data.
+ private TabularType _messagelistDataType = null; // Datatype for representing AMQ messages list.
+
+
+ private CompositeType _msgContentType = null; // For message content
+ private String[] _msgContentAttributes = {"MessageId",
+ "MimeType",
+ "Encoding",
+ "Content"
+ };
+ private String[] _msgContentDescriptions = {"Message Id",
+ "MimeType",
+ "Encoding",
+ "Message content"
+ };
+ private OpenType[] _msgContentAttributeTypes = new OpenType[4];
+
+
+ @MBeanConstructor("Creates an MBean exposing an AMQQueue.")
+ public AMQQueueMBean() throws NotCompliantMBeanException
+ {
+ super(ManagedQueue.class, ManagedQueue.TYPE);
+ init();
+ }
+
+ private void init()
+ {
+ _queueName = jmxEncode(new StringBuffer(_name), 0).toString();
+ try
+ {
+ _msgContentAttributeTypes[0] = SimpleType.LONG; // For message id
+ _msgContentAttributeTypes[1] = SimpleType.STRING; // For MimeType
+ _msgContentAttributeTypes[2] = SimpleType.STRING; // For Encoding
+ _msgContentAttributeTypes[3] = new ArrayType(1, SimpleType.BYTE); // For message content
+ _msgContentType = new CompositeType("MessageContent",
+ "AMQ Message Content",
+ _msgContentAttributes,
+ _msgContentDescriptions,
+ _msgContentAttributeTypes);
+
+
+ _msgAttributeTypes[0] = SimpleType.LONG; // For message id
+ _msgAttributeTypes[1] = new ArrayType(1, SimpleType.STRING); // For header attributes
+ _msgAttributeTypes[2] = SimpleType.LONG; // For size
+ _msgAttributeTypes[3] = SimpleType.BOOLEAN; // For redelivered
+
+ _messageDataType = new CompositeType("Message",
+ "AMQ Message",
+ _msgAttributeNames,
+ _msgAttributeDescriptions,
+ _msgAttributeTypes);
+ _messagelistDataType = new TabularType("Messages",
+ "List of messages",
+ _messageDataType,
+ _msgAttributeIndex);
+ }
+ catch (OpenDataException ex)
+ {
+ _logger.error("OpenDataTypes could not be created.", ex);
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public String getObjectInstanceName()
+ {
+ return _queueName;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public boolean isDurable()
+ {
+ return _durable;
+ }
+
+ public String getOwner()
+ {
+ return _owner;
+ }
+
+ public boolean isAutoDelete()
+ {
+ return _autoDelete;
+ }
+
+ public Integer getMessageCount()
+ {
+ return _deliveryMgr.getQueueMessageCount();
+ }
+
+ public Long getMaximumMessageSize()
+ {
+ return _maxAllowedMessageSize;
+ }
+
+ public void setMaximumMessageSize(Long value)
+ {
+ _maxAllowedMessageSize = value;
+ }
+
+ public Integer getConsumerCount()
+ {
+ return _subscribers.size();
+ }
+
+ public Integer getActiveConsumerCount()
+ {
+ return _subscribers.getWeight();
+ }
+
+ public Long getReceivedMessageCount()
+ {
+ return _totalMessagesReceived;
+ }
+
+ public Integer getMaximumMessageCount()
+ {
+ return _maxAllowedMessageCount;
+ }
+
+ public void setMaximumMessageCount(Integer value)
+ {
+ _maxAllowedMessageCount = value;
+ }
+
+ public Long getQueueDepth()
+ {
+ return _queueDepth;
+ }
+
+ // Sets the queue depth, the max queue size
+ public void setQueueDepth(Long value)
+ {
+ _queueDepth = value;
+ }
+
+ // Returns the size of messages in the queue
+ public Long getQueueSize()
+ {
+ List<AMQMessage> list = _deliveryMgr.getMessages();
+ if (list.size() == 0)
+ {
+ return 0l;
+ }
+
+ long queueSize = 0;
+ for (AMQMessage message : list)
+ {
+ queueSize = queueSize + getMessageSize(message);
+ }
+ return new Long(Math.round(queueSize / 100));
+ }
+
+ // calculates the size of an AMQMessage
+ private long getMessageSize(AMQMessage msg)
+ {
+ if (msg == null)
+ {
+ return 0l;
+ }
+
+ List<ContentBody> cBodies = msg.getContentBodies();
+ long messageSize = 0;
+ for (ContentBody body : cBodies)
+ {
+ if (body != null)
+ {
+ messageSize = messageSize + body.getSize();
+ }
+ }
+ return messageSize;
+ }
+
+ // Checks if there is any notification to be send to the listeners
+ private void checkForNotification(AMQMessage msg)
+ {
+ // Check for message count
+ Integer msgCount = getMessageCount();
+ if (msgCount >= getMaximumMessageCount())
+ {
+ notifyClients("MessageCount = " + msgCount + ", Queue has reached its size limit and is now full.");
+ }
+
+ // Check for received message size
+ long messageSize = getMessageSize(msg);
+ if (messageSize >= getMaximumMessageSize())
+ {
+ notifyClients("MessageSize = " + messageSize + ", Message size (MessageID=" + msg.getMessageId() +
+ ")is higher than the threshold value");
+ }
+
+ // Check for queue size in bytes
+ long queueSize = getQueueSize();
+ if (queueSize >= getQueueDepth())
+ {
+ notifyClients("QueueSize = " + queueSize + ", Queue size has reached the threshold value");
+ }
+ }
+
+ // Send the notification to the listeners
+ private void notifyClients(String notificationMsg)
+ {
+ Notification n = new Notification(
+ MonitorNotification.THRESHOLD_VALUE_EXCEEDED,
+ this,
+ ++_notificationSequenceNumber,
+ System.currentTimeMillis(),
+ notificationMsg);
+
+ _broadcaster.sendNotification(n);
+ }
+
+ public void deleteMessageFromTop() throws JMException
+ {
+ try
+ {
+ _deliveryMgr.removeAMessageFromTop();
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+
+ public void clearQueue() throws JMException
+ {
+ try
+ {
+ _deliveryMgr.clearAllMessages();
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+
+ public CompositeData viewMessageContent(long msgId) throws JMException
+ {
+ List<AMQMessage> list = _deliveryMgr.getMessages();
+ CompositeData messageContent = null;
+ AMQMessage msg = null;
+ for (AMQMessage message : list)
+ {
+ if (message.getMessageId() == msgId)
+ {
+ msg = message;
+ break;
+ }
+ }
+
+ if (msg != null)
+ {
+ // get message content
+ List<ContentBody> cBodies = msg.getContentBodies();
+ List<Byte> msgContent = new ArrayList<Byte>();
+ for (ContentBody body : cBodies)
+ {
+ if (body.getSize() != 0)
+ {
+ ByteBuffer slice = body.payload.slice();
+ for (int j = 0; j < slice.limit(); j++)
+ {
+ msgContent.add(slice.get());
+ }
+ }
+ }
+
+ // Create header attributes list
+ BasicContentHeaderProperties headerProperties = (BasicContentHeaderProperties)msg.getContentHeaderBody().properties;
+ String mimeType = headerProperties.getContentType();
+ String encoding = headerProperties.getEncoding() == null ? "" : headerProperties.getEncoding();
+
+ Object[] itemValues = {msgId, mimeType, encoding, msgContent.toArray(new Byte[0])};
+ messageContent = new CompositeDataSupport(_msgContentType, _msgContentAttributes, itemValues);
+ }
+ else
+ {
+ throw new JMException("AMQMessage with message id = " + msgId + " is not in the " + _queueName );
+ }
+
+ return messageContent;
+ }
+
+ /**
+ * Returns the messages stored in this queue in tabular form.
+ *
+ * @param beginIndex
+ * @param endIndex
+ * @return AMQ messages in tabular form.
+ * @throws JMException
+ */
+ public TabularData viewMessages(int beginIndex, int endIndex) throws JMException
+ {
+ if ((beginIndex > endIndex) || (beginIndex < 1))
+ {
+ throw new JMException("FromIndex = " + beginIndex + ", ToIndex = " + endIndex +
+ "\nFromIndex should be greater than 0 and less than ToIndex");
+ }
+
+ List<AMQMessage> list = _deliveryMgr.getMessages();
+ TabularDataSupport _messageList = new TabularDataSupport(_messagelistDataType);
+
+ if (beginIndex > list.size())
+ {
+ return _messageList;
+ }
+ endIndex = endIndex < list.size() ? endIndex : list.size();
+
+ for (int i = beginIndex; i <= endIndex; i++)
+ {
+ AMQMessage msg = list.get(i - 1);
+ long size = 0;
+ // get message content
+ List<ContentBody> cBodies = msg.getContentBodies();
+ for (ContentBody body : cBodies)
+ {
+ size = size + body.getSize();
+ }
+
+ // Create header attributes list
+ BasicContentHeaderProperties headerProperties = (BasicContentHeaderProperties)msg.getContentHeaderBody().properties;
+ List<String> headerAttribsList = new ArrayList<String>();
+ headerAttribsList.add("App Id=" + headerProperties.getAppId());
+ headerAttribsList.add("MimeType=" + headerProperties.getContentType());
+ headerAttribsList.add("Correlation Id=" + headerProperties.getCorrelationId());
+ headerAttribsList.add("Encoding=" + headerProperties.getEncoding());
+ headerAttribsList.add(headerProperties.toString());
+
+ Object[] itemValues = {msg.getMessageId(),
+ headerAttribsList.toArray(new String[0]),
+ size, msg.isRedelivered()};
+
+ CompositeData messageData = new CompositeDataSupport(_messageDataType,
+ _msgAttributeNames,
+ itemValues);
+ _messageList.put(messageData);
+ }
+
+ return _messageList;
+ }
+
+ /**
+ * Creates all the notifications this MBean can send.
+ *
+ * @return Notifications broadcasted by this MBean.
+ */
+ @Override
+ public MBeanNotificationInfo[] getNotificationInfo()
+ {
+ String[] notificationTypes = new String[]
+ {MonitorNotification.THRESHOLD_VALUE_EXCEEDED};
+ String name = MonitorNotification.class.getName();
+ String description = "An attribute of this MBean has reached threshold value";
+ MBeanNotificationInfo info1 = new MBeanNotificationInfo(notificationTypes,
+ name,
+ description);
+
+ return new MBeanNotificationInfo[]{info1};
+ }
+
+ } // End of AMQMBean class
+
+ public AMQQueue(String name, boolean durable, String owner,
+ boolean autoDelete, QueueRegistry queueRegistry)
+ throws AMQException
+ {
+ this(name, durable, owner, autoDelete, queueRegistry,
+ AsyncDeliveryConfig.getAsyncDeliveryExecutor(), new SubscriptionImpl.Factory());
+ }
+
+ public AMQQueue(String name, boolean durable, String owner,
+ boolean autoDelete, QueueRegistry queueRegistry, SubscriptionFactory subscriptionFactory)
+ throws AMQException
+ {
+ this(name, durable, owner, autoDelete, queueRegistry,
+ AsyncDeliveryConfig.getAsyncDeliveryExecutor(), subscriptionFactory);
+ }
+
+ public AMQQueue(String name, boolean durable, String owner,
+ boolean autoDelete, QueueRegistry queueRegistry, Executor asyncDelivery,
+ SubscriptionFactory subscriptionFactory)
+ throws AMQException
+ {
+
+ this(name, durable, owner, autoDelete, queueRegistry, asyncDelivery, new SubscriptionSet(), subscriptionFactory);
+ }
+
+ public AMQQueue(String name, boolean durable, String owner,
+ boolean autoDelete, QueueRegistry queueRegistry, Executor asyncDelivery)
+ throws AMQException
+ {
+
+ this(name, durable, owner, autoDelete, queueRegistry, asyncDelivery, new SubscriptionSet(),
+ new SubscriptionImpl.Factory());
+ }
+
+ protected AMQQueue(String name, boolean durable, String owner,
+ boolean autoDelete, QueueRegistry queueRegistry,
+ SubscriptionSet subscribers, SubscriptionFactory subscriptionFactory)
+ throws AMQException
+ {
+ this(name, durable, owner, autoDelete, queueRegistry,
+ AsyncDeliveryConfig.getAsyncDeliveryExecutor(), subscribers, subscriptionFactory);
+ }
+
+ protected AMQQueue(String name, boolean durable, String owner,
+ boolean autoDelete, QueueRegistry queueRegistry,
+ SubscriptionSet subscribers)
+ throws AMQException
+ {
+ this(name, durable, owner, autoDelete, queueRegistry,
+ AsyncDeliveryConfig.getAsyncDeliveryExecutor(), subscribers, new SubscriptionImpl.Factory());
+ }
+
+ protected AMQQueue(String name, boolean durable, String owner,
+ boolean autoDelete, QueueRegistry queueRegistry,
+ Executor asyncDelivery, SubscriptionSet subscribers, SubscriptionFactory subscriptionFactory)
+ throws AMQException
+ {
+ if (name == null)
+ {
+ throw new IllegalArgumentException("Queue name must not be null");
+ }
+ if (queueRegistry == null)
+ {
+ throw new IllegalArgumentException("Queue registry must not be null");
+ }
+ _name = name;
+ _durable = durable;
+ _owner = owner;
+ _autoDelete = autoDelete;
+ _queueRegistry = queueRegistry;
+ _asyncDelivery = asyncDelivery;
+ _managedObject = createMBean();
+ _managedObject.register();
+ _subscribers = subscribers;
+ _subscriptionFactory = subscriptionFactory;
+
+ //fixme - Pick one.
+ if (Boolean.getBoolean("concurrentdeliverymanager"))
+ {
+ _logger.warn("Using ConcurrentDeliveryManager");
+ _deliveryMgr = new ConcurrentDeliveryManager(_subscribers, this);
+ }
+ else
+ {
+ _logger.warn("Using SynchronizedDeliveryManager");
+ _deliveryMgr = new SynchronizedDeliveryManager(_subscribers, this);
+ }
+ }
+
+ private AMQQueueMBean createMBean() throws AMQException
+ {
+ try
+ {
+ return new AMQQueueMBean();
+ }
+ catch (NotCompliantMBeanException ex)
+ {
+ throw new AMQException("AMQQueue MBean creation has failed.", ex);
+ }
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public boolean isShared()
+ {
+ return _owner == null;
+ }
+
+ public boolean isDurable()
+ {
+ return _durable;
+ }
+
+ public String getOwner()
+ {
+ return _owner;
+ }
+
+ public boolean isAutoDelete()
+ {
+ return _autoDelete;
+ }
+
+ public int getMessageCount()
+ {
+ return _deliveryMgr.getQueueMessageCount();
+ }
+
+ public ManagedObject getManagedObject()
+ {
+ return _managedObject;
+ }
+
+ public void bind(String routingKey, Exchange exchange)
+ {
+ _bindings.addBinding(routingKey, exchange);
+ }
+
+ public void registerProtocolSession(AMQProtocolSession ps, int channel, String consumerTag, boolean acks)
+ throws AMQException
+ {
+ debug("Registering protocol session {0} with channel {1} and consumer tag {2} with {3}", ps, channel, consumerTag, this);
+
+ Subscription subscription = _subscriptionFactory.createSubscription(channel, ps, consumerTag, acks);
+ _subscribers.addSubscriber(subscription);
+ }
+
+ public void unregisterProtocolSession(AMQProtocolSession ps, int channel, String consumerTag) throws AMQException
+ {
+ debug("Unregistering protocol session {0} with channel {1} and consumer tag {2} from {3}", ps, channel, consumerTag,
+ this);
+
+ Subscription removedSubscription;
+ if ((removedSubscription = _subscribers.removeSubscriber(_subscriptionFactory.createSubscription(channel,
+ ps,
+ consumerTag)))
+ == null)
+ {
+ throw new AMQException("Protocol session with channel " + channel + " and consumer tag " + consumerTag +
+ " and protocol session key " + ps.getKey() + " not registered with queue " + this);
+ }
+
+ // if we are eligible for auto deletion, unregister from the queue registry
+ if (_autoDelete && _subscribers.isEmpty())
+ {
+ autodelete();
+ // we need to manually fire the event to the removed subscription (which was the last one left for this
+ // queue. This is because the delete method uses the subscription set which has just been cleared
+ removedSubscription.queueDeleted(this);
+ }
+ }
+
+ public int delete(boolean checkUnused, boolean checkEmpty) throws AMQException
+ {
+ if (checkUnused && !_subscribers.isEmpty())
+ {
+ _logger.info("Will not delete " + this + " as it is in use.");
+ return 0;
+ }
+ else if (checkEmpty && _deliveryMgr.hasQueuedMessages())
+ {
+ _logger.info("Will not delete " + this + " as it is not empty.");
+ return 0;
+ }
+ else
+ {
+ delete();
+ return _deliveryMgr.getQueueMessageCount();
+ }
+ }
+
+ public void delete() throws AMQException
+ {
+ _subscribers.queueDeleted(this);
+ _bindings.deregister();
+ _queueRegistry.unregisterQueue(_name);
+ _managedObject.unregister();
+ }
+
+ protected void autodelete() throws AMQException
+ {
+ debug("autodeleting {0}", this);
+ delete();
+ }
+
+ public void deliver(AMQMessage msg) throws AMQException
+ {
+ TxnBuffer buffer = msg.getTxnBuffer();
+ if (buffer == null)
+ {
+ //non-transactional
+ record(msg);
+ process(msg);
+ }
+ else
+ {
+ buffer.enlist(new Deliver(msg));
+ }
+ }
+
+ private void record(AMQMessage msg) throws AMQException
+ {
+ msg.enqueue(this);
+ msg.incrementReference();
+ }
+
+ private void process(AMQMessage msg) throws FailedDequeueException
+ {
+ _deliveryMgr.deliver(getName(), msg);
+ try
+ {
+ msg.checkDeliveredToConsumer();
+ updateReceivedMessageCount(msg);
+ }
+ catch (NoConsumersException e)
+ {
+ // as this message will be returned, it should be removed
+ // from the queue:
+ dequeue(msg);
+ }
+ }
+
+ void dequeue(AMQMessage msg) throws FailedDequeueException
+ {
+ try
+ {
+ msg.dequeue(this);
+ msg.decrementReference();
+ }
+ catch (MessageCleanupException e)
+ {
+ //Message was dequeued, but could notthen be deleted
+ //though it is no longer referenced. This should be very
+ //rare and can be detected and cleaned up on recovery or
+ //done through some form of manual intervention.
+ _logger.error(e, e);
+ }
+ catch (AMQException e)
+ {
+ throw new FailedDequeueException(_name, e);
+ }
+ }
+
+ public void deliverAsync()
+ {
+ _deliveryMgr.processAsync(_asyncDelivery);
+ }
+
+ protected SubscriptionManager getSubscribers()
+ {
+ return _subscribers;
+ }
+
+ protected void updateReceivedMessageCount(AMQMessage msg)
+ {
+ _totalMessagesReceived++;
+ _managedObject.checkForNotification(msg);
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+
+ final AMQQueue amqQueue = (AMQQueue) o;
+
+ return (_name.equals(amqQueue._name));
+ }
+
+ public int hashCode()
+ {
+ return _name.hashCode();
+ }
+
+ public String toString()
+ {
+ return "Queue(" + _name + ")@" + System.identityHashCode(this);
+ }
+
+ private void debug(String msg, Object... args)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug(MessageFormat.format(msg, args));
+ }
+ }
+
+ private class Deliver implements TxnOp
+ {
+ private final AMQMessage _msg;
+
+ Deliver(AMQMessage msg)
+ {
+ _msg = msg;
+ }
+
+ public void prepare() throws AMQException
+ {
+ //do the persistent part of the record()
+ _msg.enqueue(AMQQueue.this);
+ }
+
+ public void undoPrepare()
+ {
+ }
+
+ public void commit()
+ {
+ //do the memeory part of the record()
+ _msg.incrementReference();
+ //then process the message
+ try
+ {
+ process(_msg);
+ }
+ catch (FailedDequeueException e)
+ {
+ //TODO: is there anything else we can do here? I think not...
+ _logger.error("Error during commit of a queue delivery: " + e, e);
+ }
+ }
+
+ public void rollback()
+ {
+ }
+ }
+
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/AsyncDeliveryConfig.java b/java/broker/src/main/java/org/apache/qpid/server/queue/AsyncDeliveryConfig.java
new file mode 100644
index 0000000000..ba60c9e003
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/AsyncDeliveryConfig.java
@@ -0,0 +1,56 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.qpid.configuration.Configured;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class AsyncDeliveryConfig
+{
+ private Executor _executor;
+
+ @Configured(path = "delivery.poolsize", defaultValue = "0")
+ public int poolSize;
+
+ public Executor getExecutor()
+ {
+ if (_executor == null)
+ {
+ if (poolSize > 0)
+ {
+ _executor = Executors.newFixedThreadPool(poolSize);
+ }
+ else
+ {
+ _executor = Executors.newCachedThreadPool();
+ }
+ }
+ return _executor;
+ }
+
+ public static Executor getAsyncDeliveryExecutor()
+ {
+ return ApplicationRegistry.getInstance().getConfiguredObject(AsyncDeliveryConfig.class).getExecutor();
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/ConcurrentDeliveryManager.java b/java/broker/src/main/java/org/apache/qpid/server/queue/ConcurrentDeliveryManager.java
new file mode 100644
index 0000000000..dde76e5ba8
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/ConcurrentDeliveryManager.java
@@ -0,0 +1,348 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.util.ConcurrentLinkedQueueAtomicSize;
+import org.apache.qpid.configuration.Configured;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.server.configuration.Configurator;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+/**
+ * Manages delivery of messages on behalf of a queue
+ */
+public class ConcurrentDeliveryManager implements DeliveryManager
+{
+ private static final Logger _log = Logger.getLogger(ConcurrentDeliveryManager.class);
+
+ @Configured(path = "advanced.compressBufferOnQueue",
+ defaultValue = "false")
+ public boolean compressBufferOnQueue;
+ /**
+ * Holds any queued messages
+ */
+ private final Queue<AMQMessage> _messages = new ConcurrentLinkedQueueAtomicSize<AMQMessage>();
+ //private int _messageCount;
+ /**
+ * Ensures that only one asynchronous task is running for this manager at
+ * any time.
+ */
+ private final AtomicBoolean _processing = new AtomicBoolean();
+ /**
+ * The subscriptions on the queue to whom messages are delivered
+ */
+ private final SubscriptionManager _subscriptions;
+
+ /**
+ * A reference to the queue we are delivering messages for. We need this to be able
+ * to pass the code that handles acknowledgements a handle on the queue.
+ */
+ private final AMQQueue _queue;
+
+
+ /**
+ * Lock used to ensure that an channel that becomes unsuspended during the start of the queueing process is forced
+ * to wait till the first message is added to the queue. This will ensure that the _queue has messages to be delivered
+ * via the async thread.
+ * <p/>
+ * Lock is used to control access to hasQueuedMessages() and over the addition of messages to the queue.
+ */
+ private ReentrantLock _lock = new ReentrantLock();
+
+
+ ConcurrentDeliveryManager(SubscriptionManager subscriptions, AMQQueue queue)
+ {
+
+ //Set values from configuration
+ Configurator.configure(this);
+
+ if (compressBufferOnQueue)
+ {
+ _log.info("Compressing Buffers on queue.");
+ }
+
+ _subscriptions = subscriptions;
+ _queue = queue;
+ }
+
+ /**
+ * @return boolean if we are queueing
+ */
+ private boolean queueing()
+ {
+ return hasQueuedMessages();
+ }
+
+
+ /**
+ * @param msg to enqueue
+ * @return true if we are queue this message
+ */
+ private boolean enqueue(AMQMessage msg)
+ {
+ if (msg.isImmediate())
+ {
+ return false;
+ }
+ else
+ {
+ _lock.lock();
+ try
+ {
+ if (queueing())
+ {
+ return addMessageToQueue(msg);
+ }
+ else
+ {
+ return false;
+ }
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+ }
+
+ private void startQueueing(AMQMessage msg)
+ {
+ if (!msg.isImmediate())
+ {
+ addMessageToQueue(msg);
+ }
+ }
+
+ private boolean addMessageToQueue(AMQMessage msg)
+ {
+ // Shrink the ContentBodies to their actual size to save memory.
+ if (compressBufferOnQueue)
+ {
+ Iterator it = msg.getContentBodies().iterator();
+ while (it.hasNext())
+ {
+ ContentBody cb = (ContentBody) it.next();
+ cb.reduceBufferToFit();
+ }
+ }
+
+ _messages.offer(msg);
+
+ return true;
+ }
+
+
+ public boolean hasQueuedMessages()
+ {
+
+ _lock.lock();
+ try
+ {
+ return !_messages.isEmpty();
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+
+
+ }
+
+ public int getQueueMessageCount()
+ {
+ return getMessageCount();
+ }
+
+ /**
+ * This is an EXPENSIVE opperation to perform with a ConcurrentLinkedQueue as it must run the queue to determine size.
+ * The ConcurrentLinkedQueueAtomicSize uses an AtomicInteger to record the number of elements on the queue.
+ *
+ * @return int the number of messages in the delivery queue.
+ */
+ private int getMessageCount()
+ {
+ return _messages.size();
+ }
+
+
+ public synchronized List<AMQMessage> getMessages()
+ {
+ return new ArrayList<AMQMessage>(_messages);
+ }
+
+ public synchronized void removeAMessageFromTop() throws AMQException
+ {
+ AMQMessage msg = poll();
+ if (msg != null)
+ {
+ msg.dequeue(_queue);
+ }
+ }
+
+ public synchronized void clearAllMessages() throws AMQException
+ {
+ AMQMessage msg = poll();
+ while (msg != null)
+ {
+ msg.dequeue(_queue);
+ msg = poll();
+ }
+ }
+
+ /**
+ * Only one thread should ever execute this method concurrently, but
+ * it can do so while other threads invoke deliver().
+ */
+ private void processQueue()
+ {
+ try
+ {
+ boolean hasSubscribers = _subscriptions.hasActiveSubscribers();
+ AMQMessage message = peek();
+
+ //While we have messages to send and subscribers to send them to.
+ while (message != null && hasSubscribers)
+ {
+ // _log.debug("Have messages(" + _messages.size() + ") and subscribers");
+ Subscription next = _subscriptions.nextSubscriber(message);
+ //FIXME Is there still not the chance that this subscribe could be suspended between here and the send?
+
+ //We don't synchronize access to subscribers so need to re-check
+ if (next != null)
+ {
+ next.send(message, _queue);
+ poll();
+ message = peek();
+ }
+ else
+ {
+ hasSubscribers = false;
+ }
+ }
+ }
+ catch (FailedDequeueException e)
+ {
+ _log.error("Unable to deliver message as dequeue failed: " + e, e);
+ }
+ finally
+ {
+ _log.debug("End of processQueue: (" + getQueueMessageCount() + ")" + " subscribers:" + _subscriptions.hasActiveSubscribers());
+ }
+ }
+
+ private AMQMessage peek()
+ {
+ return _messages.peek();
+ }
+
+ private AMQMessage poll()
+ {
+ return _messages.poll();
+ }
+
+ Runner asyncDelivery = new Runner();
+
+ public void processAsync(Executor executor)
+ {
+ _log.debug("Processing Async. Queued:" + hasQueuedMessages() + "(" + getQueueMessageCount() + ")" +
+ " Active:" + _subscriptions.hasActiveSubscribers() +
+ " Processing:" + _processing.get());
+
+ if (hasQueuedMessages() && _subscriptions.hasActiveSubscribers())
+ {
+ //are we already running? if so, don't re-run
+ if (_processing.compareAndSet(false, true))
+ {
+ executor.execute(asyncDelivery);
+ }
+ }
+ }
+
+ public void deliver(String name, AMQMessage msg) throws FailedDequeueException
+ {
+ // first check whether we are queueing, and enqueue if we are
+ if (!enqueue(msg))
+ {
+ // not queueing so deliver message to 'next' subscriber
+ _lock.lock();
+ try
+ {
+ Subscription s = _subscriptions.nextSubscriber(msg);
+ if (s == null)
+ {
+ if (!msg.isImmediate())
+ {
+ // no subscribers yet so enter 'queueing' mode and queue this message
+ startQueueing(msg);
+ }
+ }
+ else
+ {
+ s.send(msg, _queue);
+ msg.setDeliveredToConsumer();
+ }
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+ }
+
+ private class Runner implements Runnable
+ {
+ public void run()
+ {
+ boolean running = true;
+ while (running)
+ {
+ processQueue();
+
+ //Check that messages have not been added since we did our last peek();
+ // Synchronize with the thread that adds to the queue.
+ // If the queue is still empty then we can exit
+ _lock.lock();
+ try
+ {
+ if (!(hasQueuedMessages() && _subscriptions.hasActiveSubscribers()))
+ {
+ running = false;
+ _processing.set(false);
+ }
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java
new file mode 100644
index 0000000000..3b73072e30
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java
@@ -0,0 +1,50 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.qpid.AMQException;
+
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class DefaultQueueRegistry implements QueueRegistry
+{
+ private ConcurrentMap<String, AMQQueue> _queueMap = new ConcurrentHashMap<String, AMQQueue>();
+
+ public DefaultQueueRegistry()
+ {
+ }
+
+ public void registerQueue(AMQQueue queue) throws AMQException
+ {
+ _queueMap.put(queue.getName(), queue);
+ }
+
+ public void unregisterQueue(String name) throws AMQException
+ {
+ _queueMap.remove(name);
+ }
+
+ public AMQQueue getQueue(String name)
+ {
+ return _queueMap.get(name);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java b/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java
new file mode 100644
index 0000000000..dadf86c1d8
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java
@@ -0,0 +1,76 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.qpid.AMQException;
+
+import java.util.concurrent.Executor;
+import java.util.List;
+
+interface DeliveryManager
+{
+ /**
+ * Determines whether there are queued messages. Sets _queueing to false if
+ * there are no queued messages. This needs to be atomic.
+ *
+ * @return true if there are queued messages
+ */
+ boolean hasQueuedMessages();
+
+ /**
+ * This method should not be used to determin if there are messages in the queue.
+ *
+ * @return int The number of messages in the queue
+ * @use hasQueuedMessages() for all controls relating to having messages on the queue.
+ */
+ int getQueueMessageCount();
+
+ /**
+ * Requests that the delivery manager start processing the queue asynchronously
+ * if there is work that can be done (i.e. there are messages queued up and
+ * subscribers that can receive them.
+ * <p/>
+ * This should be called when subscribers are added, but only after the consume-ok
+ * message has been returned as message delivery may start immediately. It should also
+ * be called after unsuspending a client.
+ * <p/>
+ *
+ * @param executor the executor on which the delivery should take place
+ */
+ void processAsync(Executor executor);
+
+ /**
+ * Handles message delivery. The delivery manager is always in one of two modes;
+ * it is either queueing messages for asynchronous delivery or delivering
+ * directly.
+ *
+ * @param name the name of the entity on whose behalf we are delivering the message
+ * @param msg the message to deliver
+ * @throws org.apache.qpid.server.queue.FailedDequeueException if the message could not be dequeued
+ */
+ void deliver(String name, AMQMessage msg) throws FailedDequeueException;
+
+ void removeAMessageFromTop() throws AMQException;
+
+ void clearAllMessages() throws AMQException;
+
+ List<AMQMessage> getMessages();
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java b/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java
new file mode 100644
index 0000000000..684e312fa3
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java
@@ -0,0 +1,112 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.AMQException;
+
+import java.util.List;
+import java.util.HashSet;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * When a queue is deleted, it should be deregistered from any
+ * exchange it has been bound to. This class assists in this task,
+ * by keeping track of all bindings for a given queue.
+ */
+class ExchangeBindings
+{
+ static class ExchangeBinding
+ {
+ private final Exchange exchange;
+ private final String routingKey;
+
+ ExchangeBinding(String routingKey, Exchange exchange)
+ {
+ this.routingKey = routingKey;
+ this.exchange = exchange;
+ }
+
+ void unbind(AMQQueue queue) throws AMQException
+ {
+ exchange.deregisterQueue(routingKey, queue);
+ }
+
+ public Exchange getExchange()
+ {
+ return exchange;
+ }
+
+ public String getRoutingKey()
+ {
+ return routingKey;
+ }
+
+ public int hashCode()
+ {
+ return exchange.hashCode() + routingKey.hashCode();
+ }
+
+ public boolean equals(Object o)
+ {
+ if (!(o instanceof ExchangeBinding)) return false;
+ ExchangeBinding eb = (ExchangeBinding) o;
+ return exchange.equals(eb.exchange) && routingKey.equals(eb.routingKey);
+ }
+ }
+
+ private final List<ExchangeBinding> _bindings = new CopyOnWriteArrayList<ExchangeBinding>();
+ private final AMQQueue _queue;
+
+ ExchangeBindings(AMQQueue queue)
+ {
+ _queue = queue;
+ }
+
+ /**
+ * Adds the specified binding to those being tracked.
+ * @param routingKey the routing key with which the queue whose bindings
+ * are being tracked by the instance has been bound to the exchange
+ * @param exchange the exchange bound to
+ */
+ void addBinding(String routingKey, Exchange exchange)
+ {
+ _bindings.add(new ExchangeBinding(routingKey, exchange));
+ }
+
+ /**
+ * Deregisters this queue from any exchange it has been bound to
+ */
+ void deregister() throws AMQException
+ {
+ //remove duplicates at this point
+ HashSet<ExchangeBinding> copy = new HashSet<ExchangeBinding>(_bindings);
+ for (ExchangeBinding b : copy)
+ {
+ b.unbind(_queue);
+ }
+ }
+
+ List<ExchangeBinding> getExchangeBindings()
+ {
+ return _bindings;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java b/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java
new file mode 100644
index 0000000000..b74c49e6e1
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java
@@ -0,0 +1,39 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.qpid.AMQException;
+
+/**
+ * Signals that the dequeue of a message from a queue failed
+ */
+public class FailedDequeueException extends AMQException
+{
+ public FailedDequeueException(String queue)
+ {
+ super("Failed to dequeue message from " + queue);
+ }
+
+ public FailedDequeueException(String queue, AMQException e)
+ {
+ super("Failed to dequeue message from " + queue, e);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java b/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java
new file mode 100644
index 0000000000..3a818cf31a
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java
@@ -0,0 +1,220 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.qpid.server.management.MBeanAttribute;
+import org.apache.qpid.server.management.MBeanOperation;
+import org.apache.qpid.server.management.MBeanOperationParameter;
+
+import javax.management.JMException;
+import javax.management.MBeanOperationInfo;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.CompositeData;
+import java.io.IOException;
+
+/**
+ * The management interface exposed to allow management of a queue.
+ * @author Robert J. Greig
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+public interface ManagedQueue
+{
+ static final String TYPE = "Queue";
+
+ /**
+ * Returns the Name of the ManagedQueue.
+ * @return the name of the managedQueue.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="Name", description = "Name of the " + TYPE)
+ String getName() throws IOException;
+
+ /**
+ * Tells whether this ManagedQueue is durable or not.
+ * @return true if this ManagedQueue is a durable queue.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="Durable", description = "true if the AMQQueue is durable")
+ boolean isDurable() throws IOException;
+
+ /**
+ * Tells the Owner of the ManagedQueue.
+ * @return the owner's name.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="Owner", description = "Owner")
+ String getOwner() throws IOException;
+
+ /**
+ * Tells if the ManagedQueue is set to AutoDelete.
+ * @return true if the ManagedQueue is set to AutoDelete.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="AutoDelete", description = "true if the AMQQueue is AutoDelete")
+ boolean isAutoDelete() throws IOException;
+
+ /**
+ * Total number of messages on the queue, which are yet to be delivered to the consumer(s).
+ * @return number of undelivered message in the Queue.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="MessageCount",
+ description = "Total number of undelivered messages on the queue")
+ Integer getMessageCount() throws IOException;
+
+ /**
+ * Returns the maximum size of a message (in kbytes) allowed to be accepted by the
+ * ManagedQueue. This is useful in setting notifications or taking
+ * appropriate action, if the size of the message received is more than
+ * the allowed size.
+ * @return the maximum size of a message allowed to be aceepted by the
+ * ManagedQueue.
+ * @throws IOException
+ */
+ Long getMaximumMessageSize() throws IOException;
+
+ /**
+ * Sets the maximum size of the message (in kbytes) that is allowed to be
+ * accepted by the Queue.
+ * @param size maximum size of message.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="MaximumMessageSize",
+ description="Maximum size(KB) of a message allowed for this Queue")
+ void setMaximumMessageSize(Long size) throws IOException;
+
+ /**
+ * Returns the total number of subscribers to the queue.
+ * @return the number of subscribers.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="ConsumerCount", description="The total number of subscribers to the queue")
+ Integer getConsumerCount() throws IOException;
+
+ /**
+ * Returns the total number of active subscribers to the queue.
+ * @return the number of active subscribers
+ * @throws IOException
+ */
+ @MBeanAttribute(name="ActiveConsumerCount", description="The total number of active subscribers to the queue")
+ Integer getActiveConsumerCount() throws IOException;
+
+ /**
+ * Tells the total number of messages receieved by the queue since startup.
+ * @return total number of messages received.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="ReceivedMessageCount",
+ description="The total number of messages receieved by the queue since startup")
+ Long getReceivedMessageCount() throws IOException;
+
+ /**
+ * Tells the maximum number of messages that can be stored in the queue.
+ * This is useful in setting the notifications or taking required
+ * action is the number of message increase this limit.
+ * @return maximum muber of message allowed to be stored in the queue.
+ * @throws IOException
+ */
+ Integer getMaximumMessageCount() throws IOException;
+
+ /**
+ * Sets the maximum number of messages allowed to be stored in the queue.
+ * @param value the maximum number of messages allowed to be stored in the queue.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="MaximumMessageCount",
+ description="The maximum number of messages allowed to be stored in the queue")
+ void setMaximumMessageCount(Integer value) throws IOException;
+
+ /**
+ * Size of messages in the queue
+ * @return
+ * @throws IOException
+ */
+ @MBeanAttribute(name="QueueSize", description="Size of messages(KB) in the queue")
+ Long getQueueSize() throws IOException;
+
+ /**
+ * Tells the maximum size of all the messages combined together,
+ * that can be stored in the queue. This is useful for setting notifications
+ * or taking required action if the size of messages stored in the queue
+ * increases over this limit.
+ * @return maximum size of the all the messages allowed for the queue.
+ * @throws IOException
+ */
+ Long getQueueDepth() throws IOException;
+
+ /**
+ * Sets the maximum size of all the messages together, that can be stored
+ * in the queue.
+ * @param value
+ * @throws IOException
+ */
+ @MBeanAttribute(name="QueueDepth",
+ description="The size(KB) of all the messages together, that can be stored in the queue")
+ void setQueueDepth(Long value) throws IOException;
+
+
+
+ //********** Operations *****************//
+
+
+ /**
+ * Returns a subset of all the messages stored in the queue. The messages
+ * are returned based on the given index numbers.
+ * @param fromIndex
+ * @param toIndex
+ * @return
+ * @throws IOException
+ * @throws JMException
+ */
+ @MBeanOperation(name="viewMessages",
+ description="shows messages in this queue with given indexes. eg. from index 1 - 100")
+ TabularData viewMessages(@MBeanOperationParameter(name="from index", description="from index")int fromIndex,
+ @MBeanOperationParameter(name="to index", description="to index")int toIndex)
+ throws IOException, JMException;
+
+ /**
+ * Deletes the first message from top.
+ * @throws IOException
+ * @throws JMException
+ */
+ @MBeanOperation(name="deleteMessageFromTop",
+ description="Deletes the first message from top",
+ impact= MBeanOperationInfo.ACTION)
+ void deleteMessageFromTop() throws IOException, JMException;
+
+ /**
+ * Clears the queue by deleting all the undelivered messages from the queue.
+ * @throws IOException
+ * @throws JMException
+ */
+ @MBeanOperation(name="clearQueue",
+ description="Clears the queue by deleting all the undelivered messages from the queue",
+ impact= MBeanOperationInfo.ACTION)
+ void clearQueue() throws IOException, JMException;
+
+ @MBeanOperation(name="viewMessageContent",
+ description="Returns the message content along with MimeType and Encoding")
+ CompositeData viewMessageContent(@MBeanOperationParameter(name="Message Id", description="Message Id")long messageId)
+ throws IOException, JMException;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java
new file mode 100644
index 0000000000..bfe0a0ecf1
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java
@@ -0,0 +1,35 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.qpid.AMQException;
+
+/**
+ * Signals that the removal of a message once its refcount reached
+ * zero failed.
+ */
+public class MessageCleanupException extends AMQException
+{
+ public MessageCleanupException(long messageId, AMQException e)
+ {
+ super("Failed to cleanup message with id " + messageId, e);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java b/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java
new file mode 100644
index 0000000000..2d37b806f6
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java
@@ -0,0 +1,57 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.protocol.AMQConstant;
+
+import java.util.List;
+
+/**
+ * Signals that no consumers exist for a message at a given point in time.
+ * Used if a message has immediate=true and there are no consumers registered
+ * with the queue.
+ */
+public class NoConsumersException extends RequiredDeliveryException
+{
+ public NoConsumersException(String queue,
+ BasicPublishBody publishBody,
+ ContentHeaderBody contentHeaderBody,
+ List<ContentBody> contentBodies)
+ {
+ super("Immediate delivery to " + queue + " is not possible.", publishBody, contentHeaderBody, contentBodies);
+ }
+
+ public NoConsumersException(BasicPublishBody publishBody,
+ ContentHeaderBody contentHeaderBody,
+ List<ContentBody> contentBodies)
+ {
+ super("Immediate delivery is not possible.", publishBody, contentHeaderBody, contentBodies);
+ }
+
+ public int getReplyCode()
+ {
+ return AMQConstant.NO_CONSUMERS.getCode();
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java
new file mode 100644
index 0000000000..c83f17b98c
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java
@@ -0,0 +1,33 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.qpid.AMQException;
+
+
+public interface QueueRegistry
+{
+ void registerQueue(AMQQueue queue) throws AMQException;
+
+ void unregisterQueue(String name) throws AMQException;
+
+ AMQQueue getQueue(String name);
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/Subscription.java b/java/broker/src/main/java/org/apache/qpid/server/queue/Subscription.java
new file mode 100644
index 0000000000..dfc16a7c71
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/Subscription.java
@@ -0,0 +1,32 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.qpid.AMQException;
+
+public interface Subscription
+{
+ void send(AMQMessage msg, AMQQueue queue) throws FailedDequeueException;
+
+ boolean isSuspended();
+
+ void queueDeleted(AMQQueue queue);
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.java b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.java
new file mode 100644
index 0000000000..0fd44e4fbc
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.java
@@ -0,0 +1,40 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.AMQException;
+
+/**
+ * Allows the customisation of the creation of a subscription. This is typically done within an AMQQueue. This
+ * factory primarily assists testing although in future more sophisticated subscribers may need a different
+ * subscription implementation.
+ *
+ * @see org.apache.qpid.server.queue.AMQQueue
+ */
+public interface SubscriptionFactory
+{
+ Subscription createSubscription(int channel, AMQProtocolSession protocolSession, String consumerTag, boolean acks)
+ throws AMQException;
+
+ Subscription createSubscription(int channel, AMQProtocolSession protocolSession,String consumerTag)
+ throws AMQException;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java
new file mode 100644
index 0000000000..5cad28b80d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java
@@ -0,0 +1,191 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.log4j.Logger;
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQDataBlock;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.BasicDeliverBody;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+
+/**
+ * Encapsulation of a supscription to a queue.
+ * <p/>
+ * Ties together the protocol session of a subscriber, the consumer tag that
+ * was given out by the broker and the channel id.
+ * <p/>
+ */
+public class SubscriptionImpl implements Subscription
+{
+ private static final Logger _logger = Logger.getLogger(SubscriptionImpl.class);
+
+ public final AMQChannel channel;
+
+ public final AMQProtocolSession protocolSession;
+
+ public final String consumerTag;
+
+ private final Object sessionKey;
+
+ /**
+ * True if messages need to be acknowledged
+ */
+ private final boolean _acks;
+
+ public static class Factory implements SubscriptionFactory
+ {
+ public SubscriptionImpl createSubscription(int channel, AMQProtocolSession protocolSession, String consumerTag, boolean acks)
+ throws AMQException
+ {
+ return new SubscriptionImpl(channel, protocolSession, consumerTag, acks);
+ }
+
+ public SubscriptionImpl createSubscription(int channel, AMQProtocolSession protocolSession, String consumerTag)
+ throws AMQException
+ {
+ return new SubscriptionImpl(channel, protocolSession, consumerTag);
+ }
+ }
+
+ public SubscriptionImpl(int channelId, AMQProtocolSession protocolSession,
+ String consumerTag, boolean acks)
+ throws AMQException
+ {
+ AMQChannel channel = protocolSession.getChannel(channelId);
+ if (channel == null)
+ {
+ throw new NullPointerException("channel not found in protocol session");
+ }
+
+ this.channel = channel;
+ this.protocolSession = protocolSession;
+ this.consumerTag = consumerTag;
+ sessionKey = protocolSession.getKey();
+ _acks = acks;
+ }
+
+ public SubscriptionImpl(int channel, AMQProtocolSession protocolSession,
+ String consumerTag)
+ throws AMQException
+ {
+ this(channel, protocolSession, consumerTag, false);
+ }
+
+ public boolean equals(Object o)
+ {
+ return (o instanceof SubscriptionImpl) && equals((SubscriptionImpl) o);
+ }
+
+ /**
+ * Equality holds if the session matches and the channel and consumer tag are the same.
+ */
+ private boolean equals(SubscriptionImpl psc)
+ {
+ return sessionKey.equals(psc.sessionKey)
+ && psc.channel == channel
+ && psc.consumerTag.equals(consumerTag);
+ }
+
+ public int hashCode()
+ {
+ return sessionKey.hashCode();
+ }
+
+ public String toString()
+ {
+ return "[channel=" + channel + ", consumerTag=" + consumerTag + ", session=" + protocolSession.getKey() + "]";
+ }
+
+ /**
+ * This method can be called by each of the publisher threads.
+ * As a result all changes to the channel object must be thread safe.
+ *
+ * @param msg
+ * @param queue
+ * @throws AMQException
+ */
+ public void send(AMQMessage msg, AMQQueue queue) throws FailedDequeueException
+ {
+ if (msg != null)
+ {
+ // if we do not need to wait for client acknowledgements
+ // we can decrement the reference count immediately.
+
+ // By doing this _before_ the send we ensure that it
+ // doesn't get sent if it can't be dequeued, preventing
+ // duplicate delivery on recovery.
+
+ // The send may of course still fail, in which case, as
+ // the message is unacked, it will be lost.
+ if (!_acks)
+ {
+ queue.dequeue(msg);
+ }
+ synchronized(channel)
+ {
+ long deliveryTag = channel.getNextDeliveryTag();
+
+ if (_acks)
+ {
+ channel.addUnacknowledgedMessage(msg, deliveryTag, consumerTag, queue);
+ }
+
+ ByteBuffer deliver = createEncodedDeliverFrame(deliveryTag, msg.getRoutingKey(), msg.getExchangeName());
+ AMQDataBlock frame = msg.getDataBlock(deliver, channel.getChannelId());
+
+ protocolSession.writeFrame(frame);
+ }
+ }
+ else
+ {
+ _logger.error("Attempt to send Null message", new NullPointerException());
+ }
+ }
+
+ public boolean isSuspended()
+ {
+ return channel.isSuspended();
+ }
+
+ /**
+ * Callback indicating that a queue has been deleted.
+ *
+ * @param queue
+ */
+ public void queueDeleted(AMQQueue queue)
+ {
+ channel.queueDeleted(queue);
+ }
+
+ private ByteBuffer createEncodedDeliverFrame(long deliveryTag, String routingKey, String exchange)
+ {
+ AMQFrame deliverFrame = BasicDeliverBody.createAMQFrame(channel.getChannelId(), consumerTag,
+ deliveryTag, false, exchange,
+ routingKey);
+ ByteBuffer buf = ByteBuffer.allocate((int) deliverFrame.getSize()); // XXX: Could cast be a problem?
+ deliverFrame.writePayload(buf);
+ buf.flip();
+ return buf;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.java b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.java
new file mode 100644
index 0000000000..353b461c8d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * 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.server.queue;
+
+/**
+ * Abstraction of actor that will determine the subscriber to whom
+ * a message will be sent.
+ */
+public interface SubscriptionManager
+{
+ public boolean hasActiveSubscribers();
+ public Subscription nextSubscriber(AMQMessage msg);
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java
new file mode 100644
index 0000000000..7cc3f5f719
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java
@@ -0,0 +1,183 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.log4j.Logger;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Holds a set of subscriptions for a queue and manages the round
+ * robin-ing of deliver etc.
+ */
+class SubscriptionSet implements WeightedSubscriptionManager
+{
+ private static final Logger _log = Logger.getLogger(SubscriptionSet.class);
+
+ /**
+ * List of registered subscribers
+ */
+ private List<Subscription> _subscriptions = new CopyOnWriteArrayList<Subscription>();
+
+ /**
+ * Used to control the round robin delivery of content
+ */
+ private int _currentSubscriber;
+
+ /**
+ * Accessor for unit tests.
+ */
+ int getCurrentSubscriber()
+ {
+ return _currentSubscriber;
+ }
+
+ public void addSubscriber(Subscription subscription)
+ {
+ _subscriptions.add(subscription);
+ }
+
+ /**
+ * Remove the subscription, returning it if it was found
+ * @param subscription
+ * @return null if no match was found
+ */
+ public Subscription removeSubscriber(Subscription subscription)
+ {
+ boolean isRemoved = _subscriptions.remove(subscription); // TODO: possibly need O(1) operation here.
+ if (isRemoved)
+ {
+ return subscription;
+ }
+ else
+ {
+ debugDumpSubscription(subscription);
+ return null;
+ }
+ }
+
+ private void debugDumpSubscription(Subscription subscription)
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Subscription " + subscription + " not found. Dumping subscriptions:");
+ for (Subscription s : _subscriptions)
+ {
+ _log.debug("Subscription: " + s);
+ }
+ _log.debug("Subscription dump complete");
+ }
+ }
+
+ /**
+ * Return the next unsuspended subscription or null if not found.
+ *
+ * Performance note:
+ * This method can scan all items twice when looking for a subscription that is not
+ * suspended. The worst case occcurs when all subscriptions are suspended. However, it is does this
+ * without synchronisation and subscriptions may be added and removed concurrently. Also note that because of
+ * race conditions and when subscriptions are removed between calls to nextSubscriber, the
+ * IndexOutOfBoundsException also causes the scan to start at the beginning.
+ */
+ public Subscription nextSubscriber(AMQMessage msg)
+ {
+ if (_subscriptions.isEmpty())
+ {
+ return null;
+ }
+
+ try {
+ final Subscription result = nextSubscriber();
+ if (result == null) {
+ _currentSubscriber = 0;
+ return nextSubscriber();
+ } else {
+ return result;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ _currentSubscriber = 0;
+ return nextSubscriber();
+ }
+ }
+
+ private Subscription nextSubscriber()
+ {
+ final ListIterator<Subscription> iterator = _subscriptions.listIterator(_currentSubscriber);
+ while (iterator.hasNext()) {
+ Subscription subscription = iterator.next();
+ ++_currentSubscriber;
+ subscriberScanned();
+ if (!subscription.isSuspended()) {
+ return subscription;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Overridden in test classes.
+ */
+ protected void subscriberScanned()
+ {
+ }
+
+ public boolean isEmpty()
+ {
+ return _subscriptions.isEmpty();
+ }
+
+ public boolean hasActiveSubscribers()
+ {
+ for (Subscription s : _subscriptions)
+ {
+ if (!s.isSuspended()) return true;
+ }
+ return false;
+ }
+
+ public int getWeight()
+ {
+ int count = 0;
+ for (Subscription s : _subscriptions)
+ {
+ if (!s.isSuspended()) count++;
+ }
+ return count;
+ }
+
+ /**
+ * Notification that a queue has been deleted. This is called so that the subscription can inform the
+ * channel, which in turn can update its list of unacknowledged messages.
+ * @param queue
+ */
+ public void queueDeleted(AMQQueue queue)
+ {
+ for (Subscription s : _subscriptions)
+ {
+ s.queueDeleted(queue);
+ }
+ }
+
+ int size() {
+ return _subscriptions.size();
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/SynchronizedDeliveryManager.java b/java/broker/src/main/java/org/apache/qpid/server/queue/SynchronizedDeliveryManager.java
new file mode 100644
index 0000000000..d2e53717af
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SynchronizedDeliveryManager.java
@@ -0,0 +1,255 @@
+/*
+ *
+ * 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.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.log4j.Logger;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Manages delivery of messages on behalf of a queue
+ */
+class SynchronizedDeliveryManager implements DeliveryManager
+{
+ private static final Logger _log = Logger.getLogger(ConcurrentDeliveryManager.class);
+
+ /**
+ * Holds any queued messages
+ */
+ private final Queue<AMQMessage> _messages = new LinkedList<AMQMessage>();
+ /**
+ * Ensures that only one asynchronous task is running for this manager at
+ * any time.
+ */
+ private final AtomicBoolean _processing = new AtomicBoolean();
+ /**
+ * The subscriptions on the queue to whom messages are delivered
+ */
+ private final SubscriptionManager _subscriptions;
+
+ /**
+ * An indication of the mode we are in. If this is true then messages are
+ * being queued up in _messages for asynchronous delivery. If it is false
+ * then messages can be delivered directly as they come in.
+ */
+ private volatile boolean _queueing;
+
+ /**
+ * A reference to the queue we are delivering messages for. We need this to be able
+ * to pass the code that handles acknowledgements a handle on the queue.
+ */
+ private final AMQQueue _queue;
+
+ SynchronizedDeliveryManager(SubscriptionManager subscriptions, AMQQueue queue)
+ {
+ _subscriptions = subscriptions;
+ _queue = queue;
+ }
+
+ private synchronized boolean enqueue(AMQMessage msg)
+ {
+ if (msg.isImmediate())
+ {
+ return false;
+ }
+ else
+ {
+ if (_queueing)
+ {
+ _messages.offer(msg);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ private synchronized void startQueueing(AMQMessage msg)
+ {
+ _queueing = true;
+ enqueue(msg);
+ }
+
+ /**
+ * Determines whether there are queued messages. Sets _queueing to false if
+ * there are no queued messages. This needs to be atomic.
+ *
+ * @return true if there are queued messages
+ */
+ public synchronized boolean hasQueuedMessages()
+ {
+ boolean empty = _messages.isEmpty();
+ if (empty)
+ {
+ _queueing = false;
+ }
+ return !empty;
+ }
+
+ public synchronized int getQueueMessageCount()
+ {
+ return _messages.size();
+ }
+
+ public synchronized List<AMQMessage> getMessages()
+ {
+ return new ArrayList<AMQMessage>(_messages);
+ }
+
+ public synchronized void removeAMessageFromTop() throws AMQException
+ {
+ AMQMessage msg = poll();
+ if (msg != null)
+ {
+ msg.dequeue(_queue);
+ }
+ }
+
+ public synchronized void clearAllMessages() throws AMQException
+ {
+ AMQMessage msg = poll();
+ while (msg != null)
+ {
+ msg.dequeue(_queue);
+ msg = poll();
+ }
+ }
+
+ /**
+ * Only one thread should ever execute this method concurrently, but
+ * it can do so while other threads invoke deliver().
+ */
+ private void processQueue()
+ {
+ try
+ {
+ boolean hasSubscribers = _subscriptions.hasActiveSubscribers();
+ while (hasQueuedMessages() && hasSubscribers)
+ {
+ Subscription next = _subscriptions.nextSubscriber(peek());
+ //We don't synchronize access to subscribers so need to re-check
+ if (next != null)
+ {
+ try
+ {
+ next.send(poll(), _queue);
+ }
+ catch (AMQException e)
+ {
+ _log.error("Unable to deliver message: " + e, e);
+ }
+ }
+ else
+ {
+ hasSubscribers = false;
+ }
+ }
+ }
+ finally
+ {
+ _processing.set(false);
+ }
+ }
+
+ private synchronized AMQMessage peek()
+ {
+ return _messages.peek();
+ }
+
+ private synchronized AMQMessage poll()
+ {
+ return _messages.poll();
+ }
+
+ /**
+ * Requests that the delivery manager start processing the queue asynchronously
+ * if there is work that can be done (i.e. there are messages queued up and
+ * subscribers that can receive them.
+ * <p/>
+ * This should be called when subscribers are added, but only after the consume-ok
+ * message has been returned as message delivery may start immediately. It should also
+ * be called after unsuspending a client.
+ * <p/>
+ *
+ * @param executor the executor on which the delivery should take place
+ */
+ public void processAsync(Executor executor)
+ {
+ if (hasQueuedMessages() && _subscriptions.hasActiveSubscribers())
+ {
+ //are we already running? if so, don't re-run
+ if (_processing.compareAndSet(false, true))
+ {
+ executor.execute(new Runner());
+ }
+ }
+ }
+
+ /**
+ * Handles message delivery. The delivery manager is always in one of two modes;
+ * it is either queueing messages for asynchronous delivery or delivering
+ * directly.
+ *
+ * @param name the name of the entity on whose behalf we are delivering the message
+ * @param msg the message to deliver
+ * @throws NoConsumersException if there are no active subscribers to deliver
+ * the message to
+ */
+ public void deliver(String name, AMQMessage msg) throws FailedDequeueException
+ {
+ // first check whether we are queueing, and enqueue if we are
+ if (!enqueue(msg))
+ {
+ synchronized(this)
+ {
+ // not queueing so deliver message to 'next' subscriber
+ Subscription s = _subscriptions.nextSubscriber(msg);
+ if (s == null)
+ {
+ // no subscribers yet so enter 'queueing' mode and queue this message
+ startQueueing(msg);
+ }
+ else
+ {
+ s.send(msg, _queue);
+ msg.setDeliveredToConsumer();
+ }
+ }
+ }
+
+ }
+
+ private class Runner implements Runnable
+ {
+ public void run()
+ {
+ processQueue();
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java b/java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java
new file mode 100644
index 0000000000..6c71571807
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java
@@ -0,0 +1,26 @@
+/*
+ *
+ * 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.server.queue;
+
+public interface WeightedSubscriptionManager extends SubscriptionManager
+{
+ public int getWeight();
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java
new file mode 100644
index 0000000000..48331843e5
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java
@@ -0,0 +1,200 @@
+/*
+ *
+ * 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.server.registry;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.log4j.Logger;
+import org.apache.qpid.server.configuration.Configurator;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * An abstract application registry that provides access to configuration information and handles the
+ * construction and caching of configurable objects.
+ * <p/>
+ * Subclasses should handle the construction of the "registered objects" such as the exchange registry.
+ */
+public abstract class ApplicationRegistry implements IApplicationRegistry
+{
+ private static final Logger _logger = Logger.getLogger(ApplicationRegistry.class);
+
+ private static Map _instanceMap = new HashMap();
+
+ private final Map<Class<?>, Object> _configuredObjects = new HashMap<Class<?>, Object>();
+
+ protected final Configuration _configuration;
+
+ public static final int DEFAULT_INSTANCE = 1;
+ public static final String DEFAULT_APPLICATION_REGISTRY = "org.apache.qpid.server.util.NullApplicationRegistry";
+ public static String _APPLICATION_REGISTRY = DEFAULT_APPLICATION_REGISTRY;
+
+ static
+ {
+ Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownService()));
+ }
+
+ private static class ShutdownService implements Runnable
+ {
+ public void run()
+ {
+ _logger.info("Shutting down application registries...");
+ try
+ {
+ synchronized (ApplicationRegistry.class)
+ {
+ Iterator keyIterator = _instanceMap.keySet().iterator();
+
+ while (keyIterator.hasNext())
+ {
+ int key = (Integer) keyIterator.next();
+ IApplicationRegistry instance = (IApplicationRegistry) _instanceMap.get(key);
+
+ if ((instance != null))
+ {
+ if (instance.getMessageStore() != null)
+ {
+ instance.getMessageStore().close();
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.error("Error shutting down message store: " + e, e);
+ }
+ }
+ }
+
+ public static void initialise(IApplicationRegistry instance) throws Exception
+ {
+ initialise(instance, DEFAULT_INSTANCE);
+ }
+
+ public static void initialise(IApplicationRegistry instance, int instanceID) throws Exception
+ {
+ if (instance != null)
+ {
+ _logger.info("Initialising Application Registry:" + instanceID);
+ _instanceMap.put(instanceID, instance);
+
+ try
+ {
+ instance.initialise();
+ }
+ catch (Exception e)
+ {
+ _instanceMap.remove(instanceID);
+ throw e;
+ }
+ }
+ else
+ {
+ remove(instanceID);
+ }
+ }
+
+ public static void remove(int instanceID)
+ {
+ try
+ {
+ ((IApplicationRegistry) _instanceMap.get(instanceID)).getMessageStore().close();
+ }
+ catch (Exception e)
+ {
+
+ }
+ finally
+ {
+ _instanceMap.remove(instanceID);
+ }
+ }
+
+
+ protected ApplicationRegistry(Configuration configuration)
+ {
+ _configuration = configuration;
+ }
+
+ public static IApplicationRegistry getInstance()
+ {
+ return getInstance(DEFAULT_INSTANCE);
+ }
+
+ public static IApplicationRegistry getInstance(int instanceID)
+ {
+ IApplicationRegistry instance = (IApplicationRegistry) _instanceMap.get(instanceID);
+
+ if (instance == null)
+ {
+ try
+ {
+ _logger.info("Creating DEFAULT_APPLICATION_REGISTRY: " + _APPLICATION_REGISTRY + " : Instance:" + instanceID);
+ IApplicationRegistry registry = (IApplicationRegistry) Class.forName(_APPLICATION_REGISTRY).getConstructor((Class[]) null).newInstance((Object[]) null);
+ ApplicationRegistry.initialise(registry, instanceID);
+ _logger.info("Initialised Application Registry:" + instanceID);
+ return registry;
+ }
+ catch (Exception e)
+ {
+ _logger.error("Error configuring application: " + e, e);
+ //throw new AMQBrokerCreationException(instanceID, "Unable to create Application Registry instance " + instanceID);
+ throw new RuntimeException("Unable to create Application Registry");
+ }
+ }
+ else
+ {
+ return instance;
+ }
+ }
+
+ public Configuration getConfiguration()
+ {
+ return _configuration;
+ }
+
+ public <T> T getConfiguredObject(Class<T> instanceType)
+ {
+ T instance = (T) _configuredObjects.get(instanceType);
+ if (instance == null)
+ {
+ try
+ {
+ instance = instanceType.newInstance();
+ }
+ catch (Exception e)
+ {
+ _logger.error("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor");
+ throw new IllegalArgumentException("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor");
+ }
+ Configurator.configure(instance);
+ _configuredObjects.put(instanceType, instance);
+ }
+ return instance;
+ }
+
+ public static void setDefaultApplicationRegistry(String clazz)
+ {
+ _APPLICATION_REGISTRY = clazz;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java
new file mode 100644
index 0000000000..1eb490d6fb
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java
@@ -0,0 +1,158 @@
+/*
+ *
+ * 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.server.registry;
+
+import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.SystemConfiguration;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.qpid.server.exchange.DefaultExchangeFactory;
+import org.apache.qpid.server.exchange.DefaultExchangeRegistry;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.management.JMXManagedObjectRegistry;
+import org.apache.qpid.server.management.ManagedObjectRegistry;
+import org.apache.qpid.server.management.ManagementConfiguration;
+import org.apache.qpid.server.management.NoopManagedObjectRegistry;
+import org.apache.qpid.server.queue.DefaultQueueRegistry;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.security.auth.AuthenticationManager;
+import org.apache.qpid.server.security.auth.SASLAuthenticationManager;
+import org.apache.qpid.server.store.MessageStore;
+
+import java.io.File;
+
+public class ConfigurationFileApplicationRegistry extends ApplicationRegistry
+{
+ private QueueRegistry _queueRegistry;
+
+ private ExchangeRegistry _exchangeRegistry;
+
+ private ExchangeFactory _exchangeFactory;
+
+ private ManagedObjectRegistry _managedObjectRegistry;
+
+ private AuthenticationManager _authenticationManager;
+
+ private MessageStore _messageStore;
+
+ public ConfigurationFileApplicationRegistry(File configurationURL) throws ConfigurationException
+ {
+ super(config(configurationURL));
+ }
+
+ // Our configuration class needs to make the interpolate method
+ // public so it can be called below from the config method.
+ private static class MyConfiguration extends CompositeConfiguration {
+ public String interpolate(String obj) {
+ return super.interpolate(obj);
+ }
+ }
+
+ private static final Configuration config(File url) throws ConfigurationException {
+ // We have to override the interpolate methods so that
+ // interpolation takes place accross the entirety of the
+ // composite configuration. Without doing this each
+ // configuration object only interpolates variables defined
+ // inside itself.
+ final MyConfiguration conf = new MyConfiguration();
+ conf.addConfiguration(new SystemConfiguration() {
+ protected String interpolate(String o) {
+ return conf.interpolate(o);
+ }
+ });
+ conf.addConfiguration(new XMLConfiguration(url) {
+ protected String interpolate(String o) {
+ return conf.interpolate(o);
+ }
+ });
+ return conf;
+ }
+
+ public void initialise() throws Exception
+ {
+ initialiseManagedObjectRegistry();
+ _queueRegistry = new DefaultQueueRegistry();
+ _exchangeFactory = new DefaultExchangeFactory();
+ _exchangeRegistry = new DefaultExchangeRegistry(_exchangeFactory);
+ _authenticationManager = new SASLAuthenticationManager();
+ initialiseMessageStore();
+ }
+
+ private void initialiseManagedObjectRegistry()
+ {
+ ManagementConfiguration config = getConfiguredObject(ManagementConfiguration.class);
+ if (config.enabled)
+ {
+ _managedObjectRegistry = new JMXManagedObjectRegistry();
+ }
+ else
+ {
+ _managedObjectRegistry = new NoopManagedObjectRegistry();
+ }
+ }
+
+ private void initialiseMessageStore() throws Exception
+ {
+ String messageStoreClass = _configuration.getString("store.class");
+ Class clazz = Class.forName(messageStoreClass);
+ Object o = clazz.newInstance();
+
+ if (!(o instanceof MessageStore))
+ {
+ throw new Exception("Message store class must implement " + MessageStore.class + ". Class " + clazz +
+ " does not.");
+ }
+ _messageStore = (MessageStore) o;
+ _messageStore.configure(getQueueRegistry(), "store", _configuration);
+ }
+
+ public QueueRegistry getQueueRegistry()
+ {
+ return _queueRegistry;
+ }
+
+ public ExchangeRegistry getExchangeRegistry()
+ {
+ return _exchangeRegistry;
+ }
+
+ public ExchangeFactory getExchangeFactory()
+ {
+ return _exchangeFactory;
+ }
+
+ public ManagedObjectRegistry getManagedObjectRegistry()
+ {
+ return _managedObjectRegistry;
+ }
+
+ public AuthenticationManager getAuthenticationManager()
+ {
+ return _authenticationManager;
+ }
+
+ public MessageStore getMessageStore()
+ {
+ return _messageStore;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java
new file mode 100644
index 0000000000..cd664f9a4b
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java
@@ -0,0 +1,68 @@
+/*
+ *
+ * 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.server.registry;
+
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.management.ManagedObjectRegistry;
+import org.apache.qpid.server.security.auth.AuthenticationManager;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.commons.configuration.Configuration;
+
+public interface IApplicationRegistry
+{
+ /**
+ * Initialise the application registry. All initialisation must be done in this method so that any components
+ * that need access to the application registry itself for initialisation are able to use it. Attempting to
+ * initialise in the constructor will lead to failures since the registry reference will not have been set.
+ */
+ void initialise() throws Exception;
+
+ /**
+ * This gets access to a "configured object". A configured object has fields populated from a the configuration
+ * object (Commons Configuration) automatically, where it has the appropriate attributes defined on fields.
+ * Application registry implementations can choose the refresh strategy or caching approach.
+ * @param instanceType the type of object you want initialised. This must be unique - i.e. you can only
+ * have a single object of this type in the system.
+ * @return the configured object
+ */
+ <T> T getConfiguredObject(Class<T> instanceType);
+
+ /**
+ * Get the low level configuration. For use cases where the configured object approach is not required
+ * you can get the complete configuration information.
+ * @return a Commons Configuration instance
+ */
+ Configuration getConfiguration();
+
+ QueueRegistry getQueueRegistry();
+
+ ExchangeRegistry getExchangeRegistry();
+
+ ExchangeFactory getExchangeFactory();
+
+ ManagedObjectRegistry getManagedObjectRegistry();
+
+ AuthenticationManager getAuthenticationManager();
+
+ MessageStore getMessageStore();
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationManager.java
new file mode 100644
index 0000000000..9f4addd7ee
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationManager.java
@@ -0,0 +1,33 @@
+/*
+ *
+ * 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.server.security.auth;
+
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+public interface AuthenticationManager
+{
+ String getMechanisms();
+
+ SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException;
+
+ AuthenticationResult authenticate(SaslServer server, byte[] response);
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java
new file mode 100644
index 0000000000..b26ba9b3dd
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * 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.server.security.auth;
+
+import org.apache.commons.configuration.Configuration;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.sasl.SaslServerFactory;
+import java.util.Map;
+
+public interface AuthenticationProviderInitialiser
+{
+ /**
+ * @return the mechanism's name. This will be used in the list of mechanism's advertised to the
+ * client.
+ */
+ String getMechanismName();
+
+ /**
+ * Initialise the authentication provider.
+ * @param baseConfigPath the path in the config file that points to any config options for this provider. Each
+ * provider can have its own set of configuration options
+ * @param configuration the Apache Commons Configuration instance used to configure this provider
+ * @param principalDatabases the set of principal databases that are available
+ */
+ void initialise(String baseConfigPath, Configuration configuration,
+ Map<String, PrincipalDatabase> principalDatabases) throws Exception;
+
+ /**
+ * @return the callback handler that should be used to process authentication requests for this mechanism. This will
+ * be called after initialise and will be stored by the authentication manager. The callback handler <b>must</b> be
+ * fully threadsafe.
+ */
+ CallbackHandler getCallbackHandler();
+
+ /**
+ * Get the properties that must be passed in to the Sasl.createSaslServer method.
+ * @return the properties, which may be null
+ */
+ Map<String, ?> getProperties();
+
+ /**
+ * Get the class that is the server factory. This is used for the JCA registration.
+ * @return null if no JCA registration is required, otherwise return the class
+ * that will be used in JCA registration
+ */
+ Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration();
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java
new file mode 100644
index 0000000000..0e3aea4de0
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * 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.server.security.auth;
+
+public class AuthenticationResult
+{
+ public enum AuthenticationStatus
+ {
+ SUCCESS, CONTINUE, ERROR
+ }
+
+ public AuthenticationStatus status;
+ public byte[] challenge;
+
+ public AuthenticationResult(byte[] challenge, AuthenticationStatus status)
+ {
+ this.status = status;
+ this.challenge = challenge;
+ }
+
+ public AuthenticationResult(AuthenticationStatus status)
+ {
+ this.status = status;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java
new file mode 100644
index 0000000000..4e428bbf23
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * 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.server.security.auth;
+
+import javax.security.sasl.SaslServerFactory;
+
+public class CRAMMD5Initialiser extends UsernamePasswordInitialiser
+{
+ public String getMechanismName()
+ {
+ return "CRAM-MD5";
+ }
+
+ public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration()
+ {
+ // since the CRAM-MD5 provider is registered as part of the JDK, we do not
+ // return the factory class here since we do not need to register it ourselves.
+ return null;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/JCAProvider.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/JCAProvider.java
new file mode 100644
index 0000000000..f69e5dc708
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/JCAProvider.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * 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.server.security.auth;
+
+import javax.security.sasl.SaslServerFactory;
+import java.security.Provider;
+import java.security.Security;
+import java.util.Map;
+
+public final class JCAProvider extends Provider
+{
+ public JCAProvider(Map<String, Class<? extends SaslServerFactory>> providerMap)
+ {
+ super("AMQSASLProvider", 1.0, "A JCA provider that registers all " +
+ "AMQ SASL providers that want to be registered");
+ register(providerMap);
+ Security.addProvider(this);
+ }
+
+ private void register(Map<String, Class<? extends SaslServerFactory>> providerMap)
+ {
+ for (Map.Entry<String, Class<? extends SaslServerFactory>> me :
+ providerMap.entrySet())
+ {
+ put("SaslServerFactory." + me.getKey(), me.getValue().getName());
+ }
+ }
+} \ No newline at end of file
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/NullAuthenticationManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/NullAuthenticationManager.java
new file mode 100644
index 0000000000..14cce86715
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/NullAuthenticationManager.java
@@ -0,0 +1,85 @@
+/*
+ *
+ * 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.server.security.auth;
+
+import org.apache.qpid.server.security.auth.AuthenticationManager;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+public class NullAuthenticationManager implements AuthenticationManager
+{
+ public String getMechanisms()
+ {
+ return "PLAIN";
+ }
+
+ public SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException
+ {
+ return new SaslServer()
+ {
+ public String getMechanismName()
+ {
+ return "PLAIN";
+ }
+
+ public byte[] evaluateResponse(byte[] response) throws SaslException
+ {
+ return new byte[0];
+ }
+
+ public boolean isComplete()
+ {
+ return true;
+ }
+
+ public String getAuthorizationID()
+ {
+ return "guest";
+ }
+
+ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+ {
+ return new byte[0];
+ }
+
+ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+ {
+ return new byte[0];
+ }
+
+ public Object getNegotiatedProperty(String propName)
+ {
+ return null;
+ }
+
+ public void dispose() throws SaslException
+ {
+ }
+ };
+ }
+
+ public AuthenticationResult authenticate(SaslServer server, byte[] response)
+ {
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.SUCCESS);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java
new file mode 100644
index 0000000000..fb2ac612b6
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java
@@ -0,0 +1,133 @@
+/*
+ *
+ * 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.server.security.auth;
+
+import org.apache.log4j.Logger;
+
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.AccountNotFoundException;
+import java.security.Principal;
+import java.io.*;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a user database where the account information is stored in a simple flat file.
+ *
+ * The file is expected to be in the form:
+ * username:password
+ * username1:password1
+ * ...
+ * usernamen:passwordn
+ *
+ * where a carriage return separates each username/password pair. Passwords are assumed to be in
+ * plain text.
+ *
+ */
+public class PasswordFilePrincipalDatabase implements PrincipalDatabase
+{
+ private static final Logger _logger = Logger.getLogger(PasswordFilePrincipalDatabase.class);
+
+ private File _passwordFile;
+
+ private Pattern _regexp = Pattern.compile(":");
+
+ public PasswordFilePrincipalDatabase()
+ {
+ }
+
+ public void setPasswordFile(String passwordFile) throws FileNotFoundException
+ {
+ File f = new File(passwordFile);
+ _logger.info("PasswordFilePrincipalDatabase using file " + f.getAbsolutePath());
+ _passwordFile = f;
+ if (!f.exists())
+ {
+ throw new FileNotFoundException("Cannot find password file " + f);
+ }
+ if (!f.canRead())
+ {
+ throw new FileNotFoundException("Cannot read password file " + f +
+ ". Check permissions.");
+ }
+ }
+
+ public void setPassword(Principal principal, PasswordCallback callback) throws IOException,
+ AccountNotFoundException
+ {
+ if (_passwordFile == null)
+ {
+ throw new AccountNotFoundException("Unable to locate principal since no password file was specified during initialisation");
+ }
+ if (principal == null)
+ {
+ throw new IllegalArgumentException("principal must not be null");
+ }
+ char[] pwd = lookupPassword(principal.getName());
+ if (pwd != null)
+ {
+ callback.setPassword(pwd);
+ }
+ else
+ {
+ throw new AccountNotFoundException("No account found for principal " + principal);
+ }
+ }
+
+ /**
+ * Looks up the password for a specified user in the password file.
+ * Note this code is <b>not</b> secure since it creates strings of passwords. It should be modified
+ * to create only char arrays which get nulled out.
+ * @param name
+ * @return
+ * @throws IOException
+ */
+ private char[] lookupPassword(String name) throws IOException
+ {
+ BufferedReader reader = null;
+ try
+ {
+ reader = new BufferedReader(new FileReader(_passwordFile));
+ String line;
+
+ while ((line = reader.readLine()) != null)
+ {
+ String[] result = _regexp.split(line);
+ if (result == null || result.length < 2)
+ {
+ continue;
+ }
+
+ if (name.equals(result[0]))
+ {
+ return result[1].toCharArray();
+ }
+ }
+ return null;
+ }
+ finally
+ {
+ if (reader != null)
+ {
+ reader.close();
+ }
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/PrincipalDatabase.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/PrincipalDatabase.java
new file mode 100644
index 0000000000..d7fe21735f
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/PrincipalDatabase.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * 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.server.security.auth;
+
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.AccountNotFoundException;
+import java.security.Principal;
+import java.io.IOException;
+
+/**
+ * Represents a "user database" which is really a way of storing principals (i.e. usernames) and
+ * passwords.
+ */
+public interface PrincipalDatabase
+{
+ /**
+ * Set the password for a given principal in the specified callback. This is used for certain
+ * SASL providers. The user database implementation should look up the password in any way it
+ * chooses and set it in the callback by calling its setPassword method.
+ * @param principal the principal
+ * @param callback the password callback that wants to receive the password
+ * @throws AccountNotFoundException if the account for specified principal could not be found
+ * @throws IOException if there was an error looking up the principal
+ */
+ void setPassword(Principal principal, PasswordCallback callback)
+ throws IOException, AccountNotFoundException;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java
new file mode 100644
index 0000000000..21eb80c69d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java
@@ -0,0 +1,227 @@
+/*
+ *
+ * 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.server.security.auth;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.log4j.Logger;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.configuration.PropertyUtils;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+import javax.security.sasl.SaslServerFactory;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.security.Security;
+
+public class SASLAuthenticationManager implements AuthenticationManager
+{
+ private static final Logger _log = Logger.getLogger(SASLAuthenticationManager.class);
+
+ /**
+ * The list of mechanisms, in the order in which they are configured (i.e. preferred order)
+ */
+ private String _mechanisms;
+
+ /**
+ * Maps from the mechanism to the callback handler to use for handling those requests
+ */
+ private Map<String, CallbackHandler> _callbackHandlerMap = new HashMap<String, CallbackHandler>();
+
+ /**
+ * Maps from the mechanism to the properties used to initialise the server. See the method
+ * Sasl.createSaslServer for details of the use of these properties. This map is populated during initialisation
+ * of each provider.
+ */
+ private Map<String, Map<String, ?>> _serverCreationProperties = new HashMap<String, Map<String, ?>>();
+
+ public SASLAuthenticationManager() throws Exception
+ {
+ _log.info("Initialising SASL authentication manager");
+ Map<String, PrincipalDatabase> databases = initialisePrincipalDatabases();
+ initialiseAuthenticationMechanisms(databases);
+ }
+
+ private Map<String, PrincipalDatabase> initialisePrincipalDatabases() throws Exception
+ {
+ Configuration config = ApplicationRegistry.getInstance().getConfiguration();
+ List<String> databaseNames = config.getList("security.principal-databases.principal-database.name");
+ List<String> databaseClasses = config.getList("security.principal-databases.principal-database.class");
+ Map<String, PrincipalDatabase> databases = new HashMap<String, PrincipalDatabase>();
+ for (int i = 0; i < databaseNames.size(); i++)
+ {
+ Object o;
+ try
+ {
+ o = Class.forName(databaseClasses.get(i)).newInstance();
+ }
+ catch (Exception e)
+ {
+ throw new Exception("Error initialising principal database: " + e, e);
+ }
+
+ if (!(o instanceof PrincipalDatabase))
+ {
+ throw new Exception("Principal databases must implement the PrincipalDatabase interface");
+ }
+
+ initialisePrincipalDatabase((PrincipalDatabase) o, config, i);
+
+ String name = databaseNames.get(i);
+ if (name == null || name.length() == 0)
+ {
+ throw new Exception("Principal database names must have length greater than or equal to one character");
+ }
+ PrincipalDatabase pd = databases.get(name);
+ if (pd != null)
+ {
+ throw new Exception("Duplicate principal database name provided");
+ }
+ _log.info("Initialised principal database " + name + " successfully");
+ databases.put(name, (PrincipalDatabase) o);
+ }
+ return databases;
+ }
+
+ private void initialisePrincipalDatabase(PrincipalDatabase principalDatabase, Configuration config, int index)
+ throws Exception
+ {
+ String baseName = "security.principal-databases.principal-database(" + index + ").attributes.attribute.";
+ List<String> argumentNames = config.getList(baseName + "name");
+ List<String> argumentValues = config.getList(baseName + "value");
+ for (int i = 0; i < argumentNames.size(); i++)
+ {
+ String argName = argumentNames.get(i);
+ if (argName == null || argName.length() == 0)
+ {
+ throw new Exception("Argument names must have length >= 1 character");
+ }
+ if (Character.isLowerCase(argName.charAt(0)))
+ {
+ argName = Character.toUpperCase(argName.charAt(0)) + argName.substring(1);
+ }
+ String methodName = "set" + argName;
+ Method method = principalDatabase.getClass().getMethod(methodName, String.class);
+ if (method == null)
+ {
+ throw new Exception("No method " + methodName + " found in class " + principalDatabase.getClass() +
+ " hence unable to configure principal database. The method must be public and " +
+ "have a single String argument with a void return type");
+ }
+ method.invoke(principalDatabase, PropertyUtils.replaceProperties(argumentValues.get(i)));
+ }
+ }
+
+ private void initialiseAuthenticationMechanisms(Map<String, PrincipalDatabase> databases) throws Exception
+ {
+ Configuration config = ApplicationRegistry.getInstance().getConfiguration();
+ List<String> mechanisms = config.getList("security.sasl.mechanisms.mechanism.initialiser.class");
+
+ // Maps from the mechanism to the properties used to initialise the server. See the method
+ // Sasl.createSaslServer for details of the use of these properties. This map is populated during initialisation
+ // of each provider.
+ Map<String, Class<? extends SaslServerFactory>> providerMap = new TreeMap<String, Class<? extends SaslServerFactory>>();
+
+ for (int i = 0; i < mechanisms.size(); i++)
+ {
+ String baseName = "security.sasl.mechanisms.mechanism(" + i + ").initialiser";
+ String clazz = config.getString(baseName + ".class");
+ initialiseAuthenticationMechanism(baseName, clazz, databases, config, providerMap);
+ }
+ if (providerMap.size() > 0)
+ {
+ Security.addProvider(new JCAProvider(providerMap));
+ }
+ }
+
+ private void initialiseAuthenticationMechanism(String baseName, String clazz,
+ Map<String, PrincipalDatabase> databases,
+ Configuration configuration,
+ Map<String, Class<? extends SaslServerFactory>> providerMap)
+ throws Exception
+ {
+ Class initialiserClazz = Class.forName(clazz);
+ Object o = initialiserClazz.newInstance();
+ if (!(o instanceof AuthenticationProviderInitialiser))
+ {
+ throw new Exception("The class " + clazz + " must be an instance of " +
+ AuthenticationProviderInitialiser.class);
+ }
+ AuthenticationProviderInitialiser initialiser = (AuthenticationProviderInitialiser) o;
+ initialiser.initialise(baseName, configuration, databases);
+ String mechanism = initialiser.getMechanismName();
+ if (_mechanisms == null)
+ {
+ _mechanisms = mechanism;
+ }
+ else
+ {
+ // simple append should be fine since the number of mechanisms is small and this is a one time initialisation
+ _mechanisms = _mechanisms + " " + mechanism;
+ }
+ _callbackHandlerMap.put(mechanism, initialiser.getCallbackHandler());
+ _serverCreationProperties.put(mechanism, initialiser.getProperties());
+ Class<? extends SaslServerFactory> factory = initialiser.getServerFactoryClassForJCARegistration();
+ if (factory != null)
+ {
+ providerMap.put(mechanism, factory);
+ }
+ _log.info("Initialised " + mechanism + " SASL provider successfully");
+ }
+
+ public String getMechanisms()
+ {
+ return _mechanisms;
+ }
+
+ public SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException
+ {
+ return Sasl.createSaslServer(mechanism, "AMQP", localFQDN, _serverCreationProperties.get(mechanism),
+ _callbackHandlerMap.get(mechanism));
+ }
+
+ public AuthenticationResult authenticate(SaslServer server, byte[] response)
+ {
+ try
+ {
+ // Process response from the client
+ byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]);
+
+ if (server.isComplete())
+ {
+ return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.SUCCESS);
+ }
+ else
+ {
+ return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE);
+ }
+ }
+ catch (SaslException e)
+ {
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR);
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java
new file mode 100644
index 0000000000..fccb881eaa
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java
@@ -0,0 +1,102 @@
+/*
+ *
+ * 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.server.security.auth;
+
+import org.apache.commons.configuration.Configuration;
+
+import javax.security.auth.callback.*;
+import javax.security.auth.login.AccountNotFoundException;
+import javax.security.sasl.AuthorizeCallback;
+import java.util.Map;
+import java.io.IOException;
+import java.security.Principal;
+
+public abstract class UsernamePasswordInitialiser implements AuthenticationProviderInitialiser
+{
+ private ServerCallbackHandler _callbackHandler;
+
+ private class ServerCallbackHandler implements CallbackHandler
+ {
+ private final PrincipalDatabase _principalDatabase;
+
+ protected ServerCallbackHandler(PrincipalDatabase database)
+ {
+ _principalDatabase = database;
+ }
+
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
+ {
+ Principal username = null;
+ for (Callback callback : callbacks)
+ {
+ if (callback instanceof NameCallback)
+ {
+ username = new UsernamePrincipal(((NameCallback)callback).getDefaultName());
+ }
+ else if (callback instanceof PasswordCallback)
+ {
+ try
+ {
+ _principalDatabase.setPassword(username, (PasswordCallback) callback);
+ }
+ catch (AccountNotFoundException e)
+ {
+ // very annoyingly the callback handler does not throw anything more appropriate than
+ // IOException
+ throw new IOException("Error looking up user " + e);
+ }
+ }
+ else if (callback instanceof AuthorizeCallback)
+ {
+ ((AuthorizeCallback)callback).setAuthorized(true);
+ }
+ else
+ {
+ throw new UnsupportedCallbackException(callback);
+ }
+ }
+ }
+ }
+
+ public void initialise(String baseConfigPath, Configuration configuration,
+ Map<String, PrincipalDatabase> principalDatabases) throws Exception
+ {
+ String principalDatabaseName = configuration.getString(baseConfigPath + ".principal-database");
+ PrincipalDatabase db = principalDatabases.get(principalDatabaseName);
+ if (db == null)
+ {
+ throw new Exception("Principal database " + principalDatabaseName + " not found. Ensure the name matches " +
+ "an entry in the configuration file");
+ }
+ _callbackHandler = new ServerCallbackHandler(db);
+ }
+
+ public CallbackHandler getCallbackHandler()
+ {
+ return _callbackHandler;
+ }
+
+ public Map<String, ?> getProperties()
+ {
+ // there are no properties required for the CRAM-MD5 implementation
+ return null;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java
new file mode 100644
index 0000000000..e068ba6fe4
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * 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.server.security.auth;
+
+import java.security.Principal;
+
+/**
+ * A principal that is just a wrapper for a simple username.
+ *
+ */
+public class UsernamePrincipal implements Principal
+{
+ private String _name;
+
+ public UsernamePrincipal(String name)
+ {
+ _name = name;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java
new file mode 100644
index 0000000000..1d5932ca31
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * 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.server.security.auth.amqplain;
+
+import org.apache.qpid.server.security.auth.UsernamePasswordInitialiser;
+
+import javax.security.sasl.SaslServerFactory;
+
+public class AmqPlainInitialiser extends UsernamePasswordInitialiser
+{
+ public String getMechanismName()
+ {
+ return "AMQPLAIN";
+ }
+
+ public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration()
+ {
+ return AmqPlainSaslServerFactory.class;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java
new file mode 100644
index 0000000000..3ad74ce180
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java
@@ -0,0 +1,123 @@
+/*
+ *
+ * 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.server.security.auth.amqplain;
+
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.AMQFrameDecodingException;
+import org.apache.mina.common.ByteBuffer;
+
+import javax.security.sasl.SaslServer;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.AuthorizeCallback;
+import javax.security.auth.callback.*;
+import java.io.IOException;
+
+public class AmqPlainSaslServer implements SaslServer
+{
+ public static final String MECHANISM = "AMQPLAIN";
+
+ private CallbackHandler _cbh;
+
+ private String _authorizationId;
+
+ private boolean _complete = false;
+
+ public AmqPlainSaslServer(CallbackHandler cbh)
+ {
+ _cbh = cbh;
+ }
+
+ public String getMechanismName()
+ {
+ return MECHANISM;
+ }
+
+ public byte[] evaluateResponse(byte[] response) throws SaslException
+ {
+ try
+ {
+ final FieldTable ft = new FieldTable(ByteBuffer.wrap(response), response.length);
+ String username = (String) ft.get("LOGIN");
+ // we do not care about the prompt but it throws if null
+ NameCallback nameCb = new NameCallback("prompt", username);
+ // we do not care about the prompt but it throws if null
+ PasswordCallback passwordCb = new PasswordCallback("prompt", false);
+ // TODO: should not get pwd as a String but as a char array...
+ String pwd = (String) ft.get("PASSWORD");
+ passwordCb.setPassword(pwd.toCharArray());
+ AuthorizeCallback authzCb = new AuthorizeCallback(username, username);
+ Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb};
+ _cbh.handle(callbacks);
+ _complete = true;
+ if (authzCb.isAuthorized())
+ {
+ _authorizationId = authzCb.getAuthenticationID();
+ return null;
+ }
+ else
+ {
+ throw new SaslException("Authentication failed");
+ }
+ }
+ catch (AMQFrameDecodingException e)
+ {
+ throw new SaslException("Unable to decode response: " + e, e);
+ }
+ catch (IOException e)
+ {
+ throw new SaslException("Error processing data: " + e, e);
+ }
+ catch (UnsupportedCallbackException e)
+ {
+ throw new SaslException("Unable to obtain data from callback handler: " + e, e);
+ }
+ }
+
+ public boolean isComplete()
+ {
+ return _complete;
+ }
+
+ public String getAuthorizationID()
+ {
+ return _authorizationId;
+ }
+
+ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+ {
+ throw new SaslException("Unsupported operation");
+ }
+
+ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+ {
+ throw new SaslException("Unsupported operation");
+ }
+
+ public Object getNegotiatedProperty(String propName)
+ {
+ return null;
+ }
+
+ public void dispose() throws SaslException
+ {
+ _cbh = null;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java
new file mode 100644
index 0000000000..befd724b33
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java
@@ -0,0 +1,59 @@
+/*
+ *
+ * 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.server.security.auth.amqplain;
+
+import javax.security.sasl.SaslServerFactory;
+import javax.security.sasl.SaslServer;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.Sasl;
+import javax.security.auth.callback.CallbackHandler;
+import java.util.Map;
+
+public class AmqPlainSaslServerFactory implements SaslServerFactory
+{
+ public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props,
+ CallbackHandler cbh) throws SaslException
+ {
+ if (AmqPlainSaslServer.MECHANISM.equals(mechanism))
+ {
+ return new AmqPlainSaslServer(cbh);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public String[] getMechanismNames(Map props)
+ {
+ if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) ||
+ props.containsKey(Sasl.POLICY_NODICTIONARY) ||
+ props.containsKey(Sasl.POLICY_NOACTIVE))
+ {
+ // returned array must be non null according to interface documentation
+ return new String[0];
+ }
+ else
+ {
+ return new String[]{AmqPlainSaslServer.MECHANISM};
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java
new file mode 100644
index 0000000000..b92e0b9209
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * 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.server.security.auth.plain;
+
+import org.apache.qpid.server.security.auth.UsernamePasswordInitialiser;
+
+import javax.security.sasl.SaslServerFactory;
+
+public class PlainInitialiser extends UsernamePasswordInitialiser
+{
+ public String getMechanismName()
+ {
+ return "PLAIN";
+ }
+
+ public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration()
+ {
+ return PlainSaslServerFactory.class;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java
new file mode 100644
index 0000000000..fdf655c2d9
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java
@@ -0,0 +1,144 @@
+/*
+ *
+ * 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.server.security.auth.plain;
+
+import javax.security.auth.callback.*;
+import javax.security.sasl.AuthorizeCallback;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+import java.io.IOException;
+
+public class PlainSaslServer implements SaslServer
+{
+ public static final String MECHANISM = "PLAIN";
+
+ private CallbackHandler _cbh;
+
+ private String _authorizationId;
+
+ private boolean _complete = false;
+
+ public PlainSaslServer(CallbackHandler cbh)
+ {
+ _cbh = cbh;
+ }
+
+ public String getMechanismName()
+ {
+ return MECHANISM;
+ }
+
+ public byte[] evaluateResponse(byte[] response) throws SaslException
+ {
+ try
+ {
+ int authzidNullPosition = findNullPosition(response, 0);
+ if (authzidNullPosition < 0)
+ {
+ throw new SaslException("Invalid PLAIN encoding, authzid null terminator not found");
+ }
+ int authcidNullPosition = findNullPosition(response, authzidNullPosition + 1);
+ if (authcidNullPosition < 0)
+ {
+ throw new SaslException("Invalid PLAIN encoding, authcid null terminator not found");
+ }
+
+ // we do not currently support authcid in any meaningful way
+ String authcid = new String(response, 0, authzidNullPosition, "utf8");
+ String authzid = new String(response, authzidNullPosition + 1, authcidNullPosition - 1, "utf8");
+
+ // we do not care about the prompt but it throws if null
+ NameCallback nameCb = new NameCallback("prompt", authzid);
+ // we do not care about the prompt but it throws if null
+ PasswordCallback passwordCb = new PasswordCallback("prompt", false);
+ // TODO: should not get pwd as a String but as a char array...
+ int passwordLen = response.length - authcidNullPosition - 1;
+ String pwd = new String(response, authcidNullPosition + 1, passwordLen, "utf8");
+ passwordCb.setPassword(pwd.toCharArray());
+ AuthorizeCallback authzCb = new AuthorizeCallback(authzid, authzid);
+ Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb};
+ _cbh.handle(callbacks);
+ _complete = true;
+ if (authzCb.isAuthorized())
+ {
+ _authorizationId = authzCb.getAuthenticationID();
+ return null;
+ }
+ else
+ {
+ throw new SaslException("Authentication failed");
+ }
+ }
+ catch (IOException e)
+ {
+ throw new SaslException("Error processing data: " + e, e);
+ }
+ catch (UnsupportedCallbackException e)
+ {
+ throw new SaslException("Unable to obtain data from callback handler: " + e, e);
+ }
+ }
+
+ private int findNullPosition(byte[] response, int startPosition)
+ {
+ int position = startPosition;
+ while (position < response.length)
+ {
+ if (response[position] == (byte) 0)
+ {
+ return position;
+ }
+ position++;
+ }
+ return -1;
+ }
+
+ public boolean isComplete()
+ {
+ return _complete;
+ }
+
+ public String getAuthorizationID()
+ {
+ return _authorizationId;
+ }
+
+ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+ {
+ throw new SaslException("Unsupported operation");
+ }
+
+ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+ {
+ throw new SaslException("Unsupported operation");
+ }
+
+ public Object getNegotiatedProperty(String propName)
+ {
+ return null;
+ }
+
+ public void dispose() throws SaslException
+ {
+ _cbh = null;
+ }
+
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java
new file mode 100644
index 0000000000..444f7d9b58
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java
@@ -0,0 +1,59 @@
+/*
+ *
+ * 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.server.security.auth.plain;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+import javax.security.sasl.SaslServerFactory;
+import java.util.Map;
+
+public class PlainSaslServerFactory implements SaslServerFactory
+{
+ public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props,
+ CallbackHandler cbh) throws SaslException
+ {
+ if (PlainSaslServer.MECHANISM.equals(mechanism))
+ {
+ return new PlainSaslServer(cbh);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public String[] getMechanismNames(Map props)
+ {
+ if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) ||
+ props.containsKey(Sasl.POLICY_NODICTIONARY) ||
+ props.containsKey(Sasl.POLICY_NOACTIVE))
+ {
+ // returned array must be non null according to interface documentation
+ return new String[0];
+ }
+ else
+ {
+ return new String[]{PlainSaslServer.MECHANISM};
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java b/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java
new file mode 100644
index 0000000000..f427cc7206
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java
@@ -0,0 +1,36 @@
+/*
+ *
+ * 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.server.state;
+
+/**
+ * States used in the AMQ protocol. Used by the finite state machine to determine
+ * valid responses.
+ */
+public enum AMQState
+{
+ CONNECTION_NOT_STARTED,
+ CONNECTION_NOT_AUTH,
+ CONNECTION_NOT_TUNED,
+ CONNECTION_NOT_OPENED,
+ CONNECTION_OPEN,
+ CONNECTION_CLOSING,
+ CONNECTION_CLOSED
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java b/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java
new file mode 100644
index 0000000000..5e88ff7f2d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java
@@ -0,0 +1,222 @@
+/*
+ *
+ * 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.server.state;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.handler.*;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQMethodListener;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.log4j.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * The state manager is responsible for managing the state of the protocol session.
+ * <p/>
+ * For each AMQProtocolHandler there is a separate state manager.
+ *
+ */
+public class AMQStateManager implements AMQMethodListener
+{
+ private static final Logger _logger = Logger.getLogger(AMQStateManager.class);
+
+ /**
+ * The current state
+ */
+ private AMQState _currentState;
+
+ /**
+ * Maps from an AMQState instance to a Map from Class to StateTransitionHandler.
+ * The class must be a subclass of AMQFrame.
+ */
+ private final Map<AMQState, Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>> _state2HandlersMap =
+ new HashMap<AMQState, Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>>();
+
+ private CopyOnWriteArraySet<StateListener> _stateListeners = new CopyOnWriteArraySet<StateListener>();
+
+ public AMQStateManager()
+ {
+ this(AMQState.CONNECTION_NOT_STARTED, true);
+ }
+
+ protected AMQStateManager(AMQState initial, boolean register)
+ {
+ _currentState = initial;
+ if (register)
+ {
+ registerListeners();
+ }
+ }
+
+ protected void registerListeners()
+ {
+ Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>> frame2handlerMap =
+ new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>();
+
+ // we need to register a map for the null (i.e. all state) handlers otherwise you get
+ // a stack overflow in the handler searching code when you present it with a frame for which
+ // no handlers are registered
+ //
+ _state2HandlersMap.put(null, frame2handlerMap);
+
+ frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>();
+ frame2handlerMap.put(ConnectionStartOkBody.class, ConnectionStartOkMethodHandler.getInstance());
+ _state2HandlersMap.put(AMQState.CONNECTION_NOT_STARTED, frame2handlerMap);
+
+ frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>();
+ frame2handlerMap.put(ConnectionSecureOkBody.class, ConnectionSecureOkMethodHandler.getInstance());
+ _state2HandlersMap.put(AMQState.CONNECTION_NOT_AUTH, frame2handlerMap);
+
+ frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>();
+ frame2handlerMap.put(ConnectionTuneOkBody.class, ConnectionTuneOkMethodHandler.getInstance());
+ _state2HandlersMap.put(AMQState.CONNECTION_NOT_TUNED, frame2handlerMap);
+
+ frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>();
+ frame2handlerMap.put(ConnectionOpenBody.class, ConnectionOpenMethodHandler.getInstance());
+ _state2HandlersMap.put(AMQState.CONNECTION_NOT_OPENED, frame2handlerMap);
+
+ //
+ // ConnectionOpen handlers
+ //
+ frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>();
+ frame2handlerMap.put(ChannelOpenBody.class, ChannelOpenHandler.getInstance());
+ frame2handlerMap.put(ChannelCloseBody.class, ChannelCloseHandler.getInstance());
+ frame2handlerMap.put(ChannelCloseOkBody.class, ChannelCloseOkHandler.getInstance());
+ frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance());
+ frame2handlerMap.put(ExchangeDeclareBody.class, ExchangeDeclareHandler.getInstance());
+ frame2handlerMap.put(ExchangeDeleteBody.class, ExchangeDeleteHandler.getInstance());
+ frame2handlerMap.put(BasicAckBody.class, BasicAckMethodHandler.getInstance());
+ frame2handlerMap.put(BasicRecoverBody.class, BasicRecoverMethodHandler.getInstance());
+ frame2handlerMap.put(BasicConsumeBody.class, BasicConsumeMethodHandler.getInstance());
+ frame2handlerMap.put(BasicCancelBody.class, BasicCancelMethodHandler.getInstance());
+ frame2handlerMap.put(BasicPublishBody.class, BasicPublishMethodHandler.getInstance());
+ frame2handlerMap.put(BasicQosBody.class, BasicQosHandler.getInstance());
+ frame2handlerMap.put(QueueBindBody.class, QueueBindHandler.getInstance());
+ frame2handlerMap.put(QueueDeclareBody.class, QueueDeclareHandler.getInstance());
+ frame2handlerMap.put(QueueDeleteBody.class, QueueDeleteHandler.getInstance());
+ frame2handlerMap.put(ChannelFlowBody.class, ChannelFlowHandler.getInstance());
+ frame2handlerMap.put(TxSelectBody.class, TxSelectHandler.getInstance());
+ frame2handlerMap.put(TxCommitBody.class, TxCommitHandler.getInstance());
+ frame2handlerMap.put(TxRollbackBody.class, TxRollbackHandler.getInstance());
+
+ _state2HandlersMap.put(AMQState.CONNECTION_OPEN, frame2handlerMap);
+
+ frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>();
+ frame2handlerMap.put(ConnectionCloseOkBody.class, ConnectionCloseOkMethodHandler.getInstance());
+ _state2HandlersMap.put(AMQState.CONNECTION_CLOSING, frame2handlerMap);
+
+ }
+
+ public AMQState getCurrentState()
+ {
+ return _currentState;
+ }
+
+ public void changeState(AMQState newState) throws AMQException
+ {
+ _logger.debug("State changing to " + newState + " from old state " + _currentState);
+ final AMQState oldState = _currentState;
+ _currentState = newState;
+
+ for (StateListener l : _stateListeners)
+ {
+ l.stateChanged(oldState, newState);
+ }
+ }
+
+ public void error(AMQException e)
+ {
+ _logger.error("State manager received error notification: " + e, e);
+ for (StateListener l : _stateListeners)
+ {
+ l.error(e);
+ }
+ }
+
+ public <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt,
+ AMQProtocolSession protocolSession,
+ QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry) throws AMQException
+ {
+ StateAwareMethodListener<B> handler = findStateTransitionHandler(_currentState, evt.getMethod());
+ if (handler != null)
+ {
+ handler.methodReceived(this, queueRegistry, exchangeRegistry, protocolSession, evt);
+ return true;
+ }
+ return false;
+ }
+
+ protected <B extends AMQMethodBody> StateAwareMethodListener<B> findStateTransitionHandler(AMQState currentState,
+ B frame)
+ throws IllegalStateTransitionException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Looking for state transition handler for frame " + frame.getClass());
+ }
+ final Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>
+ classToHandlerMap = _state2HandlersMap.get(currentState);
+
+ if (classToHandlerMap == null)
+ {
+ // if no specialised per state handler is registered look for a
+ // handler registered for "all" states
+ return findStateTransitionHandler(null, frame);
+ }
+ final StateAwareMethodListener<B> handler = (StateAwareMethodListener<B>) classToHandlerMap.get(frame.getClass());
+ if (handler == null)
+ {
+ if (currentState == null)
+ {
+ _logger.debug("No state transition handler defined for receiving frame " + frame);
+ return null;
+ }
+ else
+ {
+ // if no specialised per state handler is registered look for a
+ // handler registered for "all" states
+ return findStateTransitionHandler(null, frame);
+ }
+ }
+ else
+ {
+ return handler;
+ }
+ }
+
+ public void addStateListener(StateListener listener)
+ {
+ _logger.debug("Adding state listener");
+ _stateListeners.add(listener);
+ }
+
+ public void removeStateListener(StateListener listener)
+ {
+ _stateListeners.remove(listener);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java b/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java
new file mode 100644
index 0000000000..2d7cc27a85
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java
@@ -0,0 +1,48 @@
+/*
+ *
+ * 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.server.state;
+
+import org.apache.qpid.AMQException;
+
+public class IllegalStateTransitionException extends AMQException
+{
+ private AMQState _originalState;
+
+ private Class _frame;
+
+ public IllegalStateTransitionException(AMQState originalState, Class frame)
+ {
+ super("No valid state transition defined for receiving frame " + frame +
+ " from state " + originalState);
+ _originalState = originalState;
+ _frame = frame;
+ }
+
+ public AMQState getOriginalState()
+ {
+ return _originalState;
+ }
+
+ public Class getFrameClass()
+ {
+ return _frame;
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java b/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java
new file mode 100644
index 0000000000..7d58f242e5
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java
@@ -0,0 +1,40 @@
+/*
+ *
+ * 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.server.state;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.framing.AMQMethodBody;
+
+/**
+ * A frame listener that is informed of the protocol state when invoked and has
+ * the opportunity to update state.
+ *
+ */
+public interface StateAwareMethodListener <B extends AMQMethodBody>
+{
+ void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
+ ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
+ AMQMethodEvent<B> evt) throws AMQException;
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java b/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java
new file mode 100644
index 0000000000..00fc09867b
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * 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.server.state;
+
+import org.apache.qpid.AMQException;
+
+public interface StateListener
+{
+ void stateChanged(AMQState oldState, AMQState newState) throws AMQException;
+
+ void error(Throwable t);
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java b/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java
new file mode 100644
index 0000000000..328aed81d9
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java
@@ -0,0 +1,145 @@
+/*
+ *
+ * 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.server.store;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueRegistry;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A simple message store that stores the messages in a threadsafe structure in memory.
+ */
+public class MemoryMessageStore implements MessageStore
+{
+ private static final Logger _log = Logger.getLogger(MemoryMessageStore.class);
+
+ private static final int DEFAULT_HASHTABLE_CAPACITY = 50000;
+
+ private static final String HASHTABLE_CAPACITY_CONFIG = "hashtable-capacity";
+
+ protected ConcurrentMap<Long, AMQMessage> _messageMap;
+
+ private final AtomicLong _messageId = new AtomicLong(1);
+
+ public void configure()
+ {
+ _log.info("Using capacity " + DEFAULT_HASHTABLE_CAPACITY + " for hash table");
+ _messageMap = new ConcurrentHashMap<Long, AMQMessage>(DEFAULT_HASHTABLE_CAPACITY);
+ }
+
+ public void configure(String base, Configuration config)
+ {
+ int hashtableCapacity = config.getInt(base + "." + HASHTABLE_CAPACITY_CONFIG, DEFAULT_HASHTABLE_CAPACITY);
+ _log.info("Using capacity " + hashtableCapacity + " for hash table");
+ _messageMap = new ConcurrentHashMap<Long, AMQMessage>(hashtableCapacity);
+ }
+
+ public void configure(QueueRegistry queueRegistry, String base, Configuration config) throws Exception
+ {
+ configure(base, config);
+ }
+
+ public void close() throws Exception
+ {
+ if (_messageMap != null)
+ {
+ _messageMap.clear();
+ _messageMap = null;
+ }
+ }
+
+ public void put(AMQMessage msg)
+ {
+ _messageMap.put(msg.getMessageId(), msg);
+ }
+
+ public void removeMessage(long messageId)
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Removing message with id " + messageId);
+ }
+ _messageMap.remove(messageId);
+ }
+
+ public void createQueue(AMQQueue queue) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void removeQueue(String name) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void enqueueMessage(String name, long messageId) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void dequeueMessage(String name, long messageId) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void beginTran() throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void commitTran() throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void abortTran() throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean inTran()
+ {
+ return false;
+ }
+
+ public List<AMQQueue> createQueues() throws AMQException
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getNewMessageId()
+ {
+ return _messageId.getAndIncrement();
+ }
+
+ public AMQMessage getMessage(long messageId)
+ {
+ return _messageMap.get(messageId);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java b/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java
new file mode 100644
index 0000000000..8ee1d09862
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java
@@ -0,0 +1,83 @@
+/*
+ *
+ * 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.server.store;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueRegistry;
+
+import java.util.List;
+
+public interface MessageStore
+{
+ /**
+ * Called after instantiation in order to configure the message store. A particular implementation can define
+ * whatever parameters it wants.
+ * @param queueRegistry the registry of queues to be used by this store
+ * @param base the base element identifier from which all configuration items are relative. For example, if the base
+ * element is "store", the all elements used by concrete classes will be "store.foo" etc.
+ * @param config the apache commons configuration object
+ */
+ void configure(QueueRegistry queueRegistry, String base, Configuration config) throws Exception;
+
+ /**
+ * Called to close and cleanup any resources used by the message store.
+ * @throws Exception
+ */
+ void close() throws Exception;
+
+ void put(AMQMessage msg) throws AMQException;
+
+ void removeMessage(long messageId) throws AMQException;
+
+ void createQueue(AMQQueue queue) throws AMQException;
+
+ void removeQueue(String name) throws AMQException;
+
+ void enqueueMessage(String name, long messageId) throws AMQException;
+
+ void dequeueMessage(String name, long messageId) throws AMQException;
+
+ void beginTran() throws AMQException;
+
+ void commitTran() throws AMQException;
+
+ void abortTran() throws AMQException;
+
+ boolean inTran();
+
+ /**
+ * Recreate all queues that were persisted, including re-enqueuing of existing messages
+ * @return
+ * @throws AMQException
+ */
+ List<AMQQueue> createQueues() throws AMQException;
+
+ /**
+ * Return a valid, currently unused message id.
+ * @return a message id
+ */
+ long getNewMessageId();
+}
+
+
diff --git a/java/broker/src/main/java/org/apache/qpid/server/transport/ConnectorConfiguration.java b/java/broker/src/main/java/org/apache/qpid/server/transport/ConnectorConfiguration.java
new file mode 100644
index 0000000000..ca008a0bd5
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/transport/ConnectorConfiguration.java
@@ -0,0 +1,86 @@
+/*
+ *
+ * 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.server.transport;
+
+import org.apache.qpid.configuration.Configured;
+import org.apache.mina.common.IoAcceptor;
+import org.apache.mina.filter.executor.ExecutorExecutor;
+import org.apache.mina.util.NewThreadExecutor;
+
+public class ConnectorConfiguration
+{
+ public static final String DEFAULT_PORT = "5672";
+
+ public static final String SSL_PORT = "8672";
+
+ @Configured(path = "connector.processors",
+ defaultValue = "4")
+ public int processors;
+
+ @Configured(path = "connector.port",
+ defaultValue = DEFAULT_PORT)
+ public int port;
+
+ @Configured(path = "connector.bind",
+ defaultValue = "wildcard")
+ public String bindAddress;
+
+ @Configured(path = "connector.sslport",
+ defaultValue = SSL_PORT)
+ public int sslPort;
+
+ @Configured(path = "connector.socketReceiveBuffer",
+ defaultValue = "32767")
+ public int socketReceiveBufferSize;
+
+ @Configured(path = "connector.socketWriteBuffer",
+ defaultValue = "32767")
+ public int socketWriteBuferSize;
+
+ @Configured(path = "connector.tcpNoDelay",
+ defaultValue = "true")
+ public boolean tcpNoDelay;
+
+ @Configured(path = "advanced.filterchain[@enableExecutorPool]",
+ defaultValue = "false")
+ public boolean enableExecutorPool;
+
+ @Configured(path = "advanced.enablePooledAllocator",
+ defaultValue = "false")
+ public boolean enablePooledAllocator;
+
+ @Configured(path = "advanced.enableDirectBuffers",
+ defaultValue = "false")
+ public boolean enableDirectBuffers;
+
+ @Configured(path = "connector.ssl",
+ defaultValue = "false")
+ public boolean enableSSL;
+
+ @Configured(path = "connector.nonssl",
+ defaultValue = "true")
+ public boolean enableNonSSL;
+
+ public IoAcceptor createAcceptor()
+ {
+ return new org.apache.mina.transport.socket.nio.SocketAcceptor(processors, new NewThreadExecutor());
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java b/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java
new file mode 100644
index 0000000000..2ee75c001f
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java
@@ -0,0 +1,695 @@
+/*
+ *
+ * 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.server.transport;
+
+import org.apache.mina.common.*;
+import org.apache.mina.util.*;
+import org.apache.mina.util.Queue;
+import org.apache.mina.util.Stack;
+
+import java.util.*;
+
+/**
+ * A Thread-pooling filter. This filter forwards {@link IoHandler} events
+ * to its thread pool.
+ * <p/>
+ * This is an implementation of
+ * <a href="http://deuce.doc.wustl.edu/doc/pspdfs/lf.pdf">Leader/Followers
+ * thread pool</a> by Douglas C. Schmidt et al.
+ */
+public class ThreadPoolFilter extends IoFilterAdapter
+{
+ /**
+ * Default maximum size of thread pool (2G).
+ */
+ public static final int DEFAULT_MAXIMUM_POOL_SIZE = Integer.MAX_VALUE;
+
+ /**
+ * Default keep-alive time of thread pool (1 min).
+ */
+ public static final int DEFAULT_KEEP_ALIVE_TIME = 60 * 1000;
+
+ /**
+ * A queue which contains {@link Integer}s which represents reusable
+ * thread IDs. {@link Worker} first checks this queue and then
+ * uses {@link #threadId} when no reusable thread ID is available.
+ */
+ private static final Queue threadIdReuseQueue = new Queue();
+ private static int threadId = 0;
+
+ private static int acquireThreadId()
+ {
+ synchronized (threadIdReuseQueue)
+ {
+ Integer id = (Integer) threadIdReuseQueue.pop();
+ if (id == null)
+ {
+ return ++ threadId;
+ }
+ else
+ {
+ return id.intValue();
+ }
+ }
+ }
+
+ private static void releaseThreadId(int id)
+ {
+ synchronized (threadIdReuseQueue)
+ {
+ threadIdReuseQueue.push(new Integer(id));
+ }
+ }
+
+ private final String threadNamePrefix;
+ private final Map buffers = new IdentityHashMap();
+ private final BlockingQueue unfetchedSessionBuffers = new BlockingQueue();
+ private final Set allSessionBuffers = new IdentityHashSet();
+
+ private Worker leader;
+ private final Stack followers = new Stack();
+ private final Set allWorkers = new IdentityHashSet();
+
+ private int maximumPoolSize = DEFAULT_MAXIMUM_POOL_SIZE;
+ private int keepAliveTime = DEFAULT_KEEP_ALIVE_TIME;
+
+ private boolean shuttingDown;
+
+ private int poolSize;
+ private final Object poolSizeLock = new Object();
+
+ /**
+ * Creates a new instance of this filter with default thread pool settings.
+ */
+ public ThreadPoolFilter()
+ {
+ this("IoThreadPool");
+ }
+
+ /**
+ * Creates a new instance of this filter with the specified thread name prefix
+ * and other default settings.
+ *
+ * @param threadNamePrefix the prefix of the thread names this pool will create.
+ */
+ public ThreadPoolFilter(String threadNamePrefix)
+ {
+ if (threadNamePrefix == null)
+ {
+ throw new NullPointerException("threadNamePrefix");
+ }
+ threadNamePrefix = threadNamePrefix.trim();
+ if (threadNamePrefix.length() == 0)
+ {
+ throw new IllegalArgumentException("threadNamePrefix is empty.");
+ }
+ this.threadNamePrefix = threadNamePrefix;
+ }
+
+ public String getThreadNamePrefix()
+ {
+ return threadNamePrefix;
+ }
+
+ public int getPoolSize()
+ {
+ synchronized (poolSizeLock)
+ {
+ return poolSize;
+ }
+ }
+
+ public int getMaximumPoolSize()
+ {
+ return maximumPoolSize;
+ }
+
+ public int getKeepAliveTime()
+ {
+ return keepAliveTime;
+ }
+
+ public void setMaximumPoolSize(int maximumPoolSize)
+ {
+ if (maximumPoolSize <= 0)
+ {
+ throw new IllegalArgumentException();
+ }
+ this.maximumPoolSize = maximumPoolSize;
+ }
+
+ public void setKeepAliveTime(int keepAliveTime)
+ {
+ this.keepAliveTime = keepAliveTime;
+ }
+
+ public void init()
+ {
+ shuttingDown = false;
+ leader = new Worker();
+ leader.start();
+ leader.lead();
+ }
+
+ public void destroy()
+ {
+ shuttingDown = true;
+ int expectedPoolSize = 0;
+ while (getPoolSize() != expectedPoolSize)
+ {
+ List allWorkers;
+ synchronized (poolSizeLock)
+ {
+ allWorkers = new ArrayList(this.allWorkers);
+ }
+
+ // You may not interrupt the current thread.
+ if (allWorkers.remove(Thread.currentThread()))
+ {
+ expectedPoolSize = 1;
+ }
+
+ for (Iterator i = allWorkers.iterator(); i.hasNext();)
+ {
+ Worker worker = (Worker) i.next();
+ while (worker.isAlive())
+ {
+ worker.interrupt();
+ try
+ {
+ // This timeout will help us from
+ // infinite lock-up and interrupt workers again.
+ worker.join(100);
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+ }
+ }
+
+ this.allSessionBuffers.clear();
+ this.unfetchedSessionBuffers.clear();
+ this.buffers.clear();
+ this.followers.clear();
+ this.leader = null;
+ }
+
+ private void increasePoolSize(Worker worker)
+ {
+ synchronized (poolSizeLock)
+ {
+ poolSize++;
+ allWorkers.add(worker);
+ }
+ }
+
+ private void decreasePoolSize(Worker worker)
+ {
+ synchronized (poolSizeLock)
+ {
+ poolSize--;
+ allWorkers.remove(worker);
+ }
+ }
+
+ private void fireEvent(NextFilter nextFilter, IoSession session,
+ EventType type, Object data)
+ {
+ final BlockingQueue unfetchedSessionBuffers = this.unfetchedSessionBuffers;
+ final Set allSessionBuffers = this.allSessionBuffers;
+ final Event event = new Event(type, nextFilter, data);
+
+ synchronized (unfetchedSessionBuffers)
+ {
+ final SessionBuffer buf = getSessionBuffer(session);
+ final Queue eventQueue = buf.eventQueue;
+
+ synchronized (buf)
+ {
+ eventQueue.push(event);
+ }
+
+ if (!allSessionBuffers.contains(buf))
+ {
+ allSessionBuffers.add(buf);
+ unfetchedSessionBuffers.push(buf);
+ }
+ }
+ }
+
+ /**
+ * Implement this method to fetch (or pop) a {@link SessionBuffer} from
+ * the given <tt>unfetchedSessionBuffers</tt>. The default implementation
+ * simply pops the buffer from it. You could prioritize the fetch order.
+ *
+ * @return A non-null {@link SessionBuffer}
+ */
+ protected SessionBuffer fetchSessionBuffer(Queue unfetchedSessionBuffers)
+ {
+ return (SessionBuffer) unfetchedSessionBuffers.pop();
+ }
+
+ private SessionBuffer getSessionBuffer(IoSession session)
+ {
+ final Map buffers = this.buffers;
+ SessionBuffer buf = (SessionBuffer) buffers.get(session);
+ if (buf == null)
+ {
+ synchronized (buffers)
+ {
+ buf = (SessionBuffer) buffers.get(session);
+ if (buf == null)
+ {
+ buf = new SessionBuffer(session);
+ buffers.put(session, buf);
+ }
+ }
+ }
+ return buf;
+ }
+
+ private void removeSessionBuffer(SessionBuffer buf)
+ {
+ final Map buffers = this.buffers;
+ final IoSession session = buf.session;
+ synchronized (buffers)
+ {
+ buffers.remove(session);
+ }
+ }
+
+ protected static class SessionBuffer
+ {
+ private final IoSession session;
+
+ private final Queue eventQueue = new Queue();
+
+ private SessionBuffer(IoSession session)
+ {
+ this.session = session;
+ }
+
+ public IoSession getSession()
+ {
+ return session;
+ }
+
+ public Queue getEventQueue()
+ {
+ return eventQueue;
+ }
+ }
+
+ private class Worker extends Thread
+ {
+ private final int id;
+ private final Object promotionLock = new Object();
+ private boolean dead;
+
+ private Worker()
+ {
+ int id = acquireThreadId();
+ this.id = id;
+ this.setName(threadNamePrefix + '-' + id);
+ increasePoolSize(this);
+ }
+
+ public boolean lead()
+ {
+ final Object promotionLock = this.promotionLock;
+ synchronized (promotionLock)
+ {
+ if (dead)
+ {
+ return false;
+ }
+
+ leader = this;
+ promotionLock.notify();
+ }
+
+ return true;
+ }
+
+ public void run()
+ {
+ for (; ;)
+ {
+ if (!waitForPromotion())
+ {
+ break;
+ }
+
+ SessionBuffer buf = fetchBuffer();
+ giveUpLead();
+ if (buf == null)
+ {
+ break;
+ }
+
+ processEvents(buf);
+ follow();
+ releaseBuffer(buf);
+ }
+
+ decreasePoolSize(this);
+ releaseThreadId(id);
+ }
+
+ private SessionBuffer fetchBuffer()
+ {
+ BlockingQueue unfetchedSessionBuffers = ThreadPoolFilter.this.unfetchedSessionBuffers;
+ synchronized (unfetchedSessionBuffers)
+ {
+ while (!shuttingDown)
+ {
+ try
+ {
+ unfetchedSessionBuffers.waitForNewItem();
+ }
+ catch (InterruptedException e)
+ {
+ continue;
+ }
+
+ return ThreadPoolFilter.this.fetchSessionBuffer(unfetchedSessionBuffers);
+ }
+ }
+
+ return null;
+ }
+
+ private void processEvents(SessionBuffer buf)
+ {
+ final IoSession session = buf.session;
+ final Queue eventQueue = buf.eventQueue;
+ for (; ;)
+ {
+ Event event;
+ synchronized (buf)
+ {
+ event = (Event) eventQueue.pop();
+ if (event == null)
+ {
+ break;
+ }
+ }
+ processEvent(event.getNextFilter(), session,
+ event.getType(), event.getData());
+ }
+ }
+
+ private void follow()
+ {
+ final Object promotionLock = this.promotionLock;
+ final Stack followers = ThreadPoolFilter.this.followers;
+ synchronized (promotionLock)
+ {
+ if (this != leader)
+ {
+ synchronized (followers)
+ {
+ followers.push(this);
+ }
+ }
+ }
+ }
+
+ private void releaseBuffer(SessionBuffer buf)
+ {
+ final BlockingQueue unfetchedSessionBuffers = ThreadPoolFilter.this.unfetchedSessionBuffers;
+ final Set allSessionBuffers = ThreadPoolFilter.this.allSessionBuffers;
+ final Queue eventQueue = buf.eventQueue;
+
+ synchronized (unfetchedSessionBuffers)
+ {
+ if (eventQueue.isEmpty())
+ {
+ allSessionBuffers.remove(buf);
+ removeSessionBuffer(buf);
+ }
+ else
+ {
+ unfetchedSessionBuffers.push(buf);
+ }
+ }
+ }
+
+ private boolean waitForPromotion()
+ {
+ final Object promotionLock = this.promotionLock;
+
+ long startTime = System.currentTimeMillis();
+ long currentTime = System.currentTimeMillis();
+
+ synchronized (promotionLock)
+ {
+ while (this != leader && !shuttingDown)
+ {
+ // Calculate remaining keep-alive time
+ int keepAliveTime = getKeepAliveTime();
+ if (keepAliveTime > 0)
+ {
+ keepAliveTime -= (currentTime - startTime);
+ }
+ else
+ {
+ keepAliveTime = Integer.MAX_VALUE;
+ }
+
+ // Break the loop if there's no remaining keep-alive time.
+ if (keepAliveTime <= 0)
+ {
+ break;
+ }
+
+ // Wait for promotion
+ try
+ {
+ promotionLock.wait(keepAliveTime);
+ }
+ catch (InterruptedException e)
+ {
+ }
+
+ // Update currentTime for the next iteration
+ currentTime = System.currentTimeMillis();
+ }
+
+ boolean timeToLead = this == leader && !shuttingDown;
+
+ if (!timeToLead)
+ {
+ // time to die
+ synchronized (followers)
+ {
+ followers.remove(this);
+ }
+
+ // Mark as dead explicitly when we've got promotionLock.
+ dead = true;
+ }
+
+ return timeToLead;
+ }
+ }
+
+ private void giveUpLead()
+ {
+ final Stack followers = ThreadPoolFilter.this.followers;
+ Worker worker;
+ do
+ {
+ synchronized (followers)
+ {
+ worker = (Worker) followers.pop();
+ }
+
+ if (worker == null)
+ {
+ // Increase the number of threads if we
+ // are not shutting down and we can increase the number.
+ if (!shuttingDown
+ && getPoolSize() < getMaximumPoolSize())
+ {
+ worker = new Worker();
+ worker.lead();
+ worker.start();
+ }
+
+ // This loop should end because:
+ // 1) lead() is called already,
+ // 2) or it is shutting down and there's no more threads left.
+ break;
+ }
+ }
+ while (!worker.lead());
+ }
+ }
+
+ protected static class EventType
+ {
+ public static final EventType OPENED = new EventType("OPENED");
+
+ public static final EventType CLOSED = new EventType("CLOSED");
+
+ public static final EventType READ = new EventType("READ");
+
+ public static final EventType WRITTEN = new EventType("WRITTEN");
+
+ public static final EventType RECEIVED = new EventType("RECEIVED");
+
+ public static final EventType SENT = new EventType("SENT");
+
+ public static final EventType IDLE = new EventType("IDLE");
+
+ public static final EventType EXCEPTION = new EventType("EXCEPTION");
+
+ private final String value;
+
+ private EventType(String value)
+ {
+ this.value = value;
+ }
+
+ public String toString()
+ {
+ return value;
+ }
+ }
+
+ protected static class Event
+ {
+ private final EventType type;
+ private final NextFilter nextFilter;
+ private final Object data;
+
+ public Event(EventType type, NextFilter nextFilter, Object data)
+ {
+ this.type = type;
+ this.nextFilter = nextFilter;
+ this.data = data;
+ }
+
+ public Object getData()
+ {
+ return data;
+ }
+
+
+ public NextFilter getNextFilter()
+ {
+ return nextFilter;
+ }
+
+
+ public EventType getType()
+ {
+ return type;
+ }
+ }
+
+ public void sessionCreated(NextFilter nextFilter, IoSession session)
+ {
+ nextFilter.sessionCreated(session);
+ }
+
+ public void sessionOpened(NextFilter nextFilter,
+ IoSession session)
+ {
+ fireEvent(nextFilter, session, EventType.OPENED, null);
+ }
+
+ public void sessionClosed(NextFilter nextFilter,
+ IoSession session)
+ {
+ fireEvent(nextFilter, session, EventType.CLOSED, null);
+ }
+
+ public void sessionIdle(NextFilter nextFilter,
+ IoSession session, IdleStatus status)
+ {
+ fireEvent(nextFilter, session, EventType.IDLE, status);
+ }
+
+ public void exceptionCaught(NextFilter nextFilter,
+ IoSession session, Throwable cause)
+ {
+ fireEvent(nextFilter, session, EventType.EXCEPTION, cause);
+ }
+
+ public void messageReceived(NextFilter nextFilter,
+ IoSession session, Object message)
+ {
+ ByteBufferUtil.acquireIfPossible(message);
+ fireEvent(nextFilter, session, EventType.RECEIVED, message);
+ }
+
+ public void messageSent(NextFilter nextFilter,
+ IoSession session, Object message)
+ {
+ ByteBufferUtil.acquireIfPossible(message);
+ fireEvent(nextFilter, session, EventType.SENT, message);
+ }
+
+ protected void processEvent(NextFilter nextFilter,
+ IoSession session, EventType type,
+ Object data)
+ {
+ if (type == EventType.RECEIVED)
+ {
+ nextFilter.messageReceived(session, data);
+ ByteBufferUtil.releaseIfPossible(data);
+ }
+ else if (type == EventType.SENT)
+ {
+ nextFilter.messageSent(session, data);
+ ByteBufferUtil.releaseIfPossible(data);
+ }
+ else if (type == EventType.EXCEPTION)
+ {
+ nextFilter.exceptionCaught(session, (Throwable) data);
+ }
+ else if (type == EventType.IDLE)
+ {
+ nextFilter.sessionIdle(session, (IdleStatus) data);
+ }
+ else if (type == EventType.OPENED)
+ {
+ nextFilter.sessionOpened(session);
+ }
+ else if (type == EventType.CLOSED)
+ {
+ nextFilter.sessionClosed(session);
+ }
+ }
+
+ public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest)
+ {
+ nextFilter.filterWrite(session, writeRequest);
+ }
+
+ public void filterClose(NextFilter nextFilter, IoSession session) throws Exception
+ {
+ nextFilter.filterClose(session);
+ }
+} \ No newline at end of file
diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java b/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java
new file mode 100644
index 0000000000..402065d5e1
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java
@@ -0,0 +1,123 @@
+/*
+ *
+ * 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.server.txn;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.MessageStore;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holds a list of TxnOp instance representing transactional
+ * operations.
+ */
+public class TxnBuffer
+{
+ private boolean _containsPersistentChanges = false;
+ private final MessageStore _store;
+ private final List<TxnOp> _ops = new ArrayList<TxnOp>();
+ private static final Logger _log = Logger.getLogger(TxnBuffer.class);
+
+ public TxnBuffer(MessageStore store)
+ {
+ _store = store;
+ }
+
+ public void containsPersistentChanges()
+ {
+ _containsPersistentChanges = true;
+ }
+
+ public void commit() throws AMQException
+ {
+ if (_containsPersistentChanges)
+ {
+ _log.debug("Begin Transaction.");
+ _store.beginTran();
+ if(prepare())
+ {
+ _log.debug("Transaction Succeeded");
+ _store.commitTran();
+ for (TxnOp op : _ops)
+ {
+ op.commit();
+ }
+ }
+ else
+ {
+ _log.debug("Transaction Failed");
+ _store.abortTran();
+ }
+ }else{
+ if(prepare())
+ {
+ for (TxnOp op : _ops)
+ {
+ op.commit();
+ }
+ }
+ }
+ _ops.clear();
+ }
+
+ private boolean prepare()
+ {
+ for (int i = 0; i < _ops.size(); i++)
+ {
+ TxnOp op = _ops.get(i);
+ try
+ {
+ op.prepare();
+ }
+ catch(Exception e)
+ {
+ //compensate previously prepared ops
+ for(int j = 0; j < i; j++)
+ {
+ _ops.get(j).undoPrepare();
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void rollback() throws AMQException
+ {
+ for (TxnOp op : _ops)
+ {
+ op.rollback();
+ }
+ _ops.clear();
+ }
+
+ public void enlist(TxnOp op)
+ {
+ _ops.add(op);
+ }
+
+ public void cancel(TxnOp op)
+ {
+ _ops.remove(op);
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java b/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java
new file mode 100644
index 0000000000..e863bab73e
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java
@@ -0,0 +1,54 @@
+/*
+ *
+ * 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.server.txn;
+
+import org.apache.qpid.AMQException;
+
+/**
+ * This provides the abstraction of an individual operation within a
+ * transaction. It is used by the TxnBuffer class.
+ */
+public interface TxnOp
+{
+ /**
+ * Do the part of the operation that updates persistent state
+ */
+ public void prepare() throws AMQException;
+ /**
+ * Complete the operation started by prepare. Can now update in
+ * memory state or make netork transfers.
+ */
+ public void commit();
+ /**
+ * This is not the same as rollback. Unfortunately the use of an
+ * in memory reference count as a locking mechanism and a test for
+ * whether a message should be deleted means that as things are,
+ * handling an acknowledgement unavoidably alters both memory and
+ * persistent state on prepare. This is needed to 'compensate' or
+ * undo the in-memory change if the peristent update of later ops
+ * fails.
+ */
+ public void undoPrepare();
+ /**
+ * Rolls back the operation.
+ */
+ public void rollback();
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java b/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java
new file mode 100644
index 0000000000..4767844abe
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java
@@ -0,0 +1,126 @@
+/*
+ *
+ * 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.server.util;
+
+import java.util.Iterator;
+
+public class CircularBuffer implements Iterable
+{
+ private final Object[] _log;
+ private int _size;
+ private int _index;
+
+ public CircularBuffer(int size)
+ {
+ _log = new Object[size];
+ }
+
+ public void add(Object o)
+ {
+ _log[_index++] = o;
+ _size = Math.min(_size+1, _log.length);
+ if(_index >= _log.length)
+ {
+ _index = 0;
+ }
+ }
+
+ public Object get(int i)
+ {
+ if(i >= _log.length)
+ {
+ throw new ArrayIndexOutOfBoundsException(i);
+ }
+ return _log[index(i)];
+ }
+
+ public int size() {
+ return _size;
+ }
+
+ public Iterator iterator()
+ {
+ return new Iterator()
+ {
+ private int i = 0;
+
+ public boolean hasNext()
+ {
+ return i < _size;
+ }
+
+ public Object next()
+ {
+ return get(i++);
+ }
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ public String toString()
+ {
+ StringBuilder s = new StringBuilder();
+ boolean first = true;
+ for(Object o : this)
+ {
+ if(!first)
+ {
+ s.append(", ");
+ }
+ else
+ {
+ first = false;
+ }
+ s.append(o);
+ }
+ return s.toString();
+ }
+
+ public void dump()
+ {
+ for(Object o : this)
+ {
+ System.out.println(o);
+ }
+ }
+
+ int index(int i)
+ {
+ return _size == _log.length ? (_index + i) % _log.length : i;
+ }
+
+ public static void main(String[] artgv)
+ {
+ String[] items = new String[]{
+ "A","B","C","D","E","F","G","H","I","J","K"
+ };
+ CircularBuffer buffer = new CircularBuffer(5);
+ for(String s : items)
+ {
+ buffer.add(s);
+ System.out.println(buffer);
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/util/ConcurrentLinkedQueueNoSize.java b/java/broker/src/main/java/org/apache/qpid/server/util/ConcurrentLinkedQueueNoSize.java
new file mode 100644
index 0000000000..cf5e71a6e2
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/util/ConcurrentLinkedQueueNoSize.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * 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.server.util;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class ConcurrentLinkedQueueNoSize<E> extends ConcurrentLinkedQueue<E>
+{
+ public int size()
+ {
+ if (isEmpty())
+ {
+ return 0;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java b/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java
new file mode 100644
index 0000000000..eda97e0ed2
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java
@@ -0,0 +1,105 @@
+/*
+ *
+ * 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.server.util;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+
+/**
+ * Dynamic proxy that records invocations in a fixed size circular buffer,
+ * dumping details on hitting an exception.
+ * <p>
+ * Useful in debugging.
+ * <p>
+ */
+public class LoggingProxy implements InvocationHandler
+{
+ private final Object _target;
+ private final CircularBuffer _log;
+
+ public LoggingProxy(Object target, int size)
+ {
+ _target = target;
+ _log = new CircularBuffer(size);
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ try
+ {
+ entered(method, args);
+ Object result = method.invoke(_target, args);
+ returned(method, result);
+ return result;
+ }
+ catch(InvocationTargetException e)
+ {
+ dump();
+ throw e.getTargetException();
+ }
+ }
+
+ void dump()
+ {
+ _log.dump();
+ }
+
+ CircularBuffer getBuffer()
+ {
+ return _log;
+ }
+
+ private synchronized void entered(Method method, Object[] args)
+ {
+ if (args == null)
+ {
+ _log.add(Thread.currentThread() + ": " + method.getName() + "() entered");
+ }
+ else
+ {
+ _log.add(Thread.currentThread() + ": " + method.getName() + "(" + Arrays.toString(args) + ") entered");
+ }
+ }
+
+ private synchronized void returned(Method method, Object result)
+ {
+ if (method.getReturnType() == Void.TYPE)
+ {
+ _log.add(Thread.currentThread() + ": " + method.getName() + "() returned");
+ }
+ else
+ {
+ _log.add(Thread.currentThread() + ": " + method.getName() + "() returned " + result);
+ }
+ }
+
+ public Object getProxy(Class... c)
+ {
+ return Proxy.newProxyInstance(_target.getClass().getClassLoader(), c, this);
+ }
+
+ public int getBufferSize() {
+ return _log.size();
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java
new file mode 100644
index 0000000000..2e77f33363
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java
@@ -0,0 +1,109 @@
+/*
+ *
+ * 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.server.util;
+
+import org.apache.qpid.server.exchange.DefaultExchangeFactory;
+import org.apache.qpid.server.exchange.DefaultExchangeRegistry;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.management.ManagedObjectRegistry;
+import org.apache.qpid.server.management.NoopManagedObjectRegistry;
+import org.apache.qpid.server.queue.DefaultQueueRegistry;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.auth.AuthenticationManager;
+import org.apache.qpid.server.security.auth.NullAuthenticationManager;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.MemoryMessageStore;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.MapConfiguration;
+
+import java.util.HashMap;
+
+public class NullApplicationRegistry extends ApplicationRegistry
+{
+ private QueueRegistry _queueRegistry;
+
+ private ExchangeRegistry _exchangeRegistry;
+
+ private ExchangeFactory _exchangeFactory;
+
+ private ManagedObjectRegistry _managedObjectRegistry;
+
+ private AuthenticationManager _authenticationManager;
+
+ private MessageStore _messageStore;
+
+
+ public NullApplicationRegistry()
+ {
+ super(new MapConfiguration(new HashMap()));
+ }
+
+ public void initialise() throws Exception
+ {
+ _managedObjectRegistry = new NoopManagedObjectRegistry();
+ _queueRegistry = new DefaultQueueRegistry();
+ _exchangeFactory = new DefaultExchangeFactory();
+ _exchangeRegistry = new DefaultExchangeRegistry(_exchangeFactory);
+ _authenticationManager = new NullAuthenticationManager();
+ _messageStore = new MemoryMessageStore();
+ ((MemoryMessageStore)_messageStore).configure();
+
+ _configuration.addProperty("heartbeat.delay", 10 * 60); // 10 minutes
+ }
+
+ public Configuration getConfiguration()
+ {
+ return _configuration;
+ }
+
+ public QueueRegistry getQueueRegistry()
+ {
+ return _queueRegistry;
+ }
+
+ public ExchangeRegistry getExchangeRegistry()
+ {
+ return _exchangeRegistry;
+ }
+
+ public ExchangeFactory getExchangeFactory()
+ {
+ return _exchangeFactory;
+ }
+
+ public ManagedObjectRegistry getManagedObjectRegistry()
+ {
+ return _managedObjectRegistry;
+ }
+
+ public AuthenticationManager getAuthenticationManager()
+ {
+ return _authenticationManager;
+ }
+
+ public MessageStore getMessageStore()
+ {
+ return _messageStore;
+ }
+}
+