summaryrefslogtreecommitdiff
path: root/java/broker/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/broker/src')
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java565
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java22
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/Main.java613
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java64
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java49
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java125
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessage.java48
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java62
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java211
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java102
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java217
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java136
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java63
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java93
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/DestNameExchange.java219
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java223
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java47
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java28
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java28
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java38
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java142
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java239
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java85
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java90
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java37
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java39
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java52
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java58
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java90
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java77
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java46
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java54
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java58
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java51
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java61
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java58
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java65
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java63
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java68
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java115
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java127
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java54
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java79
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java62
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java31
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java94
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java124
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java84
-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.java59
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java50
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/jms/JmsConsumer.java107
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java94
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java168
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java49
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java39
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java37
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java36
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java385
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java40
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java35
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/Managable.java31
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java95
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java55
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java39
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/ManagementConfiguration.java27
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java45
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodEvent.java62
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodListener.java52
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java681
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java227
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java50
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java122
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java38
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/HeartbeatConfig.java64
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java138
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedSession.java33
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java520
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java62
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java764
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/AsyncDeliveryConfig.java53
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java47
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java259
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java109
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java36
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java110
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java213
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java32
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.java34
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java70
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java39
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java30
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/Subscription.java29
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.java37
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java170
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.java28
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java182
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java144
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java23
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java197
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java155
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java65
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationManager.java30
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java63
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java40
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java35
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/JCAProvider.java43
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/NullAuthenticationManager.java82
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java130
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/PrincipalDatabase.java42
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java224
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java99
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java39
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java35
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java120
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java56
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java35
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java141
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java56
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java33
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java219
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java45
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java37
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java27
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java172
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java87
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/transport/ConnectorConfiguration.java83
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java692
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/txn/CleanupMessageOperation.java89
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/txn/DeliverMessageOperation.java61
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java149
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java171
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java45
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java33
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java119
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java51
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java123
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java102
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java106
-rw-r--r--java/broker/src/main/log4j.properties6
-rw-r--r--java/broker/src/main/test/build-module.xml32
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/UnitTests.java40
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/ack/TxAckTest.java187
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/ack/UnitTests.java34
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java50
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/configuration/UnitTests.java32
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTest.java232
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java200
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangePerformanceTest.java181
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java81
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/exchange/UnitTests.java32
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/protocol/MockIoSession.java293
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/protocol/TestProtocolInitiation.java212
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/protocol/UnitTests.java32
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/queue/AckTest.java268
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/queue/ConcurrencyTest.java261
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/queue/DeliveryManagerTest.java161
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/queue/MessageTestHelper.java50
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/queue/MockProtocolSession.java121
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/queue/QueueConcurrentPerfTest.java46
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/queue/QueuePerfTest.java255
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/queue/SendPerfTest.java173
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionManagerTest.java105
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionSetTest.java149
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/queue/TestSubscription.java84
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/queue/UnitTests.java38
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java120
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/store/TestReferenceCounting.java73
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java47
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/store/UnitTests.java34
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/txn/TxnBufferTest.java305
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/txn/UnitTests.java34
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/util/AveragedRun.java63
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/util/ConcurrentTest.java76
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/util/LoggingProxyTest.java89
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/util/RunStats.java54
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java104
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/util/TimedRun.java49
-rw-r--r--java/broker/src/main/test/java/org/apache/qpid/server/util/UnitTests.java32
-rw-r--r--java/broker/src/main/test/lib/README1
-rw-r--r--java/broker/src/main/test/lib/junit/junit-4.0.jarbin0 -> 105601 bytes
-rw-r--r--java/broker/src/main/test/lib/junit/junit.jarbin0 -> 121070 bytes
182 files changed, 19633 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..e3d9bc31ff
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java
@@ -0,0 +1,565 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.BasicPublishBody;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+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.MessageHandleFactory;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.txn.LocalTransactionalContext;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.server.txn.TxnBuffer;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import java.util.*;
+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 _prefetchCount;
+
+ /**
+ * 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 Map<Long, UnacknowledgedMessage> _unacknowledgedMessageMap = new LinkedHashMap<Long, UnacknowledgedMessage>(DEFAULT_PREFETCH);
+ private UnacknowledgedMessageMap _unacknowledgedMessageMap = new UnacknowledgedMessageMapImpl(DEFAULT_PREFETCH);
+
+ private final AtomicBoolean _suspended = new AtomicBoolean(false);
+
+ private final MessageRouter _exchanges;
+
+ private TransactionalContext _txnContext;
+
+ private final List<RequiredDeliveryException> _returnMessages = new LinkedList<RequiredDeliveryException>();
+
+ private MessageHandleFactory _messageHandleFactory = new MessageHandleFactory();
+
+ public AMQChannel(int channelId, MessageStore messageStore, MessageRouter exchanges)
+ throws AMQException
+ {
+ _channelId = channelId;
+ _prefetchCount = DEFAULT_PREFETCH;
+ _messageStore = messageStore;
+ _exchanges = exchanges;
+ // TODO: fix me to pass in the txn context or at least have a factory for it
+ _txnContext = new NonTransactionalContext(_messageStore, this, _returnMessages);
+ }
+
+ /**
+ * Sets this channel to be part of a local transaction
+ */
+ public void setLocalTransactional()
+ {
+ _txnContext = new LocalTransactionalContext(_messageStore, new TxnBuffer(_messageStore), _returnMessages);
+ }
+
+ public int getChannelId()
+ {
+ return _channelId;
+ }
+
+ public long getPrefetchCount()
+ {
+ return _prefetchCount;
+ }
+
+ public void setPrefetchCount(long prefetchCount)
+ {
+ _prefetchCount = prefetchCount;
+ }
+
+ public void setPublishFrame(BasicPublishBody publishBody, AMQProtocolSession publisher) throws AMQException
+ {
+ _currentMessage = new AMQMessage(_messageStore.getNewMessageId(), publishBody, _txnContext);
+ // TODO: used in clustering only I think (RG)
+ //_currentMessage.setPublisher(publisher);
+ }
+
+ public void publishContentHeader(ContentHeaderBody contentHeaderBody)
+ throws AMQException
+ {
+ if (_currentMessage == null)
+ {
+ throw new AMQException("Received content header without previously receiving a BasicPublish frame");
+ }
+ else
+ {
+ _currentMessage.setContentHeaderBody(contentHeaderBody);
+ routeCurrentMessage();
+ _currentMessage.routingComplete(_messageStore, _messageHandleFactory);
+
+ // check and deliver if header says body length is zero
+ if (contentHeaderBody.bodySize == 0)
+ {
+ _currentMessage = null;
+ }
+ }
+ }
+
+ public void publishContentBody(ContentBody contentBody)
+ throws AMQException
+ {
+ if (_currentMessage == null)
+ {
+ throw new AMQException("Received content body without previously receiving a JmsPublishBody");
+ }
+
+ // returns true iff the message was delivered (i.e. if all data was
+ // received
+ try
+ {
+ if (_currentMessage.addContentBodyFrame(contentBody))
+ {
+ _currentMessage = null;
+ }
+ }
+ catch (AMQException e)
+ {
+ // we want to make sure we don't keep a reference to the message in the
+ // event of an error
+ _currentMessage = null;
+ throw e;
+ }
+ }
+
+ protected void routeCurrentMessage() throws AMQException
+ {
+ _exchanges.routeContent(_currentMessage);
+ }
+
+ /*protected void routeCurrentMessage2() 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);
+ }
+ else
+ {
+ throw new AMQException(_log, "Consumer tag " + consumerTag + " not known to channel " +
+ _channelId);
+ }
+ }
+
+ /**
+ * 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
+ }
+ } */
+ _txnContext.rollback();
+ 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 the message that was delivered
+ * @param deliveryTag the delivery tag used when delivering the message (see protocol spec for description of
+ * the delivery tag)
+ * @param queue the queue from which the message was delivered
+ */
+ public void addUnacknowledgedMessage(AMQMessage message, long deliveryTag, String consumerTag, AMQQueue queue)
+ {
+ _unacknowledgedMessageMap.add(deliveryTag, new UnacknowledgedMessage(queue, message, consumerTag, 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.
+ * @throws org.apache.qpid.AMQException if the requeue fails
+ */
+ 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
+ Collection<UnacknowledgedMessage> messagesToBeDelivered = _unacknowledgedMessageMap.cancelAllMessages();
+
+ for (UnacknowledgedMessage unacked : messagesToBeDelivered)
+ {
+ if (unacked.queue != null)
+ {
+ _txnContext.deliver(unacked.message, unacked.queue);
+ }
+ }
+ }
+
+ /**
+ * Called to resend all outstanding unacknowledged messages to this same channel.
+ */
+ public void resend(final AMQProtocolSession session) throws AMQException
+ {
+ _unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor()
+ {
+ public boolean callback(UnacknowledgedMessage message) throws AMQException
+ {
+ long deliveryTag = message.deliveryTag;
+ String consumerTag = message.consumerTag;
+ AMQMessage msg = message.message;
+
+ msg.writeDeliver(session, _channelId, deliveryTag, consumerTag);
+ // false means continue processing
+ return false;
+ }
+
+ public void visitComplete()
+ {
+ }
+ });
+ }
+
+ /**
+ * 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 since we may get an ack for a delivery tag that was generated from the
+ * deleted queue.
+ *
+ * @param queue the queue that has been deleted
+ * @throws org.apache.qpid.AMQException if there is an error processing the unacked messages
+ */
+ public void queueDeleted(final AMQQueue queue) throws AMQException
+ {
+ _unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor()
+ {
+ public boolean callback(UnacknowledgedMessage message) throws AMQException
+ {
+ if (message.queue == queue)
+ {
+ message.queue = null;
+ try
+ {
+ message.message.decrementReference();
+ }
+ catch (AMQException e)
+ {
+ _log.error("Error decrementing ref count on message " + message.message.getMessageId() + ": " +
+ e, e);
+ }
+ }
+ return false;
+ }
+
+ public void visitComplete()
+ {
+ }
+ });
+ }
+
+ /**
+ * 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
+ {
+ _unacknowledgedMessageMap.acknowledgeMessage(deliveryTag, multiple, _txnContext);
+ checkSuspension();
+ /*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);
+ } */
+ }
+
+ /**
+ * Used only for testing purposes.
+ *
+ * @return the map of unacknowledged messages
+ */
+ public UnacknowledgedMessageMap getUnacknowledgedMessageMap()
+ {
+ return _unacknowledgedMessageMap;
+ }
+
+ private void checkSuspension()
+ {
+ boolean suspend = _unacknowledgedMessageMap.size() >= _prefetchCount;
+ setSuspended(suspend);
+ }
+
+ public void setSuspended(boolean suspended)
+ {
+ 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
+ {
+ _txnContext.commit();
+ }
+
+ public void rollback() throws AMQException
+ {
+ _txnContext.rollback();
+ }
+
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(30);
+ sb.append("Channel: id ").append(_channelId).append(", transaction context: ").append(_txnContext);
+ sb.append(", prefetch count: ").append(_prefetchCount);
+ return sb.toString();
+ }
+
+ public ObjectName getObjectName()
+ throws MalformedObjectNameException
+ {
+ StringBuilder sb = new StringBuilder(30);
+ sb.append("Channel:id=").append(_channelId);
+ return new ObjectName(sb.toString());
+ }
+
+ public void setDefaultQueue(AMQQueue queue)
+ {
+ _defaultQueue = queue;
+ }
+
+ public AMQQueue getDefaultQueue()
+ {
+ return _defaultQueue;
+ }
+
+ public void processReturns(AMQProtocolSession session) throws AMQException
+ {
+ for (RequiredDeliveryException bouncedMessage : _returnMessages)
+ {
+ AMQMessage message = bouncedMessage.getAMQMessage();
+ message.writeReturn(session, _channelId, bouncedMessage.getReplyCode(), bouncedMessage.getMessage());
+ }
+ _returnMessages.clear();
+ }
+}
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..6a7e54bc45
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java
@@ -0,0 +1,22 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..13dc5fbca6
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/Main.java
@@ -0,0 +1,613 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.SocketAcceptorConfig;
+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);
+
+ 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();
+ SocketAcceptorConfig sconfig = (SocketAcceptorConfig) acceptor.getDefaultConfig();
+ SocketSessionConfig sc = (SocketSessionConfig) sconfig.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)
+ {
+ sconfig.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.bind(bindAddress, handler, sconfig);
+ _logger.info("Qpid.AMQP listening on non-SSL address " + bindAddress);
+ }
+
+ if (connectorConfig.enableSSL)
+ {
+ AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler();
+ handler.setUseSSL(true);
+ try
+ {
+ acceptor.bind(new InetSocketAddress(connectorConfig.sslPort),
+ handler, sconfig);
+ _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..815dfdcfbd
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java
@@ -0,0 +1,64 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..d2b017275a
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+
+/**
+ * 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 AMQMessage _amqMessage;
+
+ public RequiredDeliveryException(String message, AMQMessage payload)
+ {
+ super(message);
+ _amqMessage = payload;
+ }
+
+ public AMQMessage getAMQMessage()
+ {
+ return _amqMessage;
+ }
+
+ 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..85072b6976
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java
@@ -0,0 +1,125 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.txn.TxnOp;
+
+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..f13ecef7ac
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessage.java
@@ -0,0 +1,48 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..8fece7bcc1
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.server.txn.TransactionalContext;
+import org.apache.qpid.AMQException;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface UnacknowledgedMessageMap
+{
+ public interface Visitor
+ {
+ /**
+ * @param message the message being iterated over
+ * @return true to stop iteration, false to continue
+ * @throws AMQException
+ */
+ boolean callback(UnacknowledgedMessage message) throws AMQException;
+
+ void visitComplete();
+ }
+
+ void visit(Visitor visitor) throws AMQException;
+
+ void add(long deliveryTag, UnacknowledgedMessage message);
+
+ void collect(long deliveryTag, boolean multiple, List<UnacknowledgedMessage> msgs);
+
+ boolean contains(long deliveryTag) throws AMQException;
+
+ void remove(List<UnacknowledgedMessage> msgs);
+
+ UnacknowledgedMessage remove(long deliveryTag);
+
+ void drainTo(Collection<UnacknowledgedMessage> destination, long deliveryTag) throws AMQException;
+
+ Collection<UnacknowledgedMessage> cancelAllMessages();
+
+ void acknowledgeMessage(long deliveryTag, boolean multiple, TransactionalContext txnContext) throws AMQException;
+
+ int size();
+
+ void clear();
+}
+
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..8061687175
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java
@@ -0,0 +1,211 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.server.queue.AMQMessage;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.AMQException;
+
+import java.util.*;
+
+public class UnacknowledgedMessageMapImpl implements UnacknowledgedMessageMap
+{
+ private final Object _lock = new Object();
+
+ private Map<Long, UnacknowledgedMessage> _map;
+
+ private long _lastDeliveryTag;
+
+ private final int _prefetchLimit;
+
+ public UnacknowledgedMessageMapImpl(int prefetchLimit)
+ {
+ _prefetchLimit = prefetchLimit;
+ _map = new LinkedHashMap<Long, UnacknowledgedMessage>(prefetchLimit);
+ }
+
+ /*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 boolean contains(long deliveryTag) throws AMQException
+ {
+ synchronized (_lock)
+ {
+ return _map.containsKey(deliveryTag);
+ }
+ }
+
+ public void remove(List<UnacknowledgedMessage> msgs)
+ {
+ synchronized (_lock)
+ {
+ for(UnacknowledgedMessage msg : msgs)
+ {
+ _map.remove(msg.deliveryTag);
+ }
+ }
+ }
+
+ public UnacknowledgedMessage remove(long deliveryTag)
+ {
+ synchronized (_lock)
+ {
+ return _map.remove(deliveryTag);
+ }
+ }
+
+ public void visit(Visitor visitor) throws AMQException
+ {
+ synchronized (_lock)
+ {
+ Collection<UnacknowledgedMessage> currentEntries = _map.values();
+ for (UnacknowledgedMessage msg: currentEntries)
+ {
+ visitor.callback(msg);
+ }
+ }
+ }
+
+ public void add(long deliveryTag, UnacknowledgedMessage message)
+ {
+ synchronized( _lock)
+ {
+ _map.put(deliveryTag, message);
+ _lastDeliveryTag = deliveryTag;
+ }
+ }
+
+ public Collection<UnacknowledgedMessage> cancelAllMessages()
+ {
+ synchronized (_lock)
+ {
+ Collection<UnacknowledgedMessage> currentEntries = _map.values();
+ _map = new LinkedHashMap<Long, UnacknowledgedMessage>(_prefetchLimit);
+ return currentEntries;
+ }
+ }
+
+ public void acknowledgeMessage(long deliveryTag, boolean multiple, TransactionalContext txnContext)
+ throws AMQException
+ {
+ synchronized (_lock)
+ {
+ txnContext.acknowledgeMessage(deliveryTag, _lastDeliveryTag, multiple, this);
+ }
+ }
+
+ public int size()
+ {
+ synchronized (_lock)
+ {
+ return _map.size();
+ }
+ }
+
+ public void clear()
+ {
+ synchronized (_lock)
+ {
+ _map.clear();
+ }
+ }
+
+ public void drainTo(Collection<UnacknowledgedMessage> destination, long deliveryTag) throws AMQException
+ {
+ synchronized (_lock)
+ {
+ Iterator<Map.Entry<Long, UnacknowledgedMessage>> it = _map.entrySet().iterator();
+ while (it.hasNext())
+ {
+ Map.Entry<Long, UnacknowledgedMessage> unacked = it.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:" + _map.entrySet().toString());
+ }
+
+ it.remove();
+
+ destination.add(unacked.getValue());
+ if (unacked.getKey() == deliveryTag)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ public void resendMessages(AMQProtocolSession protocolSession, int channelId) throws AMQException
+ {
+ synchronized (_lock)
+ {
+ for (Map.Entry<Long, UnacknowledgedMessage> entry : _map.entrySet())
+ {
+ long deliveryTag = entry.getKey();
+ String consumerTag = entry.getValue().consumerTag;
+ AMQMessage msg = entry.getValue().message;
+
+ msg.writeDeliver(protocolSession, channelId, deliveryTag, consumerTag);
+ }
+ }
+ }
+ 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..e02b958941
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java
@@ -0,0 +1,102 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..a7ad50917c
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java
@@ -0,0 +1,217 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..cf787b4739
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java
@@ -0,0 +1,136 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..990a868e72
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..62b0415253
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java
@@ -0,0 +1,93 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.server.protocol.ExchangeInitialiser;
+import org.apache.qpid.server.queue.AMQMessage;
+
+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 BasicPublish being received (where the exchange is validated) and the final
+ // content body being received (which triggers this method)
+ // TODO: check where the exchange is validated
+ 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..38ceebe235
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DestNameExchange.java
@@ -0,0 +1,219 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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",
+ "Queue and binding keye",
+ _bindingItemNames,
+ _bindingItemDescriptions,
+ _bindingItemTypes);
+ _bindinglistDataType = new TabularType("Bindings",
+ "List of queues and binding keys",
+ _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
+ {
+ final 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)
+ {
+ payload.enqueue(q);
+ }
+ }
+ }
+}
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..6f38013ffb
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java
@@ -0,0 +1,223 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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",
+ "Queue and binding keye",
+ _bindingItemNames,
+ _bindingItemDescriptions,
+ _bindingItemTypes);
+ _bindinglistDataType = new TabularType("Bindings",
+ "List of queues and binding keys",
+ _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
+ payload.enqueue(q);
+ }
+ }
+
+ 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..02a0badb65
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java
@@ -0,0 +1,47 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+
+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..36fd159f31
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java
@@ -0,0 +1,28 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..4a6c735bee
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java
@@ -0,0 +1,28 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..5b71cd7b0c
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..d69e956b5f
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java
@@ -0,0 +1,142 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..67a6ff6596
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java
@@ -0,0 +1,239 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.*;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.management.MBeanConstructor;
+
+import javax.management.openmbean.*;
+import javax.management.ServiceNotFoundException;
+import javax.management.NotCompliantMBeanException;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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 = {"Queue"};
+ 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 and header attributes binding",
+ _bindingItemNames,
+ _bindingItemDescriptions,
+ _bindingItemTypes);
+ _bindinglistDataType = new TabularType("HeaderBindings",
+ "List of queues and related header attribute bindings",
+ _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;
+ }
+
+ public void createBinding(String QueueName, String binding)
+ throws ServiceNotFoundException
+ {
+ throw new ServiceNotFoundException("This service is not supported by \"" + this.getName() + "\"");
+ }
+
+ } // 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 routed = 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());
+ }
+ payload.enqueue(e.queue);
+ routed = true;
+ }
+ }
+ if (!routed)
+ {
+ _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..9e88b6a68c
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java
@@ -0,0 +1,85 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..bd02910d0a
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java
@@ -0,0 +1,90 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..832a60c681
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java
@@ -0,0 +1,37 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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;
+
+/**
+ * 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..6d5511d9c8
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java
@@ -0,0 +1,39 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..5e236f7da9
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..52d6d9f0f1
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..40d49815e3
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java
@@ -0,0 +1,90 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..f2f660299d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java
@@ -0,0 +1,77 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..0d1c039207
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..1dce283c9e
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java
@@ -0,0 +1,54 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..1b03f15d22
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..7731e56d4d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java
@@ -0,0 +1,51 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..1ad917b918
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..634cd70469
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..f78d6f7276
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..f918158edb
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..7bc28f9eb9
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java
@@ -0,0 +1,68 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..1c0da4f658
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java
@@ -0,0 +1,115 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..5715ce181b
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java
@@ -0,0 +1,127 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..05ca10fec5
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java
@@ -0,0 +1,54 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..444a54a4f2
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java
@@ -0,0 +1,79 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..441e991872
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..a689366a2f
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..98eec37a4a
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java
@@ -0,0 +1,94 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..d1eb387748
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java
@@ -0,0 +1,124 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..82c1d93065
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java
@@ -0,0 +1,84 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..14399b08f0
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..ff2d79fb95
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java
@@ -0,0 +1,59 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..cf665950ca
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java
@@ -0,0 +1,50 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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()).setLocalTransactional();
+ 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..da82d2166e
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/jms/JmsConsumer.java
@@ -0,0 +1,107 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..a5733ed4ef
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java
@@ -0,0 +1,94 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..cd0d58acdb
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java
@@ -0,0 +1,168 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..0cd43cecac
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..595c97dd76
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.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;
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..2ce275d50d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java
@@ -0,0 +1,37 @@
+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;
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..ef6993036f
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java
@@ -0,0 +1,36 @@
+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;
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..dcf05fa6fe
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java
@@ -0,0 +1,385 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..30fdf81eee
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java
@@ -0,0 +1,40 @@
+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;
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..919ac767c7
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java
@@ -0,0 +1,35 @@
+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;
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..e62e1c7f87
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..a38931a0d8
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java
@@ -0,0 +1,95 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..4007337173
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java
@@ -0,0 +1,55 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..7270ec83b4
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java
@@ -0,0 +1,39 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..ec80009d17
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagementConfiguration.java
@@ -0,0 +1,27 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..3bf2c8d9ca
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..0c8d049951
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodEvent.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..e8d973cd91
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMethodListener.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..9ede6157f4
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java
@@ -0,0 +1,681 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.*;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.management.*;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+
+import javax.management.*;
+import javax.management.monitor.MonitorNotification;
+import javax.management.openmbean.*;
+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;
+
+ /* 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",
+ "ChannelName",
+ "Transactional",
+ "DefaultQueue",
+ "UnacknowledgedMessageCount"};
+ private String[] _channelAttributeDescriptions = { "Channel Identifier",
+ "Channel Name",
+ "is Channel Transactional?",
+ "Default Queue Name",
+ "Unacknowledged Message Count"};
+ private OpenType[] _channelAttributeTypes = { SimpleType.INTEGER,
+ SimpleType.OBJECTNAME,
+ SimpleType.BOOLEAN,
+ SimpleType.STRING,
+ SimpleType.INTEGER};
+ /**
+ * Channels in the list will be indexed according to channelId.
+ */
+ private String[] _indexNames = { "ChannelId" };
+
+ /**
+ * represents the data type for channel data.
+ */
+ private CompositeType _channelType = null;
+ /**
+ * Datatype for list of channelsType.
+ */
+ private TabularType _channelsType = null;
+
+ 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",
+ "a Channel",
+ _channelAtttibuteNames,
+ _channelAttributeDescriptions,
+ _channelAttributeTypes);
+
+ _channelsType = new TabularType("channelsType",
+ "List of available channelsType",
+ _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");
+ }
+ 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");
+ }
+ 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();
+ //ManagedChannel channel = (AMQChannelMBean)amqChannel.getManagedObject();
+ ObjectName channelObjectName = null;
+
+ try
+ {
+ channelObjectName = channel.getObjectName();
+ }
+ catch (MalformedObjectNameException ex)
+ {
+ _logger.error("Unable to create object name: ", ex);
+ }
+
+ Object[] itemValues = {channel.getChannelId(),
+ channelObjectName,
+ (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
+ {
+ contentFrameReceived(frame);
+ }
+ }
+ }
+
+ 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().writeDeliver().
+ *
+ * @param frame the frame to writeDeliver
+ */
+ 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..78fb76ae27
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java
@@ -0,0 +1,227 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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))
+ {
+ //writeDeliver 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..0088db08bb
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java
@@ -0,0 +1,50 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..402ebc329d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java
@@ -0,0 +1,122 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..08c31ed3ff
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..d7678185d4
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/HeartbeatConfig.java
@@ -0,0 +1,64 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..2a800f327d
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java
@@ -0,0 +1,138 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.
+ */
+ @MBeanAttribute(name="MaximumNumberOfAllowedChannels",
+ description="The maximum number of channels that can be opened using this connection")
+ Long getMaximumNumberOfAllowedChannels();
+
+ /**
+ * Sets the maximum number of channels allowed to be created using
+ * this connection.
+ * @param value
+ */
+ 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/protocol/ManagedSession.java b/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedSession.java
new file mode 100644
index 0000000000..2a1a0b62c2
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedSession.java
@@ -0,0 +1,33 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 java.util.Date;
+
+public interface ManagedSession
+{
+ static final String TYPE = "Connection";
+
+ Date getLastIoTime();
+
+ String getRemoteAddress();
+
+ long getWrittenBytes();
+
+ long getReadBytes();
+}
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..afab90dcc4
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java
@@ -0,0 +1,520 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.AMQException;
+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.TransactionalContext;
+import org.apache.log4j.Logger;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Combines the information that make up a deliverable message into a more manageable form.
+ */
+public class AMQMessage
+{
+ private static final Logger _log = Logger.getLogger(AMQMessage.class);
+
+ /**
+ * Used in clustering
+ */
+ private final Set<Object> _tokens = new HashSet<Object>();
+
+ /**
+ * Used in clustering
+ * TODO need to get rid of this
+ */
+ private AMQProtocolSession _publisher;
+
+ private final long _messageId;
+
+ private final AtomicInteger _referenceCount = new AtomicInteger(1);
+
+ private AMQMessageHandle _messageHandle;
+
+ /**
+ * Stored temporarily until the header has been received at which point it is used when
+ * constructing the handle
+ */
+ private BasicPublishBody _publishBody;
+
+ /**
+ * Also stored temporarily.
+ */
+ private ContentHeaderBody _contentHeaderBody;
+
+ /**
+ * Keeps a track of how many bytes we have received in body frames
+ */
+ private long _bodyLengthReceived = 0;
+
+ private final TransactionalContext _txnContext;
+
+ /**
+ * 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;
+
+ /**
+ * This is stored during routing, to know the queues to which this message should immediately be
+ * delivered. It is <b>cleared after delivery has been attempted</b>. Any persistent record of destinations is done
+ * by the message handle.
+ */
+ private List<AMQQueue> _destinationQueues = new LinkedList<AMQQueue>();
+
+ /**
+ * Used to iterate through all the body frames associated with this message. Will not
+ * keep all the data in memory therefore is memory-efficient.
+ */
+ private class BodyFrameIterator implements Iterator<AMQDataBlock>
+ {
+ private int _channel;
+
+ private int _index = -1;
+
+ private BodyFrameIterator(int channel)
+ {
+ _channel = channel;
+ }
+
+ public boolean hasNext()
+ {
+ return _index < _messageHandle.getBodyCount() - 1;
+ }
+
+ public AMQDataBlock next()
+ {
+ try
+ {
+ ContentBody cb = _messageHandle.getContentBody(_messageId, ++_index);
+ return ContentBody.createAMQFrame(_channel, cb);
+ }
+ catch (AMQException e)
+ {
+ // have no choice but to throw a runtime exception
+ throw new RuntimeException("Error getting content body: " + e, e);
+ }
+
+ }
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private class BodyContentIterator implements Iterator<ContentBody>
+ {
+
+ private int _index = -1;
+
+ public boolean hasNext()
+ {
+ return _index < _messageHandle.getBodyCount() - 1;
+ }
+
+ public ContentBody next()
+ {
+ try
+ {
+ return _messageHandle.getContentBody(_messageId, ++_index);
+ }
+ catch (AMQException e)
+ {
+ throw new RuntimeException("Error getting content body: " + e, e);
+ }
+ }
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ public AMQMessage(long messageId, BasicPublishBody publishBody, TransactionalContext txnContext)
+ {
+ _messageId = messageId;
+ _txnContext = txnContext;
+ _publishBody = publishBody;
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Message created with id " + messageId);
+ }
+ }
+
+ protected AMQMessage(AMQMessage msg) throws AMQException
+ {
+ _publisher = msg._publisher;
+ _messageId = msg._messageId;
+ _messageHandle = msg._messageHandle;
+ _txnContext = msg._txnContext;
+ _deliveredToConsumer = msg._deliveredToConsumer;
+ }
+
+ public Iterator<AMQDataBlock> getBodyFrameIterator(int channel)
+ {
+ return new BodyFrameIterator(channel);
+ }
+
+ public Iterator<ContentBody> getContentBodyIterator()
+ {
+ return new BodyContentIterator();
+ }
+
+ public ContentHeaderBody getContentHeaderBody() throws AMQException
+ {
+ return _messageHandle.getContentHeaderBody(_messageId);
+ }
+
+ public void setContentHeaderBody(ContentHeaderBody contentHeaderBody)
+ throws AMQException
+ {
+ _contentHeaderBody = contentHeaderBody;
+ }
+
+ public void routingComplete(MessageStore store, MessageHandleFactory factory) throws AMQException
+ {
+ final boolean persistent = isPersistent();
+ _messageHandle = factory.createMessageHandle(_messageId, store, persistent);
+ if (persistent)
+ {
+ _txnContext.beginTranIfNecessary();
+ }
+
+ // enqueuing the messages ensure that if required the destinations are recorded to a
+ // persistent store
+ for (AMQQueue q : _destinationQueues)
+ {
+ _messageHandle.enqueue(_messageId, q);
+ }
+
+ if (_contentHeaderBody.bodySize == 0)
+ {
+ deliver();
+ }
+ }
+
+ public boolean addContentBodyFrame(ContentBody contentBody) throws AMQException
+ {
+ _bodyLengthReceived += contentBody.getSize();
+ _messageHandle.addContentBodyFrame(_messageId, contentBody);
+ if (isAllContentReceived())
+ {
+ deliver();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public boolean isAllContentReceived() throws AMQException
+ {
+ return _bodyLengthReceived == _contentHeaderBody.bodySize;
+ }
+
+ public long getMessageId()
+ {
+ return _messageId;
+ }
+
+ /**
+ * Threadsafe. Increment the reference count on the message.
+ */
+ public void incrementReference()
+ {
+ _referenceCount.incrementAndGet();
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Ref count on message " + _messageId + " incremented to " + _referenceCount);
+ }
+ }
+
+ /**
+ * Threadsafe. This will decrement the reference count and when it reaches zero will remove the message from the
+ * message store.
+ *
+ * @throws MessageCleanupException when an attempt was made to remove the message from the message store and that
+ * failed
+ */
+ 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
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Ref count on message " + _messageId + " is zero; removing message");
+ }
+ _messageHandle.removeMessage(_messageId);
+ }
+ catch (AMQException e)
+ {
+ //to maintain consistency, we revert the count
+ incrementReference();
+ throw new MessageCleanupException(_messageId, e);
+ }
+ }
+ else
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Ref count is now " + _referenceCount + " for message id " + _messageId);
+ if (_referenceCount.get() < 0)
+ {
+ Thread.dumpStack();
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+
+ /**
+ * Registers a queue to which this message is to be delivered. This is
+ * called from the exchange when it is routing the message. This will be called before any content bodies have
+ * been received so that the choice of AMQMessageHandle implementation can be picked based on various criteria.
+ *
+ * @param queue the queue
+ * @throws org.apache.qpid.AMQException if there is an error enqueuing the message
+ */
+ public void enqueue(AMQQueue queue) throws AMQException
+ {
+ _destinationQueues.add(queue);
+ }
+
+ public void dequeue(AMQQueue queue) throws AMQException
+ {
+ _messageHandle.dequeue(_messageId, queue);
+ }
+
+ public boolean isPersistent() throws AMQException
+ {
+ if (_contentHeaderBody != null)
+ {
+ //todo remove literal values to a constant file such as AMQConstants in common
+ return _contentHeaderBody.properties instanceof BasicContentHeaderProperties &&
+ ((BasicContentHeaderProperties) _contentHeaderBody.properties).getDeliveryMode() == 2;
+ }
+ else
+ {
+ return _messageHandle.isPersistent(_messageId);
+ }
+ }
+
+ /**
+ * 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, AMQException
+ {
+ BasicPublishBody pb = getPublishBody();
+ if (pb.immediate && !_deliveredToConsumer)
+ {
+ throw new NoConsumersException(this);
+ }
+ }
+
+ public BasicPublishBody getPublishBody() throws AMQException
+ {
+ BasicPublishBody pb;
+ if (_publishBody != null)
+ {
+ pb = _publishBody;
+ }
+ else
+ {
+ pb = _messageHandle.getPublishBody(_messageId);
+ }
+ return pb;
+ }
+
+ /**
+ * Called when this message is delivered to a consumer. (used to
+ * implement the 'immediate' flag functionality).
+ */
+ public void setDeliveredToConsumer()
+ {
+ _deliveredToConsumer = true;
+ }
+
+ /*public void registerQueue(AMQQueue queue)
+ {
+ _destinationQueues.add(queue);
+ } */
+
+ private void deliver() throws AMQException
+ {
+ // first we allow the handle to know that the message has been fully received. This is useful if it is
+ // maintaining any calculated values based on content chunks
+ try
+ {
+ _messageHandle.setPublishAndContentHeaderBody(_messageId, _publishBody, _contentHeaderBody);
+ _publishBody = null;
+ _contentHeaderBody = null;
+
+ // we then allow the transactional context to do something with the message content
+ // now that it has all been received, before we attempt delivery
+ _txnContext.messageFullyReceived(isPersistent());
+ for (AMQQueue q : _destinationQueues)
+ {
+ _txnContext.deliver(this, q);
+ }
+ }
+ finally
+ {
+ _destinationQueues.clear();
+ _destinationQueues = null;
+ decrementReference();
+ }
+ }
+
+ public void writeDeliver(AMQProtocolSession protocolSession, int channelId, long deliveryTag, String consumerTag)
+ throws AMQException
+ {
+ ByteBuffer deliver = createEncodedDeliverFrame(channelId, deliveryTag, consumerTag);
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ getContentHeaderBody());
+
+ Iterator<AMQDataBlock> bodyFrameIterator = getBodyFrameIterator(channelId);
+ //
+ // Optimise the case where we have a single content body. In that case we create a composite block
+ // so that we can writeDeliver out the deliver, header and body with a single network writeDeliver.
+ //
+ if (bodyFrameIterator.hasNext())
+ {
+ AMQDataBlock firstContentBody = bodyFrameIterator.next();
+ AMQDataBlock[] headerAndFirstContent = new AMQDataBlock[]{contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(deliver, headerAndFirstContent);
+ protocolSession.writeFrame(compositeBlock);
+ }
+ else
+ {
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(deliver,
+ new AMQDataBlock[]{contentHeader});
+ protocolSession.writeFrame(compositeBlock);
+ }
+
+ //
+ // Now start writing out the other content bodies
+ // TODO: MINA needs to be fixed so the the pending writes buffer is not unbounded
+ //
+ while (bodyFrameIterator.hasNext())
+ {
+ protocolSession.writeFrame(bodyFrameIterator.next());
+ }
+
+ }
+
+ private ByteBuffer createEncodedDeliverFrame(int channelId, long deliveryTag, String consumerTag)
+ throws AMQException
+ {
+ BasicPublishBody pb = getPublishBody();
+ AMQFrame deliverFrame = BasicDeliverBody.createAMQFrame(channelId, consumerTag,
+ deliveryTag, false, pb.exchange,
+ pb.routingKey);
+ ByteBuffer buf = ByteBuffer.allocate((int) deliverFrame.getSize()); // XXX: Could cast be a problem?
+ deliverFrame.writePayload(buf);
+ buf.flip();
+ return buf;
+ }
+
+ private ByteBuffer createEncodedReturnFrame(int channelId, int replyCode, String replyText)
+ {
+ AMQFrame returnFrame = BasicReturnBody.createAMQFrame(channelId, replyCode, replyText, _publishBody.exchange,
+ _publishBody.routingKey);
+ ByteBuffer buf = ByteBuffer.allocate((int) returnFrame.getSize()); // XXX: Could cast be a problem?
+ returnFrame.writePayload(buf);
+ buf.flip();
+ return buf;
+ }
+
+ public void writeReturn(AMQProtocolSession protocolSession, int channelId, int replyCode, String replyText)
+ throws AMQException
+ {
+ ByteBuffer returnFrame = createEncodedReturnFrame(channelId, replyCode, replyText);
+
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ getContentHeaderBody());
+
+ Iterator<AMQDataBlock> bodyFrameIterator = getBodyFrameIterator(channelId);
+ //
+ // Optimise the case where we have a single content body. In that case we create a composite block
+ // so that we can writeDeliver out the deliver, header and body with a single network writeDeliver.
+ //
+ if (bodyFrameIterator.hasNext())
+ {
+ AMQDataBlock firstContentBody = bodyFrameIterator.next();
+ AMQDataBlock[] headerAndFirstContent = new AMQDataBlock[]{contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(returnFrame, headerAndFirstContent);
+ protocolSession.writeFrame(compositeBlock);
+ }
+ else
+ {
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(returnFrame,
+ new AMQDataBlock[]{contentHeader});
+ protocolSession.writeFrame(compositeBlock);
+ }
+
+ //
+ // Now start writing out the other content bodies
+ // TODO: MINA needs to be fixed so the the pending writes buffer is not unbounded
+ //
+ while (bodyFrameIterator.hasNext())
+ {
+ protocolSession.writeFrame(bodyFrameIterator.next());
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java
new file mode 100644
index 0000000000..0fc9ec5dcd
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java
@@ -0,0 +1,62 @@
+/**
+ * User: Robert Greig
+ * Date: 23-Oct-2006
+ ******************************************************************************
+ * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of
+ * this program may be photocopied reproduced or translated to another
+ * program language without prior written consent of JP Morgan Chase Ltd
+ ******************************************************************************/
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+
+/**
+ * A pluggable way of getting message data. Implementations can provide intelligent caching for example or
+ * even no caching at all to minimise the broker memory footprint.
+ *
+ * The method all take a messageId to avoid having to store it in the instance - the AMQMessage container
+ * must already keen the messageId so it is pointless storing it twice.
+ */
+public interface AMQMessageHandle
+{
+ ContentHeaderBody getContentHeaderBody(long messageId) throws AMQException;
+
+ /**
+ * @return the number of body frames associated with this message
+ */
+ int getBodyCount();
+
+ /**
+ * @return the size of the body
+ */
+ long getBodySize(long messageId) throws AMQException;
+
+ /**
+ * Get a particular content body
+ * @param index the index of the body to retrieve, must be between 0 and getBodyCount() - 1
+ * @return a content body
+ * @throws IllegalArgumentException if the index is invalid
+ */
+ ContentBody getContentBody(long messageId, int index) throws IllegalArgumentException, AMQException;
+
+ void addContentBodyFrame(long messageId, ContentBody contentBody) throws AMQException;
+
+ BasicPublishBody getPublishBody(long messageId) throws AMQException;
+
+ boolean isRedelivered();
+
+ boolean isPersistent(long messageId) throws AMQException;
+
+ void setPublishAndContentHeaderBody(long messageId, BasicPublishBody publishBody,
+ ContentHeaderBody contentHeaderBody)
+ throws AMQException;
+
+ void removeMessage(long messageId) throws AMQException;
+
+ void enqueue(long messageId, AMQQueue queue) throws AMQException;
+
+ void dequeue(long messageId, AMQQueue queue) throws AMQException;
+} \ No newline at end of file
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..d8bab6e3bc
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java
@@ -0,0 +1,764 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.framing.ContentBody;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.management.*;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+
+import javax.management.*;
+import javax.management.monitor.MonitorNotification;
+import javax.management.openmbean.*;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+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;
+ //private MBeanInfo _mbeanInfo;
+
+ // AMQ message attribute names exposed.
+ private String[] _msgAttributeNames = { "MessageId",
+ "Redelivered",
+ "Content's size",
+ "Contents" };
+ // AMQ Message attribute descriptions.
+ private String[] _msgAttributeDescriptions = { "Message Id",
+ "Redelivered",
+ "Message content's size in bytes",
+ "Message content bodies" };
+ // AMQ message attribute types.
+ private OpenType[] _msgAttributeTypes = new OpenType[4];
+ // Messages will be indexed according to the messageId.
+ private String[] _msgAttributeIndex = { "MessageId"};
+ // Composite type for representing AMQ Message data.
+ private CompositeType _messageDataType = null;
+ // Datatype for representing AMQ messages list.
+ private TabularType _messagelistDataType = null;
+
+ private String[] _contentNames = {"SerialNumber", "ContentBody"};
+ private String[] _contentDesc = {"SerialNumber", "Message Content"};
+ private String[] _contentIndex = {"SerialNumber"};
+ private OpenType[] _contentType = new OpenType[2];
+ private CompositeType _contentBodyType = null;
+ private TabularType _contentBodyListType = null;
+
+ @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
+ {
+ _contentType[0] = SimpleType.INTEGER;
+ _contentType[1] = new ArrayType(1, SimpleType.BYTE);
+ _contentBodyType = new CompositeType("Content",
+ "Message body content",
+ _contentNames,
+ _contentDesc,
+ _contentType);
+ _contentBodyListType = new TabularType("MessageContents",
+ "MessageContent",
+ _contentBodyType,
+ _contentIndex);
+
+ _msgAttributeTypes[0] = SimpleType.LONG;
+ _msgAttributeTypes[1] = SimpleType.BOOLEAN;
+ _msgAttributeTypes[2] = SimpleType.LONG;
+ _msgAttributeTypes[3] = _contentBodyListType;
+
+ _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() throws AMQException
+ {
+ List<AMQMessage> list = _deliveryMgr.getMessages();
+ if (list.size() == 0)
+ return 0l;
+
+ long queueSize = 0;
+ for (AMQMessage message : list)
+ {
+ queueSize = queueSize + getMessageSize(message);
+ }
+ return (long) Math.round(queueSize/100);
+ }
+ // Operations
+
+ // calculates the size of an AMQMessage
+ private long getMessageSize(AMQMessage msg) throws AMQException
+ {
+ if (msg == null)
+ {
+ return 0L;
+ }
+
+ return msg.getContentHeaderBody().bodySize;
+ }
+
+ // Checks if there is any notification to be send to the listeners
+ private void checkForNotification(AMQMessage msg) throws AMQException
+ {
+ // 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());
+ }
+ }
+
+ /**
+ * 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();
+
+ if (beginIndex > list.size())
+ {
+ throw new JMException("FromIndex = " + beginIndex + ". There are only " + list.size() +
+ " messages in the queue");
+ }
+
+ endIndex = endIndex < list.size() ? endIndex : list.size();
+ TabularDataSupport _messageList = new TabularDataSupport(_messagelistDataType);
+
+ for (int i = beginIndex; i <= endIndex; i++)
+ {
+ AMQMessage msg = list.get(i - 1);
+ long msgId = msg.getMessageId();
+
+ Iterator<ContentBody> cBodies = msg.getContentBodyIterator();
+
+ TabularDataSupport _contentList = new TabularDataSupport(_contentBodyListType);
+ int contentSerialNo = 1;
+ long size = 0;
+
+ while (cBodies.hasNext())
+ {
+ ContentBody body = cBodies.next();
+ if (body.getSize() != 0)
+ {
+ Byte[] byteArray = getByteArray(body.payload.slice().array());
+ size = size + byteArray.length;
+
+ Object[] contentValues = {contentSerialNo, byteArray};
+ CompositeData contentData = new CompositeDataSupport(_contentBodyType,
+ _contentNames,
+ contentValues);
+
+ _contentList.put(contentData);
+ }
+ }
+
+ Object[] itemValues = {msgId, true, size, _contentList};
+ CompositeData messageData = new CompositeDataSupport(_messageDataType,
+ _msgAttributeNames,
+ itemValues);
+ _messageList.put(messageData);
+ }
+
+ return _messageList;
+ }
+
+ /**
+ * A utility to convert byte[] to Byte[]. Required to create composite
+ * type for message contents.
+ * @param byteArray message content as byte[]
+ * @return Byte[]
+ */
+ private Byte[] getByteArray(byte[] byteArray)
+ {
+ int size = byteArray.length;
+ List<Byte> list = new ArrayList<Byte>();
+
+ for (int i = 0; i < size; i++)
+ {
+ list.add(byteArray[i]);
+ }
+
+ return list.toArray(new Byte[0]);
+ }
+
+ /**
+ * 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;
+ _deliveryMgr = new DeliveryManager(_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.getQueueMessageCount() > 0)
+ {
+ _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();
+ } */
+
+ public void process(AMQMessage msg) throws FailedDequeueException, AMQException
+ {
+ _deliveryMgr.deliver(getName(), msg);
+ updateReceivedMessageCount(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) throws AMQException
+ {
+ _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));
+ }
+ }
+}
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..60788c1ccb
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/AsyncDeliveryConfig.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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/DefaultQueueRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java
new file mode 100644
index 0000000000..a7dc98ec22
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java
@@ -0,0 +1,47 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..55ecf799f6
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java
@@ -0,0 +1,259 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 DeliveryManager
+{
+ private static final Logger _log = Logger.getLogger(DeliveryManager.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 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;
+
+ DeliveryManager(SubscriptionManager subscriptions, AMQQueue queue)
+ {
+ _subscriptions = subscriptions;
+ _queue = queue;
+ }
+
+ private synchronized boolean enqueue(AMQMessage msg) throws AMQException
+ {
+ if (_queueing)
+ {
+ if(msg.getPublishBody().immediate)
+ {
+ //can't enqueue messages for whom immediate delivery is required
+ return false;
+ }
+ else
+ {
+ _messages.offer(msg);
+ return true;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private synchronized void startQueueing(AMQMessage msg) throws AMQException
+ {
+ _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
+ */
+ private synchronized boolean hasQueuedMessages()
+ {
+ boolean empty = _messages.isEmpty();
+ if (empty)
+ {
+ _queueing = false;
+ }
+ return !empty;
+ }
+
+ public synchronized int getQueueMessageCount()
+ {
+ return _messages.size();
+ }
+
+ protected synchronized List<AMQMessage> getMessages()
+ {
+ return new ArrayList<AMQMessage>(_messages);
+ }
+
+ protected synchronized void removeAMessageFromTop() throws AMQException
+ {
+ AMQMessage msg = poll();
+ if (msg != null)
+ {
+ msg.dequeue(_queue);
+ }
+ }
+
+ protected 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().
+ * @throws org.apache.qpid.AMQException if we cannot process the messages on the queue
+ */
+ private void processQueue() throws AMQException
+ {
+ 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)
+ {
+ next.send(poll(), _queue);
+ }
+ else
+ {
+ hasSubscribers = false;
+ }
+ }
+ }
+ catch (FailedDequeueException e)
+ {
+ _log.error("Unable to deliver message as dequeue failed: " + e, e);
+ }
+ 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
+ */
+ 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
+ * @throws FailedDequeueException if the message could not be dequeued
+ */
+ void deliver(String name, AMQMessage msg) throws FailedDequeueException, AMQException
+ {
+ // first check whether we are queueing, and enqueue if we are
+ if (!enqueue(msg))
+ {
+ // not queueing so deliver message to 'next' subscriber
+ Subscription s = _subscriptions.nextSubscriber(msg);
+ if (s == null)
+ {
+ if (!msg.getPublishBody().immediate)
+ {
+ // 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()
+ {
+ try
+ {
+ processQueue();
+ }
+ catch (AMQException e)
+ {
+ _log.error("Error processing queue: " + e, e);
+ }
+ }
+ }
+}
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..424330bd11
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java
@@ -0,0 +1,109 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..f27d935c25
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java
@@ -0,0 +1,36 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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/InMemoryMessageHandle.java b/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java
new file mode 100644
index 0000000000..e2e334c5f4
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java
@@ -0,0 +1,110 @@
+/**
+ * User: Robert Greig
+ * Date: 21-Nov-2006
+ ******************************************************************************
+ * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of
+ * this program may be photocopied reproduced or translated to another
+ * program language without prior written consent of JP Morgan Chase Ltd
+ ******************************************************************************/
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ */
+public class InMemoryMessageHandle implements AMQMessageHandle
+{
+
+ private ContentHeaderBody _contentHeaderBody;
+
+ private BasicPublishBody _publishBody;
+
+ private List<ContentBody> _contentBodies = new LinkedList<ContentBody>();
+
+ private boolean _redelivered;
+
+ public InMemoryMessageHandle()
+ {
+ }
+
+ public ContentHeaderBody getContentHeaderBody(long messageId) throws AMQException
+ {
+ return _contentHeaderBody;
+ }
+
+ public int getBodyCount()
+ {
+ return _contentBodies.size();
+ }
+
+ public long getBodySize(long messageId) throws AMQException
+ {
+ return getContentHeaderBody(messageId).bodySize;
+ }
+
+ public ContentBody getContentBody(long messageId, int index) throws AMQException, IllegalArgumentException
+ {
+ if (index > _contentBodies.size() - 1)
+ {
+ throw new IllegalArgumentException("Index " + index + " out of valid range 0 to " +
+ (_contentBodies.size() - 1));
+ }
+ return _contentBodies.get(index);
+ }
+
+ public void addContentBodyFrame(long messageId, ContentBody contentBody) throws AMQException
+ {
+ _contentBodies.add(contentBody);
+ }
+
+ public BasicPublishBody getPublishBody(long messageId) throws AMQException
+ {
+ return _publishBody;
+ }
+
+ public boolean isRedelivered()
+ {
+ return _redelivered;
+ }
+
+ public boolean isPersistent(long messageId) throws AMQException
+ {
+ //todo remove literal values to a constant file such as AMQConstants in common
+ ContentHeaderBody chb = getContentHeaderBody(messageId);
+ return chb.properties instanceof BasicContentHeaderProperties &&
+ ((BasicContentHeaderProperties) chb.properties).getDeliveryMode() == 2;
+ }
+
+ /**
+ * This is called when all the content has been received.
+ * @param publishBody
+ * @param contentHeaderBody
+ * @throws AMQException
+ */
+ public void setPublishAndContentHeaderBody(long messageId, BasicPublishBody publishBody,
+ ContentHeaderBody contentHeaderBody)
+ throws AMQException
+ {
+ _publishBody = publishBody;
+ _contentHeaderBody = contentHeaderBody;
+ }
+
+ public void removeMessage(long messageId) throws AMQException
+ {
+ }
+
+ public void enqueue(long messageId, AMQQueue queue) throws AMQException
+ {
+ }
+
+ public void dequeue(long messageId, AMQQueue queue) throws AMQException
+ {
+ }
+}
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..7528011bac
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java
@@ -0,0 +1,213 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 org.apache.qpid.AMQException;
+
+import javax.management.JMException;
+import javax.management.MBeanOperationInfo;
+import javax.management.openmbean.TabularData;
+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, AMQException;
+
+ /**
+ * 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;
+
+}
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..6f0fa285b1
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java
@@ -0,0 +1,32 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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/MessageHandleFactory.java b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.java
new file mode 100644
index 0000000000..93aceb76ec
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.java
@@ -0,0 +1,34 @@
+/**
+ * User: Robert Greig
+ * Date: 31-Oct-2006
+ ******************************************************************************
+ * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of
+ * this program may be photocopied reproduced or translated to another
+ * program language without prior written consent of JP Morgan Chase Ltd
+ ******************************************************************************/
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.server.store.MessageStore;
+
+/**
+ * Constructs a message handle based on the publish body, the content header and the queue to which the message
+ * has been routed.
+ *
+ * @author Robert Greig (robert.j.greig@jpmorgan.com)
+ */
+public class MessageHandleFactory
+{
+
+ public AMQMessageHandle createMessageHandle(long messageId, MessageStore store, boolean persistent)
+ {
+ // just hardcoded for now
+ if (persistent)
+ {
+ return new WeakReferenceMessageHandle(store);
+ }
+ else
+ {
+ return new InMemoryMessageHandle();
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java
new file mode 100644
index 0000000000..deed18c188
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java
@@ -0,0 +1,70 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.framing.BasicPublishBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+
+/**
+ * Encapsulates a publish body and a content header. In the context of the message store these are treated as a
+ * single unit.
+ */
+public class MessageMetaData
+{
+ private BasicPublishBody _publishBody;
+
+ private ContentHeaderBody _contentHeaderBody;
+
+ private int _contentChunkCount;
+
+ public MessageMetaData(BasicPublishBody publishBody, ContentHeaderBody contentHeaderBody, int contentChunkCount)
+ {
+ _contentHeaderBody = contentHeaderBody;
+ _publishBody = publishBody;
+ _contentChunkCount = contentChunkCount;
+ }
+
+ public int getContentChunkCount()
+ {
+ return _contentChunkCount;
+ }
+
+ public void setContentChunkCount(int contentChunkCount)
+ {
+ _contentChunkCount = contentChunkCount;
+ }
+
+ public ContentHeaderBody getContentHeaderBody()
+ {
+ return _contentHeaderBody;
+ }
+
+ public void setContentHeaderBody(ContentHeaderBody contentHeaderBody)
+ {
+ _contentHeaderBody = contentHeaderBody;
+ }
+
+ public BasicPublishBody getPublishBody()
+ {
+ return _publishBody;
+ }
+
+ public void setPublishBody(BasicPublishBody publishBody)
+ {
+ _publishBody = publishBody;
+ }
+}
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..09f01d6d97
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java
@@ -0,0 +1,39 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.protocol.AMQConstant;
+import org.apache.qpid.server.RequiredDeliveryException;
+
+/**
+ * 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(AMQMessage message)
+ {
+ super("Immediate delivery is not possible.", message);
+ }
+
+ 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..bc5599fb20
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..a248efecb1
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/Subscription.java
@@ -0,0 +1,29 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 AMQException;
+
+ boolean isSuspended();
+
+ void queueDeleted(AMQQueue queue) throws AMQException;
+}
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..127b19b0e4
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.java
@@ -0,0 +1,37 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..4422d1ffc2
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java
@@ -0,0 +1,170 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.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 AMQException
+ {
+ 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);
+ }
+
+ msg.writeDeliver(protocolSession, channel.getChannelId(), deliveryTag, consumerTag);
+ }
+ }
+ 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) throws AMQException
+ {
+ channel.queueDeleted(queue);
+ }
+}
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..185b0e4268
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.java
@@ -0,0 +1,28 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..fe55bd071b
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java
@@ -0,0 +1,182 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 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) throws AMQException
+ {
+ 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/WeakReferenceMessageHandle.java b/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java
new file mode 100644
index 0000000000..bf82940ec8
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java
@@ -0,0 +1,144 @@
+/**
+ * User: Robert Greig
+ * Date: 23-Oct-2006
+ ******************************************************************************
+ * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of
+ * this program may be photocopied reproduced or translated to another
+ * program language without prior written consent of JP Morgan Chase Ltd
+ ******************************************************************************/
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.server.store.MessageStore;
+
+import java.lang.ref.WeakReference;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author Robert Greig (robert.j.greig@jpmorgan.com)
+ */
+public class WeakReferenceMessageHandle implements AMQMessageHandle
+{
+ private WeakReference<ContentHeaderBody> _contentHeaderBody;
+
+ private WeakReference<BasicPublishBody> _publishBody;
+
+ private List<WeakReference<ContentBody>> _contentBodies = new LinkedList<WeakReference<ContentBody>>();
+
+ private boolean _redelivered;
+
+ private final MessageStore _messageStore;
+
+ public WeakReferenceMessageHandle(MessageStore messageStore)
+ {
+ _messageStore = messageStore;
+ }
+
+ public ContentHeaderBody getContentHeaderBody(long messageId) throws AMQException
+ {
+ ContentHeaderBody chb = _contentHeaderBody.get();
+ if (chb == null)
+ {
+ MessageMetaData mmd = _messageStore.getMessageMetaData(messageId);
+ chb = mmd.getContentHeaderBody();
+ _contentHeaderBody = new WeakReference<ContentHeaderBody>(chb);
+ _publishBody = new WeakReference<BasicPublishBody>(mmd.getPublishBody());
+ }
+ return chb;
+ }
+
+ public int getBodyCount()
+ {
+ return _contentBodies.size();
+ }
+
+ public long getBodySize(long messageId) throws AMQException
+ {
+ return getContentHeaderBody(messageId).bodySize;
+ }
+
+ public ContentBody getContentBody(long messageId, int index) throws AMQException, IllegalArgumentException
+ {
+ if (index > _contentBodies.size() - 1)
+ {
+ throw new IllegalArgumentException("Index " + index + " out of valid range 0 to " +
+ (_contentBodies.size() - 1));
+ }
+ WeakReference<ContentBody> wr = _contentBodies.get(index);
+ ContentBody cb = wr.get();
+ if (cb == null)
+ {
+ cb = _messageStore.getContentBodyChunk(messageId, index);
+ _contentBodies.set(index, new WeakReference<ContentBody>(cb));
+ }
+ return cb;
+ }
+
+ public void addContentBodyFrame(long messageId, ContentBody contentBody) throws AMQException
+ {
+ _contentBodies.add(new WeakReference<ContentBody>(contentBody));
+ _messageStore.storeContentBodyChunk(messageId, _contentBodies.size() - 1, contentBody);
+ }
+
+ public BasicPublishBody getPublishBody(long messageId) throws AMQException
+ {
+ BasicPublishBody bpb = _publishBody.get();
+ if (bpb == null)
+ {
+ MessageMetaData mmd = _messageStore.getMessageMetaData(messageId);
+ bpb = mmd.getPublishBody();
+ _publishBody = new WeakReference<BasicPublishBody>(bpb);
+ _contentHeaderBody = new WeakReference<ContentHeaderBody>(mmd.getContentHeaderBody());
+ }
+ return bpb;
+ }
+
+ public boolean isRedelivered()
+ {
+ return _redelivered;
+ }
+
+ public boolean isPersistent(long messageId) throws AMQException
+ {
+ //todo remove literal values to a constant file such as AMQConstants in common
+ ContentHeaderBody chb = getContentHeaderBody(messageId);
+ return chb.properties instanceof BasicContentHeaderProperties &&
+ ((BasicContentHeaderProperties) chb.properties).getDeliveryMode() == 2;
+ }
+
+ /**
+ * This is called when all the content has been received.
+ * @param publishBody
+ * @param contentHeaderBody
+ * @throws AMQException
+ */
+ public void setPublishAndContentHeaderBody(long messageId, BasicPublishBody publishBody,
+ ContentHeaderBody contentHeaderBody)
+ throws AMQException
+ {
+ _messageStore.storeMessageMetaData(messageId, new MessageMetaData(publishBody, contentHeaderBody,
+ _contentBodies.size()));
+ _publishBody = new WeakReference<BasicPublishBody>(publishBody);
+ _contentHeaderBody = new WeakReference<ContentHeaderBody>(contentHeaderBody);
+ }
+
+ public void removeMessage(long messageId) throws AMQException
+ {
+ _messageStore.removeMessage(messageId);
+ }
+
+ public void enqueue(long messageId, AMQQueue queue) throws AMQException
+ {
+ _messageStore.enqueueMessage(queue.getName(), messageId);
+ }
+
+ public void dequeue(long messageId, AMQQueue queue) throws AMQException
+ {
+ _messageStore.dequeueMessage(queue.getName(), messageId);
+ }
+}
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..adf6aefdfb
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java
@@ -0,0 +1,23 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..50f3d711ab
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java
@@ -0,0 +1,197 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..db79ae8876
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java
@@ -0,0 +1,155 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..0102f78424
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..0adb7b98e2
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationManager.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..71e3c81ae4
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..d7dcf2c973
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java
@@ -0,0 +1,40 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..bfd2ac1b9f
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java
@@ -0,0 +1,35 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..1477b33ebe
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/JCAProvider.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..95a53951ad
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/NullAuthenticationManager.java
@@ -0,0 +1,82 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..fb11b89367
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java
@@ -0,0 +1,130 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..8add5455ee
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/PrincipalDatabase.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..7d0b60d95e
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java
@@ -0,0 +1,224 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..02a953f47c
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java
@@ -0,0 +1,99 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..1d425b8399
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java
@@ -0,0 +1,39 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..94406237a5
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java
@@ -0,0 +1,35 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..0f7981abe1
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java
@@ -0,0 +1,120 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..c4e904f923
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java
@@ -0,0 +1,56 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..7b055a4f58
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java
@@ -0,0 +1,35 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..5a69ed02ba
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java
@@ -0,0 +1,141 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..754ecbde78
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java
@@ -0,0 +1,56 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..46d46eb4c0
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java
@@ -0,0 +1,33 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..54c8bcd9d5
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java
@@ -0,0 +1,219 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..c77cb4f833
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..9776151c28
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java
@@ -0,0 +1,37 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..d31e786ef1
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java
@@ -0,0 +1,27 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..0a243fa138
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java
@@ -0,0 +1,172 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.framing.ContentBody;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.MessageMetaData;
+import org.apache.qpid.server.queue.QueueRegistry;
+
+import java.util.ArrayList;
+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, MessageMetaData> _metaDataMap;
+
+ protected ConcurrentMap<Long, List<ContentBody>> _contentBodyMap;
+
+ private final AtomicLong _messageId = new AtomicLong(1);
+
+ public void configure()
+ {
+ _log.info("Using capacity " + DEFAULT_HASHTABLE_CAPACITY + " for hash tables");
+ _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>(DEFAULT_HASHTABLE_CAPACITY);
+ _contentBodyMap = new ConcurrentHashMap<Long, List<ContentBody>>(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 tables");
+ _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>(hashtableCapacity);
+ _contentBodyMap = new ConcurrentHashMap<Long, List<ContentBody>>(hashtableCapacity);
+ }
+
+ public void configure(QueueRegistry queueRegistry, String base, Configuration config) throws Exception
+ {
+ configure(base, config);
+ }
+
+ public void close() throws Exception
+ {
+ if (_metaDataMap != null)
+ {
+ _metaDataMap.clear();
+ _metaDataMap = null;
+ }
+ if (_contentBodyMap != null)
+ {
+ _contentBodyMap.clear();
+ _contentBodyMap = null;
+ }
+ }
+
+ public void removeMessage(long messageId)
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Removing message with id " + messageId);
+ }
+ _metaDataMap.remove(messageId);
+ _contentBodyMap.remove(messageId);
+ }
+
+ public void createQueue(AMQQueue queue) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void removeQueue(String name) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void enqueueMessage(String name, long messageId) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void dequeueMessage(String name, long messageId) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void beginTran() throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void commitTran() throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void abortTran() throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public boolean inTran()
+ {
+ return false;
+ }
+
+ public List<AMQQueue> createQueues() throws AMQException
+ {
+ return null;
+ }
+
+ public long getNewMessageId()
+ {
+ return _messageId.getAndIncrement();
+ }
+
+ public void storeContentBodyChunk(long messageId, int index, ContentBody contentBody) throws AMQException
+ {
+ List<ContentBody> bodyList = _contentBodyMap.get(messageId);
+ if (bodyList == null)
+ {
+ bodyList = new ArrayList<ContentBody>();
+ _contentBodyMap.put(messageId, bodyList);
+ }
+
+ bodyList.add(index, contentBody);
+ }
+
+ public void storeMessageMetaData(long messageId, MessageMetaData messageMetaData) throws AMQException
+ {
+ _metaDataMap.put(messageId, messageMetaData);
+ }
+
+ public MessageMetaData getMessageMetaData(long messageId) throws AMQException
+ {
+ return _metaDataMap.get(messageId);
+ }
+
+ public ContentBody getContentBodyChunk(long messageId, int index) throws AMQException
+ {
+ List<ContentBody> bodyList = _contentBodyMap.get(messageId);
+ return bodyList.get(index);
+ }
+}
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..c0e8d0465b
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java
@@ -0,0 +1,87 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.framing.ContentBody;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.MessageMetaData;
+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
+ * @throws Exception if an error occurs that means the store is unable to configure itself
+ */
+ void configure(QueueRegistry queueRegistry, String base, Configuration config) throws Exception;
+
+ /**
+ * Called to close and cleanup any resources used by the message store.
+ * @throws Exception if close fails
+ */
+ void close() throws Exception;
+
+ 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();
+
+ void storeContentBodyChunk(long messageId, int index, ContentBody contentBody) throws AMQException;
+
+ void storeMessageMetaData(long messageId, MessageMetaData messageMetaData) throws AMQException;
+
+ MessageMetaData getMessageMetaData(long messageId) throws AMQException;
+
+ ContentBody getContentBodyChunk(long messageId, int index) throws AMQException;
+
+}
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..6470d876bb
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/transport/ConnectorConfiguration.java
@@ -0,0 +1,83 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..e3d87f808c
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java
@@ -0,0 +1,692 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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/CleanupMessageOperation.java b/java/broker/src/main/java/org/apache/qpid/server/txn/CleanupMessageOperation.java
new file mode 100644
index 0000000000..7ff609b750
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/txn/CleanupMessageOperation.java
@@ -0,0 +1,89 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.queue.AMQMessage;
+import org.apache.qpid.server.queue.NoConsumersException;
+import org.apache.qpid.server.RequiredDeliveryException;
+
+import java.util.List;
+
+/**
+ * @author Apache Software Foundation
+ */
+public class CleanupMessageOperation implements TxnOp
+{
+ private static final Logger _log = Logger.getLogger(CleanupMessageOperation.class);
+
+ private final AMQMessage _msg;
+
+ private final List<RequiredDeliveryException> _returns;
+
+ public CleanupMessageOperation(AMQMessage msg, List<RequiredDeliveryException> returns)
+ {
+ _msg = msg;
+ _returns = returns;
+ }
+
+ 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);
+ }
+ catch (AMQException e)
+ {
+ _log.error("On commiting transaction, unable to determine whether delivered to a consumer immediately: " +
+ e, e);
+ }
+ }
+
+ public void rollback()
+ {
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/DeliverMessageOperation.java b/java/broker/src/main/java/org/apache/qpid/server/txn/DeliverMessageOperation.java
new file mode 100644
index 0000000000..059c13c687
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/txn/DeliverMessageOperation.java
@@ -0,0 +1,61 @@
+/**
+ * User: Robert Greig
+ * Date: 01-Nov-2006
+ ******************************************************************************
+ * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of
+ * this program may be photocopied reproduced or translated to another
+ * program language without prior written consent of JP Morgan Chase Ltd
+ ******************************************************************************/
+package org.apache.qpid.server.txn;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+
+/**
+ * @author Robert Greig (robert.j.greig@jpmorgan.com)
+ */
+public class DeliverMessageOperation implements TxnOp
+{
+ private static final Logger _logger = Logger.getLogger(DeliverMessageOperation.class);
+
+ private final AMQMessage _msg;
+
+ private final AMQQueue _queue;
+
+ public DeliverMessageOperation(AMQMessage msg, AMQQueue queue)
+ {
+ _msg = msg;
+ _queue = queue;
+ _msg.incrementReference();
+ }
+
+ public void prepare() throws AMQException
+ {
+ }
+
+ public void undoPrepare()
+ {
+ }
+
+ public void commit()
+ {
+ //do the memeory part of the record()
+ _msg.incrementReference();
+ //then process the message
+ try
+ {
+ _queue.process(_msg);
+ }
+ catch (AMQException 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/txn/LocalTransactionalContext.java b/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java
new file mode 100644
index 0000000000..df6520e0a8
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java
@@ -0,0 +1,149 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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;
+import org.apache.qpid.server.ack.TxAck;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.store.MessageStore;
+
+import java.util.List;
+
+/**
+ * A transactional context that only supports local transactions.
+ */
+public class LocalTransactionalContext implements TransactionalContext
+{
+ private final TxnBuffer _txnBuffer;
+
+ /**
+ * We keep hold of the ack operation so that we can consolidate acks, i.e. multiple acks within a txn are
+ * consolidated into a single operation
+ */
+ private TxAck _ackOp;
+
+ private List<RequiredDeliveryException> _returnMessages;
+
+ private final MessageStore _messageStore;
+
+ private boolean _inTran = false;
+
+ public LocalTransactionalContext(MessageStore messageStore,
+ TxnBuffer txnBuffer, List<RequiredDeliveryException> returnMessages)
+ {
+ _messageStore = messageStore;
+ _txnBuffer = txnBuffer;
+ _returnMessages = returnMessages;
+ _txnBuffer.enlist(new StoreMessageOperation(messageStore));
+ }
+
+ public void rollback() throws AMQException
+ {
+ _txnBuffer.rollback();
+ }
+
+ public void deliver(AMQMessage message, AMQQueue queue) throws AMQException
+ {
+ // don't create a transaction unless needed
+ if (message.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.
+
+ _txnBuffer.enlist(new DeliverMessageOperation(message, queue));
+ _txnBuffer.enlist(new CleanupMessageOperation(message, _returnMessages));
+ }
+
+ private void checkAck(long deliveryTag, UnacknowledgedMessageMap unacknowledgedMessageMap) throws AMQException
+ {
+ if (!unacknowledgedMessageMap.contains(deliveryTag))
+ {
+ throw new AMQException("Ack with delivery tag " + deliveryTag + " not known for channel");
+ }
+ }
+
+ public void acknowledgeMessage(long deliveryTag, long lastDeliveryTag, boolean multiple,
+ UnacknowledgedMessageMap unacknowledgedMessageMap) throws AMQException
+ {
+ //check that the tag exists to give early failure
+ if (!multiple || deliveryTag > 0)
+ {
+ checkAck(deliveryTag, unacknowledgedMessageMap);
+ }
+ //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(unacknowledgedMessageMap);
+ _txnBuffer.enlist(_ackOp);
+ }
+ // update the op to include this ack request
+ if (multiple && deliveryTag == 0)
+ {
+ // if have signalled to ack all, that refers only
+ // to all at this time
+ _ackOp.update(lastDeliveryTag, multiple);
+ }
+ else
+ {
+ _ackOp.update(deliveryTag, multiple);
+ }
+ }
+
+ public void messageFullyReceived(boolean persistent) throws AMQException
+ {
+ // Not required in this transactional context
+ }
+
+ public void beginTranIfNecessary() throws AMQException
+ {
+ if (!_inTran)
+ {
+ _messageStore.beginTran();
+ _inTran = true;
+ }
+ }
+
+ public void commit() throws AMQException
+ {
+ if (_ackOp != null)
+ {
+ _ackOp.consolidate();
+ if (_ackOp.checkPersistent())
+ {
+ _txnBuffer.containsPersistentChanges();
+ }
+ //already enlisted, after commit will reset regardless of outcome
+ _ackOp = null;
+ }
+
+ _txnBuffer.commit();
+ //TODO: may need to return 'immediate' messages at this point
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java b/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java
new file mode 100644
index 0000000000..9c8e7c7002
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java
@@ -0,0 +1,171 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.AMQChannel;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.ack.UnacknowledgedMessage;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
+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 java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author Apache Software Foundation
+ */
+public class NonTransactionalContext implements TransactionalContext
+{
+ private static final Logger _log = Logger.getLogger(NonTransactionalContext.class);
+
+ /**
+ * Channel is useful for logging
+ */
+ private final AMQChannel _channel;
+
+ /**
+ * Where to put undeliverable messages
+ */
+ private final List<RequiredDeliveryException> _returnMessages;
+
+ private final MessageStore _messageStore;
+
+ /**
+ * Whether we are in a transaction
+ */
+ private boolean _inTran;
+
+ public NonTransactionalContext(MessageStore messageStore, AMQChannel channel,
+ List<RequiredDeliveryException> returnMessages)
+ {
+ _channel = channel;
+ _returnMessages = returnMessages;
+ _messageStore = messageStore;
+ }
+
+ public void beginTranIfNecessary() throws AMQException
+ {
+ if (!_inTran)
+ {
+ _messageStore.beginTran();
+ _inTran = true;
+ }
+ }
+
+ public void commit() throws AMQException
+ {
+ // Does not apply to this context
+ }
+
+ public void rollback() throws AMQException
+ {
+ // Does not apply to this context
+ }
+
+ public void deliver(AMQMessage message, AMQQueue queue) throws AMQException
+ {
+ try
+ {
+ message.incrementReference();
+ queue.process(message);
+ //following check implements the functionality
+ //required by the 'immediate' flag:
+ message.checkDeliveredToConsumer();
+ }
+ catch (NoConsumersException e)
+ {
+ _returnMessages.add(e);
+ }
+ }
+
+ public void acknowledgeMessage(final long deliveryTag, long lastDeliveryTag,
+ boolean multiple, final UnacknowledgedMessageMap unacknowledgedMessageMap)
+ throws AMQException
+ {
+ if (multiple)
+ {
+ 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.info("Multiple ack on delivery tag 0. ACKing all messages. Current count:" +
+ unacknowledgedMessageMap.size());
+ unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor()
+ {
+ public boolean callback(UnacknowledgedMessage message) throws AMQException
+ {
+ message.discard();
+ return false;
+ }
+
+ public void visitComplete()
+ {
+ unacknowledgedMessageMap.clear();
+ }
+ });
+ }
+ else
+ {
+ if (!unacknowledgedMessageMap.contains(deliveryTag))
+ {
+ throw new AMQException("Multiple ack on delivery tag " + deliveryTag + " not known for channel");
+ }
+
+ LinkedList<UnacknowledgedMessage> acked = new LinkedList<UnacknowledgedMessage>();
+ unacknowledgedMessageMap.drainTo(acked, deliveryTag);
+ for (UnacknowledgedMessage msg : acked)
+ {
+ msg.discard();
+ }
+ }
+ }
+ else
+ {
+ UnacknowledgedMessage msg;
+ msg = unacknowledgedMessageMap.remove(deliveryTag);
+
+ if (msg == null)
+ {
+ _log.info("Single ack on delivery tag " + deliveryTag + " not known for channel:" +
+ _channel.getChannelId());
+ throw new AMQException("Single ack on delivery tag " + deliveryTag + " not known for channel:" +
+ _channel.getChannelId());
+ }
+ msg.discard();
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Received non-multiple ack for messaging with delivery tag " + deliveryTag);
+ }
+ }
+ }
+
+ public void messageFullyReceived(boolean persistent) throws AMQException
+ {
+ if (persistent)
+ {
+ _messageStore.commitTran();
+ _inTran = false;
+ }
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java b/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java
new file mode 100644
index 0000000000..2a3b524060
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java
@@ -0,0 +1,45 @@
+/**
+ * User: Robert Greig
+ * Date: 01-Nov-2006
+ ******************************************************************************
+ * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of
+ * this program may be photocopied reproduced or translated to another
+ * program language without prior written consent of JP Morgan Chase Ltd
+ ******************************************************************************/
+package org.apache.qpid.server.txn;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.MessageStore;
+
+/**
+ * A transactional operation to store messages in an underlying persistent store. When this operation
+ * commits it will do everything to ensure that all messages are safely committed to persistent
+ * storage.
+ */
+public class StoreMessageOperation implements TxnOp
+{
+ private final MessageStore _messsageStore;
+
+ public StoreMessageOperation(MessageStore messageStore)
+ {
+ _messsageStore = messageStore;
+ }
+
+ public void prepare() throws AMQException
+ {
+ }
+
+ public void undoPrepare()
+ {
+ }
+
+ public void commit() throws AMQException
+ {
+ _messsageStore.commitTran();
+ }
+
+ public void rollback() throws AMQException
+ {
+ _messsageStore.abortTran();
+ }
+}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java b/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java
new file mode 100644
index 0000000000..1ec216cc8b
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java
@@ -0,0 +1,33 @@
+/**
+ * User: Robert Greig
+ * Date: 01-Nov-2006
+ ******************************************************************************
+ * (c) Copyright JP Morgan Chase Ltd 2006. All rights reserved. No part of
+ * this program may be photocopied reproduced or translated to another
+ * program language without prior written consent of JP Morgan Chase Ltd
+ ******************************************************************************/
+package org.apache.qpid.server.txn;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+
+/**
+ * @author Robert Greig (robert.j.greig@jpmorgan.com)
+ */
+public interface TransactionalContext
+{
+ void beginTranIfNecessary() throws AMQException;
+
+ void commit() throws AMQException;
+
+ void rollback() throws AMQException;
+
+ void deliver(AMQMessage message, AMQQueue queue) throws AMQException;
+
+ void acknowledgeMessage(long deliveryTag, long lastDeliveryTag, boolean multiple,
+ UnacknowledgedMessageMap unacknowledgedMessageMap) throws AMQException;
+
+ void messageFullyReceived(boolean persistent) throws AMQException;
+}
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..4e141495c2
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java
@@ -0,0 +1,119 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.");
+ if (prepare())
+ {
+ _log.debug("Transaction Succeeded");
+ for (TxnOp op : _ops)
+ {
+ op.commit();
+ }
+ }
+ else
+ {
+ _log.debug("Transaction Failed");
+ }
+ }
+ 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..1a81786460
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java
@@ -0,0 +1,51 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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() throws AMQException;
+ /**
+ * 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() throws AMQException;
+}
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..d2c10cb4d1
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java
@@ -0,0 +1,123 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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/LoggingProxy.java b/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java
new file mode 100644
index 0000000000..03c4896422
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java
@@ -0,0 +1,102 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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..0fab1f6895
--- /dev/null
+++ b/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java
@@ -0,0 +1,106 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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;
+ }
+}
+
diff --git a/java/broker/src/main/log4j.properties b/java/broker/src/main/log4j.properties
new file mode 100644
index 0000000000..3ff6f0b581
--- /dev/null
+++ b/java/broker/src/main/log4j.properties
@@ -0,0 +1,6 @@
+log4j.rootCategory=${amqj.logging.level}, console
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.Threshold=info
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%t %d %p [%c{4}] %m%n
diff --git a/java/broker/src/main/test/build-module.xml b/java/broker/src/main/test/build-module.xml
new file mode 100644
index 0000000000..3128b77249
--- /dev/null
+++ b/java/broker/src/main/test/build-module.xml
@@ -0,0 +1,32 @@
+<!--
+ -
+ - Copyright (c) 2006 The Apache Software Foundation
+ -
+ - Licensed 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.
+ -
+ -->
+<project name="Broker Tests" default="build">
+ <property name="module.depends" value="broker common"/>
+ <property name="module.dist" value="false"/>
+
+ <import file="../../module.xml"/>
+
+ <target name="test" depends="build">
+ <junit fork="yes" showoutput="true" haltonfailure="yes">
+ <test name="org.apache.qpid.server.UnitTests"/>
+ <formatter type="plain"/>
+ <classpath refid="module.class.path"/>
+ </junit>
+ </target>
+
+</project>
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/UnitTests.java
new file mode 100644
index 0000000000..3372584c07
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/UnitTests.java
@@ -0,0 +1,40 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ org.apache.qpid.server.configuration.UnitTests.class,
+ org.apache.qpid.server.exchange.UnitTests.class,
+ org.apache.qpid.server.protocol.UnitTests.class,
+ org.apache.qpid.server.queue.UnitTests.class,
+ org.apache.qpid.server.store.UnitTests.class,
+ org.apache.qpid.server.txn.UnitTests.class,
+ org.apache.qpid.server.util.UnitTests.class
+ })
+public class UnitTests
+{
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(UnitTests.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/ack/TxAckTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/ack/TxAckTest.java
new file mode 100644
index 0000000000..3f8898069b
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/ack/TxAckTest.java
@@ -0,0 +1,187 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.Ignore;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.store.TestableMemoryMessageStore;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+public class TxAckTest
+{
+ private Scenario individual;
+ private Scenario multiple;
+ private Scenario combined;
+
+ @Before
+ public void setup() throws Exception
+ {
+ //ack only 5th msg
+ individual = new Scenario(10, Arrays.asList(5l), Arrays.asList(1l, 2l, 3l, 4l, 6l, 7l, 8l, 9l, 10l));
+ individual.update(5, false);
+
+ //ack all up to and including 5th msg
+ multiple = new Scenario(10, Arrays.asList(1l, 2l, 3l, 4l, 5l), Arrays.asList(6l, 7l, 8l, 9l, 10l));
+ multiple.update(5, true);
+
+ //leave only 8th and 9th unacked
+ combined = new Scenario(10, Arrays.asList(1l, 2l, 3l, 4l, 5l, 6l, 7l, 10l), Arrays.asList(8l, 9l));
+ combined.update(3, false);
+ combined.update(5, true);
+ combined.update(7, true);
+ combined.update(2, true);//should be ignored
+ combined.update(1, false);//should be ignored
+ combined.update(10, false);
+ }
+
+ @Test
+ public void prepare() throws AMQException
+ {
+ individual.prepare();
+ multiple.prepare();
+ combined.prepare();
+ }
+
+ @Test
+ public void undoPrepare() throws AMQException
+ {
+ individual.undoPrepare();
+ multiple.undoPrepare();
+ combined.undoPrepare();
+ }
+
+ @Test
+ public void commit() throws AMQException
+ {
+ individual.commit();
+ multiple.commit();
+ combined.commit();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(TxAckTest.class);
+ }
+
+ private class Scenario
+ {
+ private final LinkedHashMap<Long, UnacknowledgedMessage> _messages = new LinkedHashMap<Long, UnacknowledgedMessage>();
+ private final UnacknowledgedMessageMap _map = new UnacknowledgedMessageMapImpl(500);
+ private final TxAck _op = new TxAck(_map);
+ private final List<Long> _acked;
+ private final List<Long> _unacked;
+
+ Scenario(int messageCount, List<Long> acked, List<Long> unacked) throws AMQException
+ {
+ for(int i = 0; i < messageCount; i++)
+ {
+ long deliveryTag = i + 1;
+ _messages.put(deliveryTag, new UnacknowledgedMessage(null, new TestMessage(deliveryTag), null, deliveryTag));
+ }
+ _acked = acked;
+ _unacked = unacked;
+ }
+
+ void update(long deliverytag, boolean multiple)
+ {
+ _op.update(deliverytag, multiple);
+ }
+
+ private void assertCount(List<Long> tags, int expected)
+ {
+ for(long tag : tags)
+ {
+ UnacknowledgedMessage u = _messages.get(tag);
+ assertTrue("Message not found for tag " + tag, u != null);
+ ((TestMessage) u.message).assertCountEquals(expected);
+ }
+ }
+
+ void prepare() throws AMQException
+ {
+ _op.consolidate();
+ _op.prepare();
+
+ assertCount(_acked, -1);
+ assertCount(_unacked, 0);
+
+ }
+ void undoPrepare()
+ {
+ _op.consolidate();
+ _op.undoPrepare();
+
+ assertCount(_acked, 1);
+ assertCount(_unacked, 0);
+ }
+
+ void commit()
+ {
+ _op.consolidate();
+ _op.commit();
+
+
+ //check acked messages are removed from map
+ HashSet<Long> keys = new HashSet<Long>(_messages.keySet());
+ keys.retainAll(_acked);
+ assertTrue("Expected messages with following tags to have been removed from map: " + keys, keys.isEmpty());
+ //check unacked messages are still in map
+ keys = new HashSet<Long>(_unacked);
+ keys.removeAll(_messages.keySet());
+ assertTrue("Expected messages with following tags to still be in map: " + keys, keys.isEmpty());
+ }
+ }
+
+ private class TestMessage extends AMQMessage
+ {
+ private final long _tag;
+ private int _count;
+
+ TestMessage(long tag) throws AMQException
+ {
+ super(null); // new TestableMemoryMessageStore(), null);
+ _tag = tag;
+ throw new AMQException("Fix this!");
+ }
+
+ public void incrementReference()
+ {
+ _count++;
+ }
+
+ public void decrementReference()
+ {
+ _count--;
+ }
+
+ void assertCountEquals(int expected)
+ {
+ assertEquals("Wrong count for message with tag " + _tag, expected, _count);
+ }
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/ack/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/ack/UnitTests.java
new file mode 100644
index 0000000000..3d7db01365
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/ack/UnitTests.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ TxAckTest.class
+})
+public class UnitTests
+{
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(UnitTests.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java b/java/broker/src/main/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java
new file mode 100644
index 0000000000..bd78d1c786
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java
@@ -0,0 +1,50 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.apache.qpid.configuration.PropertyException;
+import org.apache.qpid.configuration.PropertyUtils;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+// TODO: This belongs in the "common" module.
+public class TestPropertyUtils
+{
+ @Test
+ public void testSimpleExpansion() throws PropertyException
+ {
+ System.setProperty("banana", "fruity");
+ String expandedProperty = PropertyUtils.replaceProperties("${banana}");
+ assertEquals(expandedProperty, "fruity");
+ }
+
+ @Test
+ public void testDualExpansion() throws PropertyException
+ {
+ System.setProperty("banana", "fruity");
+ System.setProperty("concrete", "horrible");
+ String expandedProperty = PropertyUtils.replaceProperties("${banana}xyz${concrete}");
+ assertEquals(expandedProperty, "fruityxyzhorrible");
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(TestPropertyUtils.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/configuration/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/configuration/UnitTests.java
new file mode 100644
index 0000000000..4c70d7c4da
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/configuration/UnitTests.java
@@ -0,0 +1,32 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({TestPropertyUtils.class})
+public class UnitTests
+{
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(UnitTests.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTest.java
new file mode 100644
index 0000000000..1e9dd54d85
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTest.java
@@ -0,0 +1,232 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.*;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.SkeletonMessageStore;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.*;
+
+public class AbstractHeadersExchangeTest
+{
+ private static final Logger _log = Logger.getLogger(AbstractHeadersExchangeTest.class);
+
+ private final HeadersExchange exchange = new HeadersExchange();
+ protected final Set<TestQueue> queues = new HashSet<TestQueue>();
+ private int count;
+
+ protected TestQueue bindDefault(String... bindings) throws AMQException
+ {
+ return bind("Queue" + (++count), bindings);
+ }
+
+ protected TestQueue bind(String queueName, String... bindings) throws AMQException
+ {
+ return bind(queueName, getHeaders(bindings));
+ }
+
+ protected TestQueue bind(String queue, FieldTable bindings) throws AMQException
+ {
+ return bind(new TestQueue(queue), bindings);
+ }
+
+ protected TestQueue bind(TestQueue queue, String... bindings) throws AMQException
+ {
+ return bind(queue, getHeaders(bindings));
+ }
+
+ protected TestQueue bind(TestQueue queue, FieldTable bindings) throws AMQException
+ {
+ queues.add(queue);
+ exchange.registerQueue(null, queue, bindings);
+ return queue;
+ }
+
+
+ protected void route(Message m) throws AMQException
+ {
+ m.route(exchange);
+ }
+
+ protected void routeAndTest(Message m, TestQueue... expected) throws AMQException
+ {
+ routeAndTest(m, Arrays.asList(expected));
+ }
+
+ protected void routeAndTest(Message m, List<TestQueue> expected) throws AMQException
+ {
+ route(m);
+ for (TestQueue q : queues)
+ {
+ if (expected.contains(q))
+ {
+ assertTrue("Expected " + m + " to be delivered to " + q, m.isInQueue(q));
+ //assert m.isInQueue(q) : "Expected " + m + " to be delivered to " + q;
+ }
+ else
+ {
+ assertFalse("Did not expect " + m + " to be delivered to " + q, m.isInQueue(q));
+ //assert !m.isInQueue(q) : "Did not expect " + m + " to be delivered to " + q;
+ }
+ }
+ }
+
+ static FieldTable getHeaders(String... entries)
+ {
+ FieldTable headers = new FieldTable();
+ for (String s : entries)
+ {
+ String[] parts = s.split("=", 2);
+ headers.put(parts[0], parts.length > 1 ? parts[1] : "");
+ }
+ return headers;
+ }
+
+ static BasicPublishBody getPublishRequest(String id)
+ {
+ BasicPublishBody request = new BasicPublishBody();
+ request.routingKey = id;
+ return request;
+ }
+
+ static ContentHeaderBody getContentHeader(FieldTable headers)
+ {
+ ContentHeaderBody header = new ContentHeaderBody();
+ header.properties = getProperties(headers);
+ return header;
+ }
+
+ static BasicContentHeaderProperties getProperties(FieldTable headers)
+ {
+ BasicContentHeaderProperties properties = new BasicContentHeaderProperties();
+ properties.setHeaders(headers);
+ return properties;
+ }
+
+ static class TestQueue extends AMQQueue
+ {
+ final List<HeadersExchangeTest.Message> messages = new ArrayList<HeadersExchangeTest.Message>();
+
+ public TestQueue(String name) throws AMQException
+ {
+ super(name, false, "test", true, ApplicationRegistry.getInstance().getQueueRegistry());
+ }
+
+ public void deliver(AMQMessage msg) throws AMQException
+ {
+ messages.add(new HeadersExchangeTest.Message(msg));
+ }
+ }
+
+ /**
+ * Just add some extra utility methods to AMQMessage to aid testing.
+ */
+ static class Message extends AMQMessage
+ {
+ private static MessageStore _messageStore = new SkeletonMessageStore();
+
+ Message(String id, String... headers) throws AMQException
+ {
+ this(id, getHeaders(headers));
+ }
+
+ Message(String id, FieldTable headers) throws AMQException
+ {
+ this(getPublishRequest(id), getContentHeader(headers), null);
+ }
+
+ private Message(BasicPublishBody publish, ContentHeaderBody header, List<ContentBody> bodies) throws AMQException
+ {
+ //super(_messageStore, publish, header, bodies);
+ super(null);
+ throw new AMQException("Fix this!!!!");
+ }
+
+ private Message(AMQMessage msg) throws AMQException
+ {
+ super(msg);
+ }
+
+ void route(Exchange exchange) throws AMQException
+ {
+ exchange.route(this);
+ }
+
+ boolean isInQueue(TestQueue queue)
+ {
+ return queue.messages.contains(this);
+ }
+
+ public int hashCode()
+ {
+ try
+ {
+ return getKey().hashCode();
+ }
+ catch (AMQException e)
+ {
+ _log.error("Error getting key: " + e, e);
+ return 0;
+ }
+ }
+
+ public boolean equals(Object o)
+ {
+ return o instanceof HeadersExchangeTest.Message && equals((HeadersExchangeTest.Message) o);
+ }
+
+ private boolean equals(HeadersExchangeTest.Message m)
+ {
+ try
+ {
+ return getKey().equals(m.getKey());
+ }
+ catch (AMQException e)
+ {
+ _log.error("Error getting key: " + e, e);
+ return false;
+ }
+ }
+
+ public String toString()
+ {
+ try
+ {
+ return getKey().toString();
+ }
+ catch (AMQException e)
+ {
+ _log.error("Error getting key: " + e, e);
+ return null;
+ }
+ }
+
+ private Object getKey() throws AMQException
+ {
+ return getPublishBody().routingKey;
+ }
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java
new file mode 100644
index 0000000000..7e33b1d711
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java
@@ -0,0 +1,200 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.junit.Test;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+
+import java.util.Map;
+import java.util.HashMap;
+
+import junit.framework.JUnit4TestAdapter;
+
+/**
+ */
+public class HeadersBindingTest
+{
+ private Map<String, String> bindHeaders = new HashMap<String, String>();
+ private Map<String, String> matchHeaders = new HashMap<String, String>();
+
+ @Test public void default_1()
+ {
+ bindHeaders.put("A", "Value of A");
+
+ matchHeaders.put("A", "Value of A");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void default_2()
+ {
+ bindHeaders.put("A", "Value of A");
+
+ matchHeaders.put("A", "Value of A");
+ matchHeaders.put("B", "Value of B");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void default_3()
+ {
+ bindHeaders.put("A", "Value of A");
+
+ matchHeaders.put("A", "Altered value of A");
+
+ assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void all_1()
+ {
+ bindHeaders.put("X-match", "all");
+ bindHeaders.put("A", "Value of A");
+
+ matchHeaders.put("A", "Value of A");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void all_2()
+ {
+ bindHeaders.put("X-match", "all");
+ bindHeaders.put("A", "Value of A");
+ bindHeaders.put("B", "Value of B");
+
+ matchHeaders.put("A", "Value of A");
+
+ assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void all_3()
+ {
+ bindHeaders.put("X-match", "all");
+ bindHeaders.put("A", "Value of A");
+ bindHeaders.put("B", "Value of B");
+
+ matchHeaders.put("A", "Value of A");
+ matchHeaders.put("B", "Value of B");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void all_4()
+ {
+ bindHeaders.put("X-match", "all");
+ bindHeaders.put("A", "Value of A");
+ bindHeaders.put("B", "Value of B");
+
+ matchHeaders.put("A", "Value of A");
+ matchHeaders.put("B", "Value of B");
+ matchHeaders.put("C", "Value of C");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void all_5()
+ {
+ bindHeaders.put("X-match", "all");
+ bindHeaders.put("A", "Value of A");
+ bindHeaders.put("B", "Value of B");
+
+ matchHeaders.put("A", "Value of A");
+ matchHeaders.put("B", "Altered value of B");
+ matchHeaders.put("C", "Value of C");
+
+ assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void any_1()
+ {
+ bindHeaders.put("X-match", "any");
+ bindHeaders.put("A", "Value of A");
+
+ matchHeaders.put("A", "Value of A");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void any_2()
+ {
+ bindHeaders.put("X-match", "any");
+ bindHeaders.put("A", "Value of A");
+ bindHeaders.put("B", "Value of B");
+
+ matchHeaders.put("A", "Value of A");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void any_3()
+ {
+ bindHeaders.put("X-match", "any");
+ bindHeaders.put("A", "Value of A");
+ bindHeaders.put("B", "Value of B");
+
+ matchHeaders.put("A", "Value of A");
+ matchHeaders.put("B", "Value of B");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void any_4()
+ {
+ bindHeaders.put("X-match", "any");
+ bindHeaders.put("A", "Value of A");
+ bindHeaders.put("B", "Value of B");
+
+ matchHeaders.put("A", "Value of A");
+ matchHeaders.put("B", "Value of B");
+ matchHeaders.put("C", "Value of C");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void any_5()
+ {
+ bindHeaders.put("X-match", "any");
+ bindHeaders.put("A", "Value of A");
+ bindHeaders.put("B", "Value of B");
+
+ matchHeaders.put("A", "Value of A");
+ matchHeaders.put("B", "Altered value of B");
+ matchHeaders.put("C", "Value of C");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ @Test public void any_6()
+ {
+ bindHeaders.put("X-match", "any");
+ bindHeaders.put("A", "Value of A");
+ bindHeaders.put("B", "Value of B");
+
+ matchHeaders.put("A", "Altered value of A");
+ matchHeaders.put("B", "Altered value of B");
+ matchHeaders.put("C", "Value of C");
+
+ assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(HeadersBindingTest.class);
+ }
+
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangePerformanceTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangePerformanceTest.java
new file mode 100644
index 0000000000..74cb082db7
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangePerformanceTest.java
@@ -0,0 +1,181 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.NoConsumersException;
+import org.apache.qpid.server.util.TimedRun;
+import org.apache.qpid.server.util.AveragedRun;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.ContentBody;
+
+import java.util.List;
+
+/**
+ * Want to vary the number of regsitrations, messages and matches and measure
+ * the corresponding variance in execution time.
+ * <p/>
+ * Each registration will contain the 'All' header, even registrations will
+ * contain the 'Even' header and odd headers will contain the 'Odd' header.
+ * In additions each regsitration will have a unique value for the 'Specific'
+ * header as well.
+ * <p/>
+ * Messages can then be routed to all registrations, to even- or odd- registrations
+ * or to a specific registration.
+ *
+ */
+public class HeadersExchangePerformanceTest extends AbstractHeadersExchangeTest
+{
+ private static enum Mode {ALL, ODD_OR_EVEN, SPECIFIC}
+
+ private final TestQueue[] queues;
+ private final Mode mode;
+
+ public HeadersExchangePerformanceTest(Mode mode, int registrations) throws AMQException
+ {
+ this.mode = mode;
+ queues = new TestQueue[registrations];
+ for (int i = 0; i < queues.length; i++)
+ {
+ switch(mode)
+ {
+ case ALL:
+ queues[i] = bind(new FastQueue("Queue" + i), "All");
+ break;
+ case ODD_OR_EVEN:
+ queues[i] = bind(new FastQueue("Queue" + i), "All", oddOrEven(i));
+ break;
+ case SPECIFIC:
+ queues[i] = bind(new FastQueue("Queue" + i), "All", oddOrEven(i), "Specific"+ i);
+ break;
+ }
+ }
+ }
+
+ void sendToAll(int count) throws AMQException
+ {
+ send(count, "All=True");
+ }
+
+ void sendToOdd(int count) throws AMQException
+ {
+ send(count, "All=True", "Odd=True");
+ }
+
+ void sendToEven(int count) throws AMQException
+ {
+ send(count, "All=True", "Even=True");
+ }
+
+ void sendToAllSpecifically(int count) throws AMQException
+ {
+ for (int i = 0; i < queues.length; i++)
+ {
+ sendToSpecific(count, i);
+ }
+ }
+
+ void sendToSpecific(int count, int index) throws AMQException
+ {
+ send(count, "All=True", oddOrEven(index) + "=True", "Specific=" + index);
+ }
+
+ private void send(int count, String... headers) throws AMQException
+ {
+ for (int i = 0; i < count; i++)
+ {
+ route(new Message("Message" + i, headers));
+ }
+ }
+
+ private static String oddOrEven(int i)
+ {
+ return (i % 2 == 0 ? "Even" : "Odd");
+ }
+
+ static class FastQueue extends TestQueue
+ {
+
+ public FastQueue(String name) throws AMQException
+ {
+ super(name);
+ }
+
+ public void deliver(BasicPublishBody publishBody, ContentHeaderBody contentHeaderBody, List<ContentBody> contentBodies) throws NoConsumersException
+ {
+ //just discard as we are not testing routing functionality here
+ }
+ }
+
+ static class Test extends TimedRun
+ {
+ private final Mode mode;
+ private final int registrations;
+ private final int count;
+ private HeadersExchangePerformanceTest test;
+
+ Test(Mode mode, int registrations, int count)
+ {
+ super(mode + ", registrations=" + registrations + ", count=" + count);
+ this.mode = mode;
+ this.registrations = registrations;
+ this.count = count;
+ }
+
+ protected void setup() throws Exception
+ {
+ test = new HeadersExchangePerformanceTest(mode, registrations);
+ run(100); //do a warm up run before times start
+ }
+
+ protected void teardown() throws Exception
+ {
+ test = null;
+ System.gc();
+ }
+
+ protected void run() throws Exception
+ {
+ run(count);
+ }
+
+ private void run(int count) throws Exception
+ {
+ switch(mode)
+ {
+ case ALL:
+ test.sendToAll(count);
+ break;
+ default:
+ System.out.println("Test for " + mode + " not yet implemented.");
+ }
+ }
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ int registrations = Integer.parseInt(argv[0]);
+ int messages = Integer.parseInt(argv[1]);
+ int iterations = Integer.parseInt(argv[2]);
+ TimedRun test = new Test(Mode.ALL, registrations, messages);
+ AveragedRun tests = new AveragedRun(test, iterations);
+ System.out.println(tests.call());
+ }
+}
+
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java
new file mode 100644
index 0000000000..1c594d4374
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java
@@ -0,0 +1,81 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.junit.Test;
+import org.junit.Before;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.util.TestApplicationRegistry;
+import junit.framework.JUnit4TestAdapter;
+
+public class HeadersExchangeTest extends AbstractHeadersExchangeTest
+{
+ @Before
+ public void init() throws Exception
+ {
+ ApplicationRegistry.initialise(new TestApplicationRegistry());
+ }
+
+ @Test
+ public void simple() throws AMQException
+ {
+ TestQueue q1 = bindDefault("F0000");
+ TestQueue q2 = bindDefault("F0000=Aardvark");
+ TestQueue q3 = bindDefault("F0001");
+ TestQueue q4 = bindDefault("F0001=Bear");
+ TestQueue q5 = bindDefault("F0000", "F0001");
+ TestQueue q6 = bindDefault("F0000=Aardvark", "F0001=Bear");
+ TestQueue q7 = bindDefault("F0000", "F0001=Bear");
+ TestQueue q8 = bindDefault("F0000=Aardvark", "F0001");
+ TestQueue q9 = bindDefault("F0000=Apple", "F0001=Banana");
+ TestQueue q10 = bindDefault("F0000=Apple", "F0001");
+
+ routeAndTest(new Message("Message1", "F0000"), q1);
+ routeAndTest(new Message("Message2", "F0000=Aardvark"), q1, q2);
+ routeAndTest(new Message("Message3", "F0000=Aardvark", "F0001"), q1, q2, q3, q5, q8);
+ routeAndTest(new Message("Message4", "F0000", "F0001=Bear"), q1, q3, q4, q5, q7);
+ routeAndTest(new Message("Message5", "F0000=Aardvark", "F0001=Bear"),
+ q1, q2, q3, q4, q5, q6, q7, q8);
+ routeAndTest(new Message("Message6", "F0002"));
+ }
+
+ @Test
+ public void any() throws AMQException
+ {
+ TestQueue q1 = bindDefault("F0000", "F0001", "X-match=any");
+ TestQueue q2 = bindDefault("F0000=Aardvark", "F0001=Bear", "X-match=any");
+ TestQueue q3 = bindDefault("F0000", "F0001=Bear", "X-match=any");
+ TestQueue q4 = bindDefault("F0000=Aardvark", "F0001", "X-match=any");
+ TestQueue q5 = bindDefault("F0000=Apple", "F0001=Banana", "X-match=any");
+ TestQueue q6 = bindDefault("F0000=Apple", "F0001", "X-match=any");
+
+ routeAndTest(new Message("Message1", "F0000"), q1, q3);
+ routeAndTest(new Message("Message2", "F0000=Aardvark"), q1, q2, q3, q4);
+ routeAndTest(new Message("Message3", "F0000=Aardvark", "F0001"), q1, q2, q3, q4, q6);
+ routeAndTest(new Message("Message4", "F0000", "F0001=Bear"), q1, q2, q3, q4, q6);
+ routeAndTest(new Message("Message5", "F0000=Aardvark", "F0001=Bear"), q1, q2, q3, q4, q6);
+ routeAndTest(new Message("Message6", "F0002"));
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(HeadersExchangeTest.class);
+ }
+
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/exchange/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/UnitTests.java
new file mode 100644
index 0000000000..a3c6439b67
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/exchange/UnitTests.java
@@ -0,0 +1,32 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({HeadersBindingTest.class, HeadersExchangeTest.class})
+public class UnitTests
+{
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(UnitTests.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/protocol/MockIoSession.java b/java/broker/src/main/test/java/org/apache/qpid/server/protocol/MockIoSession.java
new file mode 100644
index 0000000000..f985050e9f
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/protocol/MockIoSession.java
@@ -0,0 +1,293 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.mina.common.*;
+import org.apache.mina.common.support.DefaultCloseFuture;
+import org.apache.mina.common.support.DefaultWriteFuture;
+
+import java.net.SocketAddress;
+import java.util.Set;
+
+public class MockIoSession implements IoSession
+{
+ private AMQProtocolSession _protocolSession;
+
+ /**
+ * Stores the last response written
+ */
+ private Object _lastWrittenObject;
+
+ private boolean _closing;
+
+ public MockIoSession()
+ {
+ }
+
+ public Object getLastWrittenObject()
+ {
+ return _lastWrittenObject;
+ }
+
+ public IoService getService()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public IoServiceConfig getServiceConfig()
+ {
+ return null;
+ }
+
+ public IoHandler getHandler()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public IoSessionConfig getConfig()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public IoFilterChain getFilterChain()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public WriteFuture write(Object message)
+ {
+ WriteFuture wf = new DefaultWriteFuture(null);
+ _lastWrittenObject = message;
+ return wf;
+ }
+
+ public CloseFuture close()
+ {
+ _closing = true;
+ CloseFuture cf = new DefaultCloseFuture(null);
+ cf.setClosed();
+ return cf;
+ }
+
+ public Object getAttachment()
+ {
+ return _protocolSession;
+ }
+
+ public Object setAttachment(Object attachment)
+ {
+ Object current = _protocolSession;
+ _protocolSession = (AMQProtocolSession) attachment;
+ return current;
+ }
+
+ public Object getAttribute(String key)
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public Object setAttribute(String key, Object value)
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public Object setAttribute(String key)
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public Object removeAttribute(String key)
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean containsAttribute(String key)
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public Set getAttributeKeys()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public TransportType getTransportType()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isConnected()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isClosing()
+ {
+ return _closing;
+ }
+
+ public CloseFuture getCloseFuture()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public SocketAddress getRemoteAddress()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public SocketAddress getLocalAddress()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public SocketAddress getServiceAddress()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public int getIdleTime(IdleStatus status)
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getIdleTimeInMillis(IdleStatus status)
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setIdleTime(IdleStatus status, int idleTime)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public int getWriteTimeout()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getWriteTimeoutInMillis()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setWriteTimeout(int writeTimeout)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public TrafficMask getTrafficMask()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setTrafficMask(TrafficMask trafficMask)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void suspendRead()
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void suspendWrite()
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void resumeRead()
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void resumeWrite()
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getReadBytes()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getWrittenBytes()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getReadMessages()
+ {
+ return 0L;
+ }
+
+ public long getWrittenMessages()
+ {
+ return 0L;
+ }
+
+ public long getWrittenWriteRequests()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public int getScheduledWriteRequests()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public int getScheduledWriteBytes()
+ {
+ return 0; //TODO
+ }
+
+ public long getCreationTime()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getLastIoTime()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getLastReadTime()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getLastWriteTime()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isIdle(IdleStatus status)
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public int getIdleCount(IdleStatus status)
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getLastIdleTime(IdleStatus status)
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/protocol/TestProtocolInitiation.java b/java/broker/src/main/test/java/org/apache/qpid/server/protocol/TestProtocolInitiation.java
new file mode 100644
index 0000000000..34e1709a2d
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/protocol/TestProtocolInitiation.java
@@ -0,0 +1,212 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.Assert;
+import junit.framework.JUnit4TestAdapter;
+import org.apache.qpid.codec.AMQDecoder;
+import org.apache.qpid.codec.AMQEncoder;
+import org.apache.qpid.framing.*;
+import org.apache.mina.common.ByteBuffer;
+import org.apache.mina.common.WriteFuture;
+import org.apache.mina.filter.codec.ProtocolDecoderOutput;
+import org.apache.mina.filter.codec.ProtocolEncoderOutput;
+import org.apache.mina.filter.codec.support.SimpleProtocolDecoderOutput;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * This test suite tests the handling of protocol initiation frames and related issues.
+ */
+public class TestProtocolInitiation implements ProtocolVersionList
+{
+ private AMQPFastProtocolHandler _protocolHandler;
+
+ private MockIoSession _mockIoSession;
+
+ /**
+ * We need to use the object encoder mechanism so to allow us to retrieve the
+ * output (a bytebuffer) we define our own encoder output class. The encoder
+ * writes the encoded data to this class, from where we can retrieve it during
+ * the test run.
+ */
+ private class TestProtocolEncoderOutput implements ProtocolEncoderOutput
+ {
+ public ByteBuffer result;
+
+ public void write(ByteBuffer buf)
+ {
+ result = buf;
+ }
+
+ public void mergeAll()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public WriteFuture flush()
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private class TestProtocolDecoderOutput implements ProtocolDecoderOutput
+ {
+ public Object result;
+
+ public void write(Object buf)
+ {
+ result = buf;
+ }
+
+ public void flush()
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Before
+ public void createCommonObjects()
+ {
+ _mockIoSession = new MockIoSession();
+ _protocolHandler = new AMQPFastProtocolHandler(null, null);
+ }
+
+
+ /**
+ * Tests that the AMQDecoder handles invalid protocol classes
+ * @throws Exception
+ */
+ @Test(expected = AMQProtocolClassException.class)
+ public void testDecoderValidateProtocolClass() throws Exception
+ {
+ ProtocolInitiation pi = createValidProtocolInitiation();
+ pi.protocolClass = 2;
+ decodePI(pi);
+ }
+
+ /**
+ * Tests that the AMQDecoder handles invalid protocol instance numbers
+ * @throws Exception
+ */
+ @Test(expected = AMQProtocolInstanceException.class)
+ public void testDecoderValidatesProtocolInstance() throws Exception
+ {
+ ProtocolInitiation pi = createValidProtocolInitiation();
+ pi.protocolInstance = 2;
+ decodePI(pi);
+ }
+
+ /**
+ * Tests that the AMQDecoder handles invalid protocol major
+ * @throws Exception
+ */
+ @Test(expected = AMQProtocolVersionException.class)
+ public void testDecoderValidatesProtocolMajor() throws Exception
+ {
+ ProtocolInitiation pi = createValidProtocolInitiation();
+ pi.protocolMajor = 2;
+ decodePI(pi);
+ }
+
+ /**
+ * Tests that the AMQDecoder handles invalid protocol minor
+ * @throws Exception
+ */
+ @Test(expected = AMQProtocolVersionException.class)
+ public void testDecoderValidatesProtocolMinor() throws Exception
+ {
+ ProtocolInitiation pi = createValidProtocolInitiation();
+ pi.protocolMinor = 99;
+ decodePI(pi);
+ }
+
+ /**
+ * Tests that the AMQDecoder accepts a valid PI
+ * @throws Exception
+ */
+ @Test(expected = AMQProtocolHeaderException.class)
+ public void testDecoderValidatesHeader() throws Exception
+ {
+ ProtocolInitiation pi = createValidProtocolInitiation();
+ pi.header = new char[] {'P', 'Q', 'M', 'A' };
+ decodePI(pi);
+ }
+
+ /**
+ * Test that a valid header is passed by the decoder.
+ * @throws Exception
+ */
+ @Test
+ public void testDecoderAcceptsValidHeader() throws Exception
+ {
+ ProtocolInitiation pi = createValidProtocolInitiation();
+ decodePI(pi);
+ }
+
+ /**
+ * This test checks that an invalid protocol header results in the
+ * connection being closed.
+ */
+ @Test
+ public void testInvalidProtocolHeaderClosesConnection() throws Exception
+ {
+ AMQProtocolHeaderException pe = new AMQProtocolHeaderException("Test");
+ _protocolHandler.exceptionCaught(_mockIoSession, pe);
+ Assert.assertNotNull(_mockIoSession.getLastWrittenObject());
+ Object piResponse = _mockIoSession.getLastWrittenObject();
+ Assert.assertEquals(piResponse.getClass(), ProtocolInitiation.class);
+ ProtocolInitiation pi = (ProtocolInitiation) piResponse;
+ Assert.assertEquals("Protocol Initiation sent out was not the broker's expected header", pi,
+ createValidProtocolInitiation());
+ Assert.assertTrue("Session has not been closed", _mockIoSession.isClosing());
+ }
+
+ private ProtocolInitiation createValidProtocolInitiation()
+ {
+ /* 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;
+ return new ProtocolInitiation(pv[i][PROTOCOL_MAJOR], pv[i][PROTOCOL_MINOR]);
+ }
+
+ /**
+ * Helper that encodes a protocol initiation and attempts to decode it
+ * @param pi
+ * @throws Exception
+ */
+ private void decodePI(ProtocolInitiation pi) throws Exception
+ {
+ // we need to do this test at the level of the decoder since we initially only expect PI frames
+ // so the protocol handler is not set up to know whether it should be expecting a PI frame or
+ // a different type of frame
+ AMQDecoder decoder = new AMQDecoder(true);
+ AMQEncoder encoder = new AMQEncoder();
+ TestProtocolEncoderOutput peo = new TestProtocolEncoderOutput();
+ encoder.encode(_mockIoSession, pi, peo);
+ TestProtocolDecoderOutput pdo = new TestProtocolDecoderOutput();
+ decoder.decode(_mockIoSession, peo.result, pdo);
+ ((ProtocolInitiation) pdo.result).checkVersion(this);
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(TestProtocolInitiation.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/protocol/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/protocol/UnitTests.java
new file mode 100644
index 0000000000..09dc76d310
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/protocol/UnitTests.java
@@ -0,0 +1,32 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({TestProtocolInitiation.class})
+public class UnitTests
+{
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(UnitTests.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/AckTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/AckTest.java
new file mode 100644
index 0000000000..a76dac7263
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/AckTest.java
@@ -0,0 +1,268 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.ack.UnacknowledgedMessage;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.store.TestableMemoryMessageStore;
+import org.apache.qpid.server.util.TestApplicationRegistry;
+import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Tests that acknowledgements are handled correctly.
+ */
+public class AckTest
+{
+ private static final Logger _log = Logger.getLogger(AckTest.class);
+
+ private SubscriptionImpl _subscription;
+
+ private MockProtocolSession _protocolSession;
+
+ private TestableMemoryMessageStore _messageStore;
+
+ private AMQChannel _channel;
+
+ private SubscriptionSet _subscriptionManager;
+
+ private AMQQueue _queue;
+
+ public AckTest() throws Exception
+ {
+ ApplicationRegistry.initialise(new TestApplicationRegistry());
+ }
+
+ @Before
+ public void setup() throws Exception
+ {
+ _messageStore = new TestableMemoryMessageStore();
+ _channel = new AMQChannel(5, _messageStore, null/*dont need exchange registry*/);
+ _protocolSession = new MockProtocolSession(_messageStore);
+ _protocolSession.addChannel(_channel);
+ _subscriptionManager = new SubscriptionSet();
+ _queue = new AMQQueue("myQ", false, "guest", true, new DefaultQueueRegistry(), _subscriptionManager);
+ }
+
+ private void publishMessages(int count) throws AMQException
+ {
+ for (int i = 1; i <= count; i++)
+ {
+ BasicPublishBody publishBody = new BasicPublishBody();
+ publishBody.routingKey = "rk";
+ publishBody.exchange = "someExchange";
+ AMQMessage msg = null; //new AMQMessage(_messageStore, publishBody);
+ msg.setContentHeaderBody(new ContentHeaderBody());
+ _subscription.send(msg, _queue);
+ }
+ }
+
+ /**
+ * Tests that the acknowledgements are correctly associated with a channel and
+ * order is preserved when acks are enabled
+ */
+ @Test @Ignore /* FIXME: broken at the moment */
+ public void ackChannelAssociationTest() throws AMQException
+ {
+ _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true);
+ final int msgCount = 10;
+ publishMessages(msgCount);
+
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == msgCount);
+
+ map.visit(new UnacknowledgedMessageMap.Visitor()
+ {
+ private int i = 1;
+
+ public boolean callback(UnacknowledgedMessage message) throws AMQException
+ {
+ assertTrue(message.deliveryTag == i++);
+ assertTrue(message.queue == _queue);
+ return false;
+ }
+
+ public void visitComplete()
+ {
+ }
+ });
+
+ assertTrue(_messageStore.getMessageMetaDataMap().size() == msgCount);
+ }
+
+ /**
+ * Tests that in no-ack mode no messages are retained
+ */
+ @Test
+ public void testNoAckMode() throws AMQException
+ {
+ // false arg means no acks expected
+ _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", false);
+ final int msgCount = 10;
+ publishMessages(msgCount);
+
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == 0);
+ assertTrue(_messageStore.getMessageMetaDataMap().size() == 0);
+ }
+
+ /**
+ * Tests that a single acknowledgement is handled correctly (i.e multiple flag not
+ * set case)
+ */
+ @Test
+ public void singleAckReceivedTest() throws AMQException
+ {
+ _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true);
+ final int msgCount = 10;
+ publishMessages(msgCount);
+
+ _channel.acknowledgeMessage(5, false);
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == msgCount - 1);
+
+ map.visit(new UnacknowledgedMessageMap.Visitor()
+ {
+ private int i = 1;
+
+ public boolean callback(UnacknowledgedMessage message) throws AMQException
+ {
+ assertTrue(message.deliveryTag == i);
+ assertTrue(message.queue == _queue);
+ // 5 is the delivery tag of the message that *should* be removed
+ if (++i == 5)
+ {
+ ++i;
+ }
+ return false;
+ }
+
+ public void visitComplete()
+ {
+ }
+ });
+ }
+
+ /**
+ * Tests that a single acknowledgement is handled correctly (i.e multiple flag not
+ * set case)
+ */
+ @Test
+ public void multiAckReceivedTest() throws AMQException
+ {
+ _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true);
+ final int msgCount = 10;
+ publishMessages(msgCount);
+
+ _channel.acknowledgeMessage(5, true);
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == 5);
+
+ map.visit(new UnacknowledgedMessageMap.Visitor()
+ {
+ private int i = 1;
+
+ public boolean callback(UnacknowledgedMessage message) throws AMQException
+ {
+ assertTrue(message.deliveryTag == i + 5);
+ assertTrue(message.queue == _queue);
+ ++i;
+ return false;
+ }
+
+ public void visitComplete()
+ {
+ }
+ });
+ }
+
+ /**
+ * Tests that a multiple acknowledgement is handled correctly. When ack'ing all pending msgs.
+ *
+ */
+ @Test
+ public void multiAckAllReceivedTest() throws AMQException
+ {
+ _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true);
+ final int msgCount = 10;
+ publishMessages(msgCount);
+
+ _channel.acknowledgeMessage(0, true);
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == 0);
+
+ map.visit(new UnacknowledgedMessageMap.Visitor()
+ {
+ private int i = 1;
+
+ public boolean callback(UnacknowledgedMessage message) throws AMQException
+ {
+ assertTrue(message.deliveryTag == i + 5);
+ assertTrue(message.queue == _queue);
+ ++i;
+ return false;
+ }
+
+ public void visitComplete()
+ {
+ }
+ });
+ }
+
+
+
+ @Test
+ public void testPrefetch() throws AMQException
+ {
+ _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true);
+ _channel.setPrefetchCount(5);
+ final int msgCount = 5;
+ publishMessages(msgCount);
+
+ // at this point we should have sent out only 5 messages with a further 5 queued
+ // up in the channel which should be suspended
+ assertTrue(_subscription.isSuspended());
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == 5);
+ _channel.acknowledgeMessage(5, true);
+ assertTrue(!_subscription.isSuspended());
+ try
+ {
+ Thread.sleep(3000);
+ }
+ catch (InterruptedException e)
+ {
+ _log.error("Error: " + e, e);
+ }
+ assertTrue(map.size() == 0);
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(AckTest.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/ConcurrencyTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/ConcurrencyTest.java
new file mode 100644
index 0000000000..1cf11933fa
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/ConcurrencyTest.java
@@ -0,0 +1,261 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import org.junit.Test;
+import org.junit.Assert;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.handler.OnCurrentThreadExecutor;
+
+import java.util.*;
+import java.util.concurrent.Executor;
+
+/**
+ * Tests delivery in the face of concurrent incoming _messages, subscription alterations
+ * and attempts to asynchronously process queued _messages.
+ */
+public class ConcurrencyTest extends MessageTestHelper
+{
+ private final Random random = new Random();
+
+ private final int numMessages = 1000;
+
+ private final List<TestSubscription> _subscribers = new ArrayList<TestSubscription>();
+ private final Set<Subscription> _active = new HashSet<Subscription>();
+ private final List<AMQMessage> _messages = new ArrayList<AMQMessage>();
+ private int next = 0;//index to next message to send
+ private final List<AMQMessage> _received = Collections.synchronizedList(new ArrayList<AMQMessage>());
+ private final Executor _executor = new OnCurrentThreadExecutor();
+ private final List<Thread> _threads = new ArrayList<Thread>();
+
+ private final SubscriptionSet _subscriptionMgr = new SubscriptionSet();
+ private final DeliveryManager _deliveryMgr;
+
+ private boolean isComplete;
+ private boolean failed;
+
+ public ConcurrencyTest() throws Exception
+ {
+ _deliveryMgr = new DeliveryManager(_subscriptionMgr, new AMQQueue("myQ", false, "guest", false,
+ new DefaultQueueRegistry()));
+ }
+
+ @Test
+ public void concurrent1() throws InterruptedException, AMQException
+ {
+ initSubscriptions(10);
+ initMessages(numMessages);
+ initThreads(1, 4, 4, 4);
+ run();
+ check();
+ }
+
+ @Test
+ public void concurrent2() throws InterruptedException, AMQException
+ {
+ initSubscriptions(10);
+ initMessages(numMessages);
+ initThreads(4, 2, 2, 2);
+ run();
+ check();
+ }
+
+ void check()
+ {
+ assertFalse("Failed", failed);
+
+ _deliveryMgr.processAsync(_executor);
+
+ assertEquals("Did not recieve the correct number of messages", _messages.size(), _received.size());
+ for(int i = 0; i < _messages.size(); i++)
+ {
+ assertEquals("Wrong message at " + i, _messages.get(i), _received.get(i));
+ }
+ }
+
+ void initSubscriptions(int subscriptions)
+ {
+ for(int i = 0; i < subscriptions; i++)
+ {
+ _subscribers.add(new TestSubscription("Subscriber" + i, _received));
+ }
+ }
+
+ void initMessages(int messages) throws AMQException
+ {
+ for(int i = 0; i < messages; i++)
+ {
+ _messages.add(message());
+ }
+ }
+
+ void initThreads(int senders, int subscribers, int suspenders, int processors)
+ {
+ addThreads(senders, senders == 1 ? new Sender() : new OrderedSender());
+ addThreads(subscribers, new Subscriber());
+ addThreads(suspenders, new Suspender());
+ addThreads(processors, new Processor());
+ }
+
+ void addThreads(int count, Runnable runner)
+ {
+ for(int i = 0; i < count; i++)
+ {
+ _threads.add(new Thread(runner, runner.toString()));
+ }
+ }
+
+ void run() throws InterruptedException
+ {
+ for(Thread t : _threads)
+ {
+ t.start();
+ }
+
+ for(Thread t : _threads)
+ {
+ t.join();
+ }
+ }
+
+ private void toggle(Subscription s)
+ {
+ synchronized (_active)
+ {
+ if (_active.contains(s))
+ {
+ _active.remove(s);
+ Subscription result = _subscriptionMgr.removeSubscriber(s);
+ Assert.assertTrue("Removed subscription " + result + " but trying to remove subscription " + s,
+ result != null && result.equals(s));
+ }
+ else
+ {
+ _active.add(s);
+ _subscriptionMgr.addSubscriber(s);
+ }
+ }
+ }
+
+ private AMQMessage nextMessage()
+ {
+ synchronized (_messages)
+ {
+ if (next < _messages.size())
+ {
+ return _messages.get(next++);
+ }
+ else
+ {
+ if (_deliveryMgr.getQueueMessageCount() == 0) {
+ isComplete = true;
+ }
+ return null;
+ }
+ }
+ }
+
+ private boolean randomBoolean()
+ {
+ return random.nextBoolean();
+ }
+
+ private TestSubscription randomSubscriber()
+ {
+ return _subscribers.get(random.nextInt(_subscribers.size()));
+ }
+
+ private class Sender extends Runner
+ {
+ void doRun() throws Throwable
+ {
+ AMQMessage msg = nextMessage();
+ if (msg != null)
+ {
+ _deliveryMgr.deliver(toString(), msg);
+ }
+ }
+ }
+
+ private class OrderedSender extends Sender
+ {
+ synchronized void doRun() throws Throwable
+ {
+ super.doRun();
+ }
+ }
+
+ private class Suspender extends Runner
+ {
+ void doRun() throws Throwable
+ {
+ randomSubscriber().setSuspended(randomBoolean());
+ }
+ }
+
+ private class Subscriber extends Runner
+ {
+ void doRun() throws Throwable
+ {
+ toggle(randomSubscriber());
+ }
+ }
+
+ private class Processor extends Runner
+ {
+ void doRun() throws Throwable
+ {
+ _deliveryMgr.processAsync(_executor);
+ }
+ }
+
+ private abstract class Runner implements Runnable
+ {
+ public void run()
+ {
+ try
+ {
+ while (!stop())
+ {
+ doRun();
+ }
+ }
+ catch (Throwable t)
+ {
+ failed = true;
+ t.printStackTrace();
+ }
+ }
+
+ abstract void doRun() throws Throwable;
+
+ boolean stop()
+ {
+ return isComplete || failed;
+ }
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(ConcurrencyTest.class);
+ }
+
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/DeliveryManagerTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/DeliveryManagerTest.java
new file mode 100644
index 0000000000..ef287e079b
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/DeliveryManagerTest.java
@@ -0,0 +1,161 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.apache.qpid.server.handler.OnCurrentThreadExecutor;
+import org.apache.qpid.AMQException;
+import junit.framework.JUnit4TestAdapter;
+
+public class DeliveryManagerTest extends MessageTestHelper
+{
+ private final SubscriptionSet _subscriptions = new SubscriptionSet();
+ private final DeliveryManager _mgr;
+
+ public DeliveryManagerTest() throws Exception
+ {
+ try
+ {
+ _mgr = new DeliveryManager(_subscriptions, new AMQQueue("myQ", false, "guest", false,
+ new DefaultQueueRegistry()));
+ }
+ catch(Throwable t)
+ {
+ t.printStackTrace();
+ throw new AMQException("Could not initialise delivery manager", t);
+ }
+ }
+
+ @Test
+ public void startInQueueingMode() throws AMQException
+ {
+ AMQMessage[] messages = new AMQMessage[10];
+ for(int i = 0; i < messages.length; i++)
+ {
+ messages[i] = message();
+ }
+ int batch = messages.length / 2;
+
+ for(int i = 0; i < batch; i++)
+ {
+ _mgr.deliver("Me", messages[i]);
+ }
+
+ TestSubscription s1 = new TestSubscription("1");
+ TestSubscription s2 = new TestSubscription("2");
+ _subscriptions.addSubscriber(s1);
+ _subscriptions.addSubscriber(s2);
+
+ for(int i = batch; i < messages.length; i++)
+ {
+ _mgr.deliver("Me", messages[i]);
+ }
+
+ assertTrue(s1.getMessages().isEmpty());
+ assertTrue(s2.getMessages().isEmpty());
+
+ _mgr.processAsync(new OnCurrentThreadExecutor());
+
+ assertEquals(messages.length / 2, s1.getMessages().size());
+ assertEquals(messages.length / 2, s2.getMessages().size());
+
+ for(int i = 0; i < messages.length; i++)
+ {
+ if(i % 2 == 0)
+ {
+ assertTrue(s1.getMessages().get(i / 2) == messages[i]);
+ }
+ else
+ {
+ assertTrue(s2.getMessages().get(i / 2) == messages[i]);
+ }
+ }
+ }
+
+ @Test
+ public void startInDirectMode() throws AMQException
+ {
+ AMQMessage[] messages = new AMQMessage[10];
+ for(int i = 0; i < messages.length; i++)
+ {
+ messages[i] = message();
+ }
+ int batch = messages.length / 2;
+
+ TestSubscription s1 = new TestSubscription("1");
+ _subscriptions.addSubscriber(s1);
+
+ for(int i = 0; i < batch; i++)
+ {
+ _mgr.deliver("Me", messages[i]);
+ }
+
+ assertEquals(batch, s1.getMessages().size());
+ for(int i = 0; i < batch; i++)
+ {
+ assertTrue(messages[i] == s1.getMessages().get(i));
+ }
+ s1.getMessages().clear();
+ assertEquals(0, s1.getMessages().size());
+
+ s1.setSuspended(true);
+ for(int i = batch; i < messages.length; i++)
+ {
+ _mgr.deliver("Me", messages[i]);
+ }
+
+ _mgr.processAsync(new OnCurrentThreadExecutor());
+ assertEquals(0, s1.getMessages().size());
+ s1.setSuspended(false);
+
+ _mgr.processAsync(new OnCurrentThreadExecutor());
+ assertEquals(messages.length - batch, s1.getMessages().size());
+
+ for(int i = batch; i < messages.length; i++)
+ {
+ assertTrue(messages[i] == s1.getMessages().get(i - batch));
+ }
+
+ }
+
+ @Test (expected=NoConsumersException.class)
+ public void noConsumers() throws AMQException
+ {
+ AMQMessage msg = message(true);
+ _mgr.deliver("Me", msg);
+ msg.checkDeliveredToConsumer();
+ }
+
+ @Test (expected=NoConsumersException.class)
+ public void noActiveConsumers() throws AMQException
+ {
+ TestSubscription s = new TestSubscription("A");
+ _subscriptions.addSubscriber(s);
+ s.setSuspended(true);
+ AMQMessage msg = message(true);
+ _mgr.deliver("Me", msg);
+ msg.checkDeliveredToConsumer();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(DeliveryManagerTest.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/MessageTestHelper.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/MessageTestHelper.java
new file mode 100644
index 0000000000..483cbedb58
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/MessageTestHelper.java
@@ -0,0 +1,50 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.framing.BasicPublishBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.SkeletonMessageStore;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.util.TestApplicationRegistry;
+import org.apache.qpid.AMQException;
+
+class MessageTestHelper
+{
+ private final MessageStore _messageStore = new SkeletonMessageStore();
+
+ MessageTestHelper() throws Exception
+ {
+ ApplicationRegistry.initialise(new TestApplicationRegistry());
+ }
+
+ AMQMessage message() throws AMQException
+ {
+ return message(false);
+ }
+
+ AMQMessage message(boolean immediate) throws AMQException
+ {
+ BasicPublishBody publish = new BasicPublishBody();
+ publish.immediate = immediate;
+ //return new AMQMessage(_messageStore, publish, new ContentHeaderBody(), null);
+ throw new AMQException("Need to fix this!!!");
+ }
+
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/MockProtocolSession.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/MockProtocolSession.java
new file mode 100644
index 0000000000..f26d6d64b3
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/MockProtocolSession.java
@@ -0,0 +1,121 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.qpid.framing.AMQDataBlock;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.store.MessageStore;
+
+import javax.security.sasl.SaslServer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A protocol session that can be used for testing purposes.
+ */
+public class MockProtocolSession implements AMQProtocolSession
+{
+ private MessageStore _messageStore;
+
+ private Map<Integer, AMQChannel> _channelMap = new HashMap<Integer, AMQChannel>();
+
+ public MockProtocolSession(MessageStore messageStore)
+ {
+ _messageStore = messageStore;
+ }
+
+ public void dataBlockReceived(AMQDataBlock message) throws Exception
+ {
+ }
+
+ public void writeFrame(AMQDataBlock frame)
+ {
+ }
+
+ public String getContextKey()
+ {
+ return null;
+ }
+
+ public void setContextKey(String contextKey)
+ {
+ }
+
+ public AMQChannel getChannel(int channelId)
+ {
+ AMQChannel channel = _channelMap.get(channelId);
+ if (channel == null)
+ {
+ throw new IllegalArgumentException("Invalid channel id: " + channelId);
+ }
+ else
+ {
+ return channel;
+ }
+ }
+
+ public void addChannel(AMQChannel channel)
+ {
+ if (channel == null)
+ {
+ throw new IllegalArgumentException("Channel must not be null");
+ }
+ else
+ {
+ _channelMap.put(channel.getChannelId(), channel);
+ }
+ }
+
+ public void closeChannel(int channelId) throws AMQException
+ {
+ }
+
+ public void removeChannel(int channelId)
+ {
+ _channelMap.remove(channelId);
+ }
+
+ public void initHeartbeats(int delay)
+ {
+ }
+
+ public void closeSession() throws AMQException
+ {
+ }
+
+ public Object getKey()
+ {
+ return null;
+ }
+
+ public String getLocalFQDN()
+ {
+ return null;
+ }
+
+ public SaslServer getSaslServer()
+ {
+ return null;
+ }
+
+ public void setSaslServer(SaslServer saslServer)
+ {
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/QueueConcurrentPerfTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/QueueConcurrentPerfTest.java
new file mode 100644
index 0000000000..8ae8ebae79
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/QueueConcurrentPerfTest.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.util.AveragedRun;
+import org.apache.qpid.server.util.ConcurrentTest;
+
+public class QueueConcurrentPerfTest extends QueuePerfTest
+{
+ QueueConcurrentPerfTest(Factory factory, int queueCount, int messages)
+ {
+ super(factory, queueCount, messages);
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ Factory[] factories = new Factory[]{SYNCHRONIZED, CONCURRENT};
+ int iterations = 5;
+ String label = argv.length > 0 ? argv[0]: null;
+ System.out.println((label == null ? "" : "Label, ") + "Queue Type, No. of Queues, No. of Operations, Avg Time, Min Time, Max Time");
+ //vary number of queues:
+ for(Factory f : factories)
+ {
+ run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 100, 10000), iterations), 5));
+ run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 1000, 10000), iterations), 5));
+ run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 10000, 10000), iterations), 5));
+ run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 1000, 1000), iterations), 5));
+ run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 1000, 100000), iterations), 5));
+ }
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/QueuePerfTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/QueuePerfTest.java
new file mode 100644
index 0000000000..36e4e90f35
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/QueuePerfTest.java
@@ -0,0 +1,255 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.util.AveragedRun;
+import org.apache.qpid.server.util.TimedRun;
+import org.apache.qpid.server.util.RunStats;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class QueuePerfTest extends TimedRun
+{
+ private final Factory _factory;
+ private final int _queueCount;
+ private final int _messages;
+ private final String _msg = "";
+ private List<Queue<String>> _queues;
+
+ QueuePerfTest(Factory factory, int queueCount, int messages)
+ {
+ super(factory + ", " + queueCount + ", " + messages);
+ _factory = factory;
+ _queueCount = queueCount;
+ _messages = messages;
+ }
+
+ protected void setup() throws Exception
+ {
+ //init
+ int count = Integer.getInteger("prepopulate", 0);
+// System.err.println("Prepopulating with " + count + " items");
+ _queues = new ArrayList<Queue<String>>(_queueCount);
+ for (int i = 0; i < _queueCount; i++)
+ {
+ Queue<String> q = _factory.create();
+ for(int j = 0; j < count; ++j)
+ {
+ q.add("Item"+ j);
+ }
+ _queues.add(q);
+ }
+ System.gc();
+ }
+
+ protected void teardown() throws Exception
+ {
+ System.gc();
+ }
+
+ protected void run() throws Exception
+ {
+ //dispatch
+ for (int i = 0; i < _messages; i++)
+ {
+ for (Queue<String> q : _queues)
+ {
+ q.offer(_msg);
+ q.poll();
+ }
+ }
+ }
+
+ static interface Factory
+ {
+ Queue<String> create();
+ }
+
+ static Factory CONCURRENT = new Factory()
+ {
+ public Queue<String> create()
+ {
+ return new ConcurrentLinkedQueue<String>();
+ }
+
+ public String toString()
+ {
+ return "ConcurrentLinkedQueue";
+ }
+
+ };
+
+ static Factory SYNCHRONIZED = new Factory()
+ {
+ public Queue<String> create()
+ {
+ return new SynchronizedQueue<String>(new LinkedList<String>());
+ }
+
+
+ public String toString()
+ {
+ return "Synchronized LinkedList";
+ }
+ };
+
+ static Factory PLAIN = new Factory()
+ {
+ public Queue<String> create()
+ {
+ return new LinkedList<String>();
+ }
+
+ public String toString()
+ {
+ return "Plain LinkedList";
+ }
+ };
+
+ static class SynchronizedQueue<E> implements Queue<E>
+ {
+ private final Queue<E> queue;
+
+ SynchronizedQueue(Queue<E> queue)
+ {
+ this.queue = queue;
+ }
+
+ public synchronized E element()
+ {
+ return queue.element();
+ }
+
+ public synchronized boolean offer(E o)
+ {
+ return queue.offer(o);
+ }
+
+ public synchronized E peek()
+ {
+ return queue.peek();
+ }
+
+ public synchronized E poll()
+ {
+ return queue.poll();
+ }
+
+ public synchronized E remove()
+ {
+ return queue.remove();
+ }
+
+ public synchronized int size()
+ {
+ return queue.size();
+ }
+
+ public synchronized boolean isEmpty()
+ {
+ return queue.isEmpty();
+ }
+
+ public synchronized boolean contains(Object o)
+ {
+ return queue.contains(o);
+ }
+
+ public synchronized Iterator<E> iterator()
+ {
+ return queue.iterator();
+ }
+
+ public synchronized Object[] toArray()
+ {
+ return queue.toArray();
+ }
+
+ public synchronized <T>T[] toArray(T[] a)
+ {
+ return queue.toArray(a);
+ }
+
+ public synchronized boolean add(E o)
+ {
+ return queue.add(o);
+ }
+
+ public synchronized boolean remove(Object o)
+ {
+ return queue.remove(o);
+ }
+
+ public synchronized boolean containsAll(Collection<?> c)
+ {
+ return queue.containsAll(c);
+ }
+
+ public synchronized boolean addAll(Collection<? extends E> c)
+ {
+ return queue.addAll(c);
+ }
+
+ public synchronized boolean removeAll(Collection<?> c)
+ {
+ return queue.removeAll(c);
+ }
+
+ public synchronized boolean retainAll(Collection<?> c)
+ {
+ return queue.retainAll(c);
+ }
+
+ public synchronized void clear()
+ {
+ queue.clear();
+ }
+ }
+
+ static void run(String label, AveragedRun test) throws Exception
+ {
+ RunStats stats = test.call();
+ System.out.println((label == null ? "" : label + ", ") + test
+ + ", " + stats.getAverage() + ", " + stats.getMax() + ", " + stats.getMin());
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ Factory[] factories = new Factory[]{PLAIN, SYNCHRONIZED, CONCURRENT};
+ int iterations = 5;
+ String label = argv.length > 0 ? argv[0]: null;
+ System.out.println((label == null ? "" : "Label, ") + "Queue Type, No. of Queues, No. of Operations, Avg Time, Min Time, Max Time");
+ //vary number of queues:
+
+ for(Factory f : factories)
+ {
+ run(label, new AveragedRun(new QueuePerfTest(f, 100, 10000), iterations));
+ run(label, new AveragedRun(new QueuePerfTest(f, 1000, 10000), iterations));
+ run(label, new AveragedRun(new QueuePerfTest(f, 10000, 10000), iterations));
+ run(label, new AveragedRun(new QueuePerfTest(f, 1000, 1000), iterations));
+ run(label, new AveragedRun(new QueuePerfTest(f, 1000, 100000), iterations));
+ }
+ }
+
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/SendPerfTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/SendPerfTest.java
new file mode 100644
index 0000000000..15aceaf037
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/SendPerfTest.java
@@ -0,0 +1,173 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.exchange.AbstractExchange;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.handler.OnCurrentThreadExecutor;
+import org.apache.qpid.server.protocol.AMQMinaProtocolSession;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.MockIoSession;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.SkeletonMessageStore;
+import org.apache.qpid.server.util.AveragedRun;
+import org.apache.qpid.server.util.TestApplicationRegistry;
+import org.apache.qpid.server.util.TimedRun;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SendPerfTest extends TimedRun
+{
+ private int _messages = 1000;
+ private int _clients = 10;
+ private List<AMQQueue> _queues;
+
+ public SendPerfTest(int clients, int messages)
+ {
+ super("SendPerfTest, msgs=" + messages + ", clients=" + clients);
+ _messages = messages;
+ _clients = clients;
+ }
+
+ protected void setup() throws Exception
+ {
+ _queues = initQueues(_clients);
+ System.gc();
+ }
+
+ protected void teardown() throws Exception
+ {
+ System.gc();
+ }
+
+ protected void run() throws Exception
+ {
+ deliver(_messages, _queues);
+ }
+
+ //have a dummy AMQProtocolSession that does nothing on the writeFrame()
+ //set up x number of queues
+ //create necessary bits and pieces to deliver a message
+ //deliver y messages to each queue
+
+ public static void main(String[] argv) throws Exception
+ {
+ ApplicationRegistry.initialise(new TestApplicationRegistry());
+ int clients = Integer.parseInt(argv[0]);
+ int messages = Integer.parseInt(argv[1]);
+ int iterations = Integer.parseInt(argv[2]);
+ AveragedRun test = new AveragedRun(new SendPerfTest(clients, messages), iterations);
+ test.run();
+ }
+
+ /**
+ * Delivers messages to a number of queues.
+ * @param count the number of messages to deliver
+ * @param queues the list of queues
+ * @throws NoConsumersException
+ */
+ static void deliver(int count, List<AMQQueue> queues) throws AMQException
+ {
+ BasicPublishBody publish = new BasicPublishBody();
+ publish.exchange = new NullExchange().getName();
+ ContentHeaderBody header = new ContentHeaderBody();
+ List<ContentBody> body = new ArrayList<ContentBody>();
+ MessageStore messageStore = new SkeletonMessageStore();
+ body.add(new ContentBody());
+ for (int i = 0; i < count; i++)
+ {
+ for (AMQQueue q : queues)
+ {
+ //q.deliver(new AMQMessage(messageStore, i, publish, header, body));
+ //q.process(new AMQMessage(messageStore, i, publish, header, body));
+ throw new AMQException("Need to fix this!!");
+ }
+ }
+ }
+
+ static List<AMQQueue> initQueues(int number) throws AMQException
+ {
+ Exchange exchange = new NullExchange();
+ List<AMQQueue> queues = new ArrayList<AMQQueue>(number);
+ for (int i = 0; i < number; i++)
+ {
+ AMQQueue q = createQueue("Queue" + (i + 1));
+ q.bind("routingKey", exchange);
+ try
+ {
+ q.registerProtocolSession(createSession(), 1, "1", false);
+ }
+ catch (Exception e)
+ {
+ throw new AMQException("Error creating protocol session: " + e, e);
+ }
+ queues.add(q);
+ }
+ return queues;
+ }
+
+ static AMQQueue createQueue(String name) throws AMQException
+ {
+ return new AMQQueue(name, false, null, false, ApplicationRegistry.getInstance().getQueueRegistry(),
+ new OnCurrentThreadExecutor());
+ }
+
+ static AMQProtocolSession createSession() throws Exception
+ {
+ IApplicationRegistry reg = ApplicationRegistry.getInstance();
+ AMQCodecFactory codecFactory = new AMQCodecFactory(true);
+ AMQMinaProtocolSession result = new AMQMinaProtocolSession(new MockIoSession(), reg.getQueueRegistry(), reg.getExchangeRegistry(), codecFactory);
+ result.addChannel(new AMQChannel(1, null, null));
+ return result;
+ }
+
+ static class NullExchange extends AbstractExchange
+ {
+ public String getName()
+ {
+ return "NullExchange";
+ }
+
+ protected ExchangeMBean createMBean()
+ {
+ return null;
+ }
+
+ public void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ }
+
+ public void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException
+ {
+ }
+
+ public void route(AMQMessage payload) throws AMQException
+ {
+ }
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionManagerTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionManagerTest.java
new file mode 100644
index 0000000000..7743db5078
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionManagerTest.java
@@ -0,0 +1,105 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import junit.framework.JUnit4TestAdapter;
+
+public class SubscriptionManagerTest
+{
+ private final SubscriptionSet mgr = new SubscriptionSet();
+
+ @Test
+ public void basicSubscriptionManagement()
+ {
+ assertTrue(mgr.isEmpty());
+ assertFalse(mgr.hasActiveSubscribers());
+ TestSubscription s1 = new TestSubscription("S1");
+ mgr.addSubscriber(s1);
+ assertFalse(mgr.isEmpty());
+ assertTrue(mgr.hasActiveSubscribers());
+
+ TestSubscription s2 = new TestSubscription("S2");
+ mgr.addSubscriber(s2);
+
+ s2.setSuspended(true);
+ assertFalse(mgr.isEmpty());
+ assertTrue(mgr.hasActiveSubscribers());
+ assertTrue(s2.isSuspended());
+ assertFalse(s1.isSuspended());
+
+ s1.setSuspended(true);
+ assertFalse(mgr.hasActiveSubscribers());
+
+ mgr.removeSubscriber(new TestSubscription("S1"));
+ assertFalse(mgr.isEmpty());
+ mgr.removeSubscriber(new TestSubscription("S2"));
+ assertTrue(mgr.isEmpty());
+ }
+
+ @Test
+ public void roundRobin()
+ {
+ TestSubscription a = new TestSubscription("A");
+ TestSubscription b = new TestSubscription("B");
+ TestSubscription c = new TestSubscription("C");
+ TestSubscription d = new TestSubscription("D");
+ mgr.addSubscriber(a);
+ mgr.addSubscriber(b);
+ mgr.addSubscriber(c);
+ mgr.addSubscriber(d);
+
+ for (int i = 0; i < 3; i++)
+ {
+ assertEquals(a, mgr.nextSubscriber(null));
+ assertEquals(b, mgr.nextSubscriber(null));
+ assertEquals(c, mgr.nextSubscriber(null));
+ assertEquals(d, mgr.nextSubscriber(null));
+ }
+
+ c.setSuspended(true);
+
+ for (int i = 0; i < 3; i++)
+ {
+ assertEquals(a, mgr.nextSubscriber(null));
+ assertEquals(b, mgr.nextSubscriber(null));
+ assertEquals(d, mgr.nextSubscriber(null));
+ }
+
+ mgr.removeSubscriber(a);
+ d.setSuspended(true);
+ c.setSuspended(false);
+ Subscription e = new TestSubscription("D");
+ mgr.addSubscriber(e);
+
+ for (int i = 0; i < 3; i++)
+ {
+ assertEquals(b, mgr.nextSubscriber(null));
+ assertEquals(c, mgr.nextSubscriber(null));
+ assertEquals(e, mgr.nextSubscriber(null));
+ }
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(SubscriptionManagerTest.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionSetTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionSetTest.java
new file mode 100644
index 0000000000..b6e8f8b44d
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/SubscriptionSetTest.java
@@ -0,0 +1,149 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import org.junit.Test;
+
+public class SubscriptionSetTest
+{
+ /**
+ * A SubscriptionSet that counts the number of items scanned.
+ */
+ static class TestSubscriptionSet extends SubscriptionSet
+ {
+ private int scanned = 0;
+
+ void resetScanned()
+ {
+ scanned = 0;
+ }
+
+ protected void subscriberScanned()
+ {
+ ++scanned;
+ }
+
+ int getScanned()
+ {
+ return scanned;
+ }
+ }
+
+ final TestSubscription sub1 = new TestSubscription("1");
+ final TestSubscription sub2 = new TestSubscription("2");
+ final TestSubscription sub3 = new TestSubscription("3");
+
+ final TestSubscription suspendedSub1 = new TestSubscription("sus1", true);
+ final TestSubscription suspendedSub2 = new TestSubscription("sus2", true);
+ final TestSubscription suspendedSub3 = new TestSubscription("sus3", true);
+
+ @Test
+ public void nextMessage()
+ {
+ SubscriptionSet ss = new SubscriptionSet();
+ assertNull(ss.nextSubscriber(null));
+ assertEquals(0, ss.getCurrentSubscriber());
+
+ ss.addSubscriber(sub1);
+ assertEquals(sub1, ss.nextSubscriber(null));
+ assertEquals(1, ss.getCurrentSubscriber());
+ assertEquals(sub1, ss.nextSubscriber(null));
+ assertEquals(1, ss.getCurrentSubscriber());
+
+ ss.addSubscriber(sub2);
+ ss.addSubscriber(sub3);
+
+ assertEquals(sub2, ss.nextSubscriber(null));
+ assertEquals(2, ss.getCurrentSubscriber());
+
+ assertEquals(sub3, ss.nextSubscriber(null));
+ assertEquals(3, ss.getCurrentSubscriber());
+ }
+
+ @Test
+ public void nextMessageWhenAllSuspended()
+ {
+ SubscriptionSet ss = createAllSuspendedSubscriptionSet();
+ assertNull(ss.nextSubscriber(null));
+ assertEquals(3, ss.getCurrentSubscriber());
+
+ assertNull(ss.nextSubscriber(null));
+ assertEquals(3, ss.getCurrentSubscriber());
+ }
+
+ private TestSubscriptionSet createAllSuspendedSubscriptionSet()
+ {
+ TestSubscriptionSet ss = new TestSubscriptionSet();
+ ss.addSubscriber(suspendedSub1);
+ ss.addSubscriber(suspendedSub2);
+ ss.addSubscriber(suspendedSub3);
+ return ss;
+ }
+
+ @Test
+ public void nextMessageAfterRemove()
+ {
+ SubscriptionSet ss = new SubscriptionSet();
+ ss.addSubscriber(suspendedSub1);
+ ss.addSubscriber(suspendedSub2);
+ ss.addSubscriber(sub3);
+ assertEquals(sub3, ss.nextSubscriber(null));
+ assertEquals(3, ss.getCurrentSubscriber());
+
+ assertEquals(suspendedSub1, ss.removeSubscriber(suspendedSub1));
+
+ assertEquals(sub3, ss.nextSubscriber(null)); // Current implementation handles OutOfBoundsException here.
+ assertEquals(2, ss.getCurrentSubscriber());
+ }
+
+ @Test
+ public void nextMessageOverScanning()
+ {
+ TestSubscriptionSet ss = new TestSubscriptionSet();
+ TestSubscription sub = new TestSubscription("test");
+ ss.addSubscriber(suspendedSub1);
+ ss.addSubscriber(sub);
+ ss.addSubscriber(suspendedSub3);
+ assertEquals(sub, ss.nextSubscriber(null));
+ assertEquals(2, ss.getCurrentSubscriber());
+ assertEquals(2, ss.getScanned());
+
+ ss.resetScanned();
+ sub.setSuspended(true);
+ assertNull(ss.nextSubscriber(null));
+ assertEquals(3, ss.getCurrentSubscriber());
+ // Current implementation overscans by one item here.
+ assertEquals(ss.size() + 1, ss.getScanned());
+ }
+
+ @Test
+ public void nextMessageOverscanWorstCase() {
+ TestSubscriptionSet ss = createAllSuspendedSubscriptionSet();
+ ss.nextSubscriber(null);
+ // Scans the subscriptions twice.
+ assertEquals(ss.size() * 2, ss.getScanned());
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(SubscriptionSetTest.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/TestSubscription.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/TestSubscription.java
new file mode 100644
index 0000000000..093d7e60f4
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/TestSubscription.java
@@ -0,0 +1,84 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 java.util.ArrayList;
+import java.util.List;
+
+public class TestSubscription implements Subscription
+{
+ private final List<AMQMessage> messages;
+ private final Object key;
+ private boolean isSuspended;
+
+ public TestSubscription(Object key)
+ {
+ this(key, new ArrayList<AMQMessage>());
+ }
+
+ public TestSubscription(final Object key, final boolean isSuspended)
+ {
+ this(key);
+ setSuspended(isSuspended);
+ }
+
+ TestSubscription(Object key, List<AMQMessage> messages)
+ {
+ this.key = key;
+ this.messages = messages;
+ }
+
+ List<AMQMessage> getMessages()
+ {
+ return messages;
+ }
+
+ public void send(AMQMessage msg, AMQQueue queue)
+ {
+ messages.add(msg);
+ }
+
+ public void setSuspended(boolean suspended)
+ {
+ isSuspended = suspended;
+ }
+
+ public boolean isSuspended()
+ {
+ return isSuspended;
+ }
+
+ public void queueDeleted(AMQQueue queue)
+ {
+ }
+
+ public int hashCode()
+ {
+ return key.hashCode();
+ }
+
+ public boolean equals(Object o)
+ {
+ return o instanceof TestSubscription && ((TestSubscription) o).key.equals(key);
+ }
+
+ public String toString()
+ {
+ return key.toString();
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/queue/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/queue/UnitTests.java
new file mode 100644
index 0000000000..3a86773a15
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/queue/UnitTests.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ AckTest.class,
+ DeliveryManagerTest.class,
+ SubscriptionManagerTest.class,
+ SubscriptionSetTest.class,
+ ConcurrencyTest.class}
+)
+public class UnitTests
+{
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(UnitTests.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java b/java/broker/src/main/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java
new file mode 100644
index 0000000000..981665b4e3
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java
@@ -0,0 +1,120 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.framing.BasicPublishBody;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.queue.MessageMetaData;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A message store that does nothing. Designed to be used in tests that do not want to use any message store
+ * functionality.
+ */
+public class SkeletonMessageStore implements MessageStore
+{
+ private final AtomicLong _messageId = new AtomicLong(1);
+
+ public void configure(QueueRegistry queueRegistry, String base, Configuration config) throws Exception
+ {
+ }
+
+ public void close() throws Exception
+ {
+ }
+
+ public void removeMessage(long messageId)
+ {
+ }
+
+ public void createQueue(AMQQueue queue) throws AMQException
+ {
+ }
+
+ public void removeQueue(String name) throws AMQException
+ {
+ }
+
+ public void enqueueMessage(String name, long messageId) throws AMQException
+ {
+ }
+
+ public void dequeueMessage(String name, long messageId) throws AMQException
+ {
+ }
+
+ public void beginTran() throws AMQException
+ {
+ }
+
+ public boolean inTran()
+ {
+ return false;
+ }
+
+ public void commitTran() throws AMQException
+ {
+ }
+
+ public void abortTran() throws AMQException
+ {
+ }
+
+ public List<AMQQueue> createQueues() throws AMQException
+ {
+ return null;
+ }
+
+ public long getNewMessageId()
+ {
+ return _messageId.getAndIncrement();
+ }
+
+ public void storePublishBody(long messageId, BasicPublishBody publishBody) throws AMQException
+ {
+ }
+
+ public void storeContentHeader(long messageId, ContentHeaderBody contentHeaderBody) throws AMQException
+ {
+ }
+
+ public void storeContentBodyChunk(long messageId, int index, ContentBody contentBody) throws AMQException
+ {
+ }
+
+ public void storeMessageMetaData(long messageId, MessageMetaData messageMetaData) throws AMQException
+ {
+ }
+
+ public MessageMetaData getMessageMetaData(long messageId) throws AMQException
+ {
+ return null;
+ }
+
+ public ContentBody getContentBodyChunk(long messageId, int index) throws AMQException
+ {
+ return null;
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/store/TestReferenceCounting.java b/java/broker/src/main/test/java/org/apache/qpid/server/store/TestReferenceCounting.java
new file mode 100644
index 0000000000..fea0c25852
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/store/TestReferenceCounting.java
@@ -0,0 +1,73 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.junit.Test;
+import org.junit.Assert;
+import org.junit.Before;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+import org.apache.qpid.AMQException;
+
+/**
+ * Tests that reference counting works correctly with AMQMessage and the message store
+ */
+public class TestReferenceCounting
+{
+ private TestableMemoryMessageStore _store;
+
+ @Before
+ public void createCommonObjects()
+ {
+ _store = new TestableMemoryMessageStore();
+ }
+
+ /**
+ * Check that when the reference count is decremented the message removes itself from the store
+ */
+ @Test
+ public void testMessageGetsRemoved() throws AMQException
+ {
+ throw new AMQException("Fix this!!!");
+ /*AMQMessage message = new AMQMessage(_store, null);
+ _store.put(message);
+ Assert.assertTrue(_store.getMessageMap().size() == 1);
+ message.decrementReference();
+ Assert.assertTrue(_store.getMessageMap().size() == 0);
+ */
+ }
+
+ @Test
+ public void testMessageRemains() throws AMQException
+ {
+ throw new AMQException("Fix me!!!");
+ /*AMQMessage message = new AMQMessage(_store, null);
+ _store.put(message);
+ Assert.assertTrue(_store.getMessageMap().size() == 1);
+ message.incrementReference();
+ message.decrementReference();
+ Assert.assertTrue(_store.getMessageMap().size() == 1);*/
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(TestReferenceCounting.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java b/java/broker/src/main/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java
new file mode 100644
index 0000000000..c6ff9ce561
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java
@@ -0,0 +1,47 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.qpid.framing.ContentBody;
+import org.apache.qpid.server.queue.MessageMetaData;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Adds some extra methods to the memory message store for testing purposes.
+ */
+public class TestableMemoryMessageStore extends MemoryMessageStore
+{
+ public TestableMemoryMessageStore()
+ {
+ _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>();
+ _contentBodyMap = new ConcurrentHashMap<Long, List<ContentBody>>();
+ }
+
+ public ConcurrentMap<Long, MessageMetaData> getMessageMetaDataMap()
+ {
+ return _metaDataMap;
+ }
+
+ public ConcurrentMap<Long, List<ContentBody>> getContentBodyMap()
+ {
+ return _contentBodyMap;
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/store/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/store/UnitTests.java
new file mode 100644
index 0000000000..a917d736a2
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/store/UnitTests.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ TestReferenceCounting.class
+})
+public class UnitTests
+{
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(UnitTests.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/txn/TxnBufferTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/txn/TxnBufferTest.java
new file mode 100644
index 0000000000..2b49752023
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/txn/TxnBufferTest.java
@@ -0,0 +1,305 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.Ignore;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.TestableMemoryMessageStore;
+
+import java.util.LinkedList;
+
+public class TxnBufferTest
+{
+ private final LinkedList<MockOp> ops = new LinkedList<MockOp>();
+
+ @Before
+ public void setup() throws Exception
+ {
+ }
+
+ @Test
+ public void commit() throws AMQException
+ {
+ MockStore store = new MockStore();
+
+ TxnBuffer buffer = new TxnBuffer(store);
+ buffer.enlist(new MockOp().expectPrepare().expectCommit());
+ //check relative ordering
+ MockOp op = new MockOp().expectPrepare().expectPrepare().expectCommit().expectCommit();
+ buffer.enlist(op);
+ buffer.enlist(op);
+ buffer.enlist(new MockOp().expectPrepare().expectCommit());
+
+ buffer.commit();
+
+ validateOps();
+ store.validate();
+ }
+
+ @Test
+ public void rollback() throws AMQException
+ {
+ MockStore store = new MockStore();
+
+ TxnBuffer buffer = new TxnBuffer(store);
+ buffer.enlist(new MockOp().expectRollback());
+ buffer.enlist(new MockOp().expectRollback());
+ buffer.enlist(new MockOp().expectRollback());
+
+ buffer.rollback();
+
+ validateOps();
+ store.validate();
+ }
+
+ @Test
+ public void commitWithFailureDuringPrepare() throws AMQException
+ {
+ MockStore store = new MockStore();
+ store.expectBegin().expectAbort();
+
+ TxnBuffer buffer = new TxnBuffer(store);
+ buffer.containsPersistentChanges();
+ buffer.enlist(new MockOp().expectPrepare().expectUndoPrepare());
+ buffer.enlist(new TxnTester(store));
+ buffer.enlist(new MockOp().expectPrepare().expectUndoPrepare());
+ buffer.enlist(new FailedPrepare());
+ buffer.enlist(new MockOp());
+
+ buffer.commit();
+ validateOps();
+ store.validate();
+ }
+
+ @Test
+ public void commitWithPersistance() throws AMQException
+ {
+ MockStore store = new MockStore();
+ store.expectBegin().expectCommit();
+
+ TxnBuffer buffer = new TxnBuffer(store);
+ buffer.enlist(new MockOp().expectPrepare().expectCommit());
+ buffer.enlist(new MockOp().expectPrepare().expectCommit());
+ buffer.enlist(new MockOp().expectPrepare().expectCommit());
+ buffer.enlist(new TxnTester(store));
+ buffer.containsPersistentChanges();
+
+ buffer.commit();
+ validateOps();
+ store.validate();
+ }
+
+ private void validateOps()
+ {
+ for(MockOp op : ops)
+ {
+ op.validate();
+ }
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(TxnBufferTest.class);
+ }
+
+ class MockOp implements TxnOp
+ {
+ final Object PREPARE = "PREPARE";
+ final Object COMMIT = "COMMIT";
+ final Object UNDO_PREPARE = "UNDO_PREPARE";
+ final Object ROLLBACK = "ROLLBACK";
+
+ private final LinkedList expected = new LinkedList();
+
+ MockOp()
+ {
+ ops.add(this);
+ }
+
+ public void prepare()
+ {
+ assertEquals(expected.removeLast(), PREPARE);
+ }
+
+ public void commit()
+ {
+ assertEquals(expected.removeLast(), COMMIT);
+ }
+
+ public void undoPrepare()
+ {
+ assertEquals(expected.removeLast(), UNDO_PREPARE);
+ }
+
+ public void rollback()
+ {
+ assertEquals(expected.removeLast(), ROLLBACK);
+ }
+
+ private MockOp expect(Object optype)
+ {
+ expected.addFirst(optype);
+ return this;
+ }
+
+ MockOp expectPrepare()
+ {
+ return expect(PREPARE);
+ }
+
+ MockOp expectCommit()
+ {
+ return expect(COMMIT);
+ }
+
+ MockOp expectUndoPrepare()
+ {
+ return expect(UNDO_PREPARE);
+ }
+
+ MockOp expectRollback()
+ {
+ return expect(ROLLBACK);
+ }
+
+ void validate()
+ {
+ assertEquals("Expected ops were not all invoked", new LinkedList(), expected);
+ }
+
+ void clear()
+ {
+ expected.clear();
+ }
+ }
+
+ class MockStore extends TestableMemoryMessageStore
+ {
+ final Object BEGIN = "BEGIN";
+ final Object ABORT = "ABORT";
+ final Object COMMIT = "COMMIT";
+
+ private final LinkedList expected = new LinkedList();
+ private boolean inTran;
+
+ public void beginTran() throws AMQException
+ {
+ assertEquals(expected.removeLast(), BEGIN);
+ inTran = true;
+ }
+
+ public void commitTran() throws AMQException
+ {
+ assertEquals(expected.removeLast(), COMMIT);
+ inTran = false;
+ }
+
+ public void abortTran() throws AMQException
+ {
+ assertEquals(expected.removeLast(), ABORT);
+ inTran = false;
+ }
+
+ public boolean inTran()
+ {
+ return inTran;
+ }
+
+ private MockStore expect(Object optype)
+ {
+ expected.addFirst(optype);
+ return this;
+ }
+
+ MockStore expectBegin()
+ {
+ return expect(BEGIN);
+ }
+
+ MockStore expectCommit()
+ {
+ return expect(COMMIT);
+ }
+
+ MockStore expectAbort()
+ {
+ return expect(ABORT);
+ }
+
+ void clear()
+ {
+ expected.clear();
+ }
+
+ void validate()
+ {
+ assertEquals("Expected ops were not all invoked", new LinkedList(), expected);
+ }
+ }
+
+ class NullOp implements TxnOp
+ {
+ public void prepare() throws AMQException
+ {
+ }
+ public void commit()
+ {
+ }
+ public void undoPrepare()
+ {
+ }
+ public void rollback()
+ {
+ }
+ }
+
+ class FailedPrepare extends NullOp
+ {
+ public void prepare() throws AMQException
+ {
+ throw new AMQException("Fail!");
+ }
+ }
+
+ class TxnTester extends NullOp
+ {
+ private final MessageStore store;
+
+ TxnTester(MessageStore store)
+ {
+ this.store = store;
+ }
+
+ public void prepare() throws AMQException
+ {
+ assertTrue("Expected prepare to be performed under txn", store.inTran());
+ }
+
+ public void commit()
+ {
+ assertTrue("Expected commit not to be performed under txn", !store.inTran());
+ }
+ }
+
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/txn/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/txn/UnitTests.java
new file mode 100644
index 0000000000..dd63b260fd
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/txn/UnitTests.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ TxnBufferTest.class
+})
+public class UnitTests
+{
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(UnitTests.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/AveragedRun.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/AveragedRun.java
new file mode 100644
index 0000000000..3e4a1edac4
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/AveragedRun.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.util.TimedRun;
+
+import java.util.concurrent.Callable;
+import java.util.Collection;
+
+public class AveragedRun implements Callable<RunStats>
+{
+ private final RunStats stats = new RunStats();
+ private final TimedRun test;
+ private final int iterations;
+
+ public AveragedRun(TimedRun test, int iterations)
+ {
+ this.test = test;
+ this.iterations = iterations;
+ }
+
+ public RunStats call() throws Exception
+ {
+ for (int i = 0; i < iterations; i++)
+ {
+ stats.record(test.call());
+ }
+ return stats;
+ }
+
+ public void run() throws Exception
+ {
+ System.out.println(test + ": " + call());
+ }
+
+ public String toString()
+ {
+ return test.toString();
+ }
+
+ static void run(Collection<AveragedRun> tests) throws Exception
+ {
+ for(AveragedRun test : tests)
+ {
+ test.run();
+ }
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/ConcurrentTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/ConcurrentTest.java
new file mode 100644
index 0000000000..6c5185e254
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/ConcurrentTest.java
@@ -0,0 +1,76 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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;
+
+public class ConcurrentTest extends TimedRun
+{
+ private final TimedRun _test;
+ private final Thread[] _threads;
+
+ public ConcurrentTest(TimedRun test, int threads)
+ {
+ super(test.toString());
+ _test = test;
+ _threads = new Thread[threads];
+ }
+
+ protected void setup() throws Exception
+ {
+ _test.setup();
+ for(int i = 0; i < _threads.length; i++)
+ {
+ _threads[i] = new Thread(new Runner());
+ }
+ }
+
+ protected void teardown() throws Exception
+ {
+ _test.teardown();
+ }
+
+ protected void run() throws Exception
+ {
+ for(Thread t : _threads)
+ {
+ t.start();
+ }
+ for(Thread t : _threads)
+ {
+ t.join();
+ }
+ }
+
+ private class Runner implements Runnable
+ {
+ private Exception error;
+
+ public void run()
+ {
+ try
+ {
+ _test.run();
+ }
+ catch(Exception e)
+ {
+ error = e;
+ e.printStackTrace();
+ }
+ }
+ }
+
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/LoggingProxyTest.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/LoggingProxyTest.java
new file mode 100644
index 0000000000..15c9e1a59a
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/LoggingProxyTest.java
@@ -0,0 +1,89 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.junit.Assert;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+public class LoggingProxyTest
+{
+ static interface IFoo {
+ void foo();
+ void foo(int i, Collection c);
+ String bar();
+ String bar(String s, List l);
+ }
+
+ static class Foo implements IFoo {
+ public void foo()
+ {
+ }
+
+ public void foo(int i, Collection c)
+ {
+ }
+
+ public String bar()
+ {
+ return null;
+ }
+
+ public String bar(String s, List l)
+ {
+ return "ha";
+ }
+ }
+
+ @Test
+ public void simple() {
+ LoggingProxy proxy = new LoggingProxy(new Foo(), 20);
+ IFoo foo = (IFoo)proxy.getProxy(IFoo.class);
+ foo.foo();
+ assertEquals(2, proxy.getBufferSize());
+ Assert.assertTrue(proxy.getBuffer().get(0).toString().matches(".*: foo\\(\\) entered$"));
+ Assert.assertTrue(proxy.getBuffer().get(1).toString().matches(".*: foo\\(\\) returned$"));
+
+ foo.foo(3, Arrays.asList(0, 1, 2));
+ assertEquals(4, proxy.getBufferSize());
+ Assert.assertTrue(proxy.getBuffer().get(2).toString().matches(".*: foo\\(\\[3, \\[0, 1, 2\\]\\]\\) entered$"));
+ Assert.assertTrue(proxy.getBuffer().get(3).toString().matches(".*: foo\\(\\) returned$"));
+
+ foo.bar();
+ assertEquals(6, proxy.getBufferSize());
+ Assert.assertTrue(proxy.getBuffer().get(4).toString().matches(".*: bar\\(\\) entered$"));
+ Assert.assertTrue(proxy.getBuffer().get(5).toString().matches(".*: bar\\(\\) returned null$"));
+
+ foo.bar("hello", Arrays.asList(1, 2, 3));
+ assertEquals(8, proxy.getBufferSize());
+ Assert.assertTrue(proxy.getBuffer().get(6).toString().matches(".*: bar\\(\\[hello, \\[1, 2, 3\\]\\]\\) entered$"));
+ Assert.assertTrue(proxy.getBuffer().get(7).toString().matches(".*: bar\\(\\) returned ha$"));
+
+ proxy.dump();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(LoggingProxyTest.class);
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/RunStats.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/RunStats.java
new file mode 100644
index 0000000000..248622836d
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/RunStats.java
@@ -0,0 +1,54 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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;
+
+public class RunStats
+{
+ private long min = Long.MAX_VALUE;
+ private long max;
+ private long total;
+ private int count;
+
+ public void record(long time)
+ {
+ max = Math.max(time, max);
+ min = Math.min(time, min);
+ total += time;
+ count++;
+ }
+
+ public long getMin()
+ {
+ return min;
+ }
+
+ public long getMax()
+ {
+ return max;
+ }
+
+ public long getAverage()
+ {
+ return total / count;
+ }
+
+ public String toString()
+ {
+ return "avg=" + getAverage() + ", min=" + min + ", max=" + max;
+ }
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java
new file mode 100644
index 0000000000..69939b88e4
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java
@@ -0,0 +1,104 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.TestableMemoryMessageStore;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.MapConfiguration;
+
+import java.util.HashMap;
+
+public class TestApplicationRegistry extends ApplicationRegistry
+{
+ private QueueRegistry _queueRegistry;
+
+ private ExchangeRegistry _exchangeRegistry;
+
+ private ExchangeFactory _exchangeFactory;
+
+ private ManagedObjectRegistry _managedObjectRegistry;
+
+ private AuthenticationManager _authenticationManager;
+
+ private MessageStore _messageStore;
+
+ public TestApplicationRegistry()
+ {
+ 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 TestableMemoryMessageStore();
+
+ _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;
+ }
+}
+
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/TimedRun.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/TimedRun.java
new file mode 100644
index 0000000000..f779b7fbb6
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/TimedRun.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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.Callable;
+
+public abstract class TimedRun implements Callable<Long>
+{
+ private final String description;
+
+ public TimedRun(String description)
+ {
+ this.description = description;
+ }
+
+ public Long call() throws Exception
+ {
+ setup();
+ long start = System.currentTimeMillis();
+ run();
+ long stop = System.currentTimeMillis();
+ teardown();
+ return stop - start;
+ }
+
+ public String toString()
+ {
+ return description;
+ }
+
+ protected void setup() throws Exception{}
+ protected void teardown() throws Exception{}
+ protected abstract void run() throws Exception;
+}
diff --git a/java/broker/src/main/test/java/org/apache/qpid/server/util/UnitTests.java b/java/broker/src/main/test/java/org/apache/qpid/server/util/UnitTests.java
new file mode 100644
index 0000000000..d6cc471413
--- /dev/null
+++ b/java/broker/src/main/test/java/org/apache/qpid/server/util/UnitTests.java
@@ -0,0 +1,32 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed 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 junit.framework.JUnit4TestAdapter;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({LoggingProxyTest.class})
+public class UnitTests
+{
+ public static junit.framework.Test suite()
+ {
+ return new JUnit4TestAdapter(UnitTests.class);
+ }
+}
diff --git a/java/broker/src/main/test/lib/README b/java/broker/src/main/test/lib/README
new file mode 100644
index 0000000000..a75673935e
--- /dev/null
+++ b/java/broker/src/main/test/lib/README
@@ -0,0 +1 @@
+Junit copied here momentarily.
diff --git a/java/broker/src/main/test/lib/junit/junit-4.0.jar b/java/broker/src/main/test/lib/junit/junit-4.0.jar
new file mode 100644
index 0000000000..b20406924a
--- /dev/null
+++ b/java/broker/src/main/test/lib/junit/junit-4.0.jar
Binary files differ
diff --git a/java/broker/src/main/test/lib/junit/junit.jar b/java/broker/src/main/test/lib/junit/junit.jar
new file mode 100644
index 0000000000..674d71e89e
--- /dev/null
+++ b/java/broker/src/main/test/lib/junit/junit.jar
Binary files differ