summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Ritchie <ritchiem@apache.org>2009-04-14 15:54:16 +0000
committerMartin Ritchie <ritchiem@apache.org>2009-04-14 15:54:16 +0000
commitab8ba0dfc8979ac2fbba266b42e16f71c52276fe (patch)
tree80ffdaea6a17003b904f569748544ac8d4829b2a
parent3e7f6b624a7131c352e2240495da116f060fafc9 (diff)
downloadqpid-python-ab8ba0dfc8979ac2fbba266b42e16f71c52276fe.tar.gz
QPID-1807 : Add 0.5-fix broker and update SlowMessageStore to use MessageStores rather than TransactionLogs
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@764850 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--qpid/java/broker/bin/create-example-ssl-stores.bat36
-rw-r--r--qpid/java/broker/bin/create-example-ssl-stores.sh38
-rwxr-xr-xqpid/java/broker/bin/msTool.sh60
-rwxr-xr-xqpid/java/broker/bin/qpid-passwd35
-rwxr-xr-xqpid/java/broker/bin/qpid-server39
-rwxr-xr-xqpid/java/broker/bin/qpid-server-bdb.bat22
-rwxr-xr-xqpid/java/broker/bin/qpid-server.bat203
-rwxr-xr-xqpid/java/broker/bin/qpid.start21
-rwxr-xr-xqpid/java/broker/bin/qpid.stop178
-rwxr-xr-xqpid/java/broker/bin/qpid.stopall27
-rw-r--r--qpid/java/broker/build.xml57
-rw-r--r--qpid/java/broker/etc/access19
-rw-r--r--qpid/java/broker/etc/acl.config.xml230
-rw-r--r--qpid/java/broker/etc/config.xml138
-rw-r--r--qpid/java/broker/etc/debug.log4j.xml114
-rw-r--r--qpid/java/broker/etc/jmxremote.access23
-rw-r--r--qpid/java/broker/etc/log4j.xml98
-rw-r--r--qpid/java/broker/etc/md5passwd21
-rw-r--r--qpid/java/broker/etc/mstool-log4j.xml54
-rw-r--r--qpid/java/broker/etc/passwd22
-rw-r--r--qpid/java/broker/etc/passwdVhost19
-rw-r--r--qpid/java/broker/etc/persistent_config.xml121
-rw-r--r--qpid/java/broker/etc/qpid-server.conf25
-rw-r--r--qpid/java/broker/etc/qpid-server.conf.jpp49
-rw-r--r--qpid/java/broker/etc/qpid.passwd23
-rw-r--r--qpid/java/broker/etc/transient_config.xml118
-rw-r--r--qpid/java/broker/etc/virtualhosts.xml123
-rwxr-xr-xqpid/java/broker/python-test.xml56
-rw-r--r--qpid/java/broker/scripts/resetAlerting.sh95
-rw-r--r--qpid/java/broker/src/main/grammar/SelectorParser.jj621
-rw-r--r--qpid/java/broker/src/main/java/log4j.properties24
-rw-r--r--qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java1007
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/configuration/Configuration.java188
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/AMQBrokerManagerMBean.java235
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java909
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java25
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/ExtractResendAndRequeue.java108
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java509
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java68
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java79
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java148
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java81
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java232
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java58
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java111
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SecurityConfiguration.java41
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java519
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java169
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagement.java43
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagementMBean.java49
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java73
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java38
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java211
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java114
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java139
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java251
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java98
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java41
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java45
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java51
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeType.java35
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java224
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java219
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java350
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java99
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java99
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java41
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java49
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java670
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKey.java40
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKeyDictionary.java50
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderMatcherResult.java25
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersMatcherDFAState.java339
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersParser.java439
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java295
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java25
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java613
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java54
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java63
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ArithmeticExpression.java275
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BinaryExpression.java106
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BooleanExpression.java41
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ComparisonExpression.java601
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java211
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/Expression.java38
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.java39
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java67
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java56
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/LogicExpression.java122
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java30
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java42
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/PropertyExpression.java268
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java77
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/UnaryExpression.java369
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XPathExpression.java127
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XQueryExpression.java58
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XalanXPathEvaluator.java103
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java62
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/flow/BytesOnlyCreditManager.java77
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java44
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java44
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java79
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java76
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java185
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java61
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java67
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java74
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java173
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicGetMethodHandler.java190
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java106
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java58
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java73
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverSyncMethodHandler.java75
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRejectMethodHandler.java125
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java77
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java53
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java66
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java103
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java72
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java63
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java102
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java126
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java163
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java54
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeBoundHandler.java178
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java122
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java75
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java34
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java143
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java203
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java127
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueuePurgeHandler.java122
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueUnbindHandler.java137
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl.java566
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_9.java164
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_8_0.java86
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java78
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java77
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java63
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/UnexpectedMethodException.java33
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagement.java136
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java674
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java97
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java200
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java371
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java41
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java39
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java38
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java388
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java257
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java43
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java37
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java34
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java98
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java58
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java51
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java60
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverter.java57
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverterRegistry.java61
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_8/ProtocolOutputConverterImpl.java284
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9/ProtocolOutputConverterImpl.java391
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Activator.java44
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java176
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java859
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQNoMethodHandlerException.java46
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java284
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java52
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java207
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBean.java304
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java51
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java136
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/UnknnownMessageTypeException.java46
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java482
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java79
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java70
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java220
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java86
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueMBean.java478
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java71
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBinding.java84
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java82
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java50
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Filterable.java33
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java157
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/IncomingMessage.java319
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java245
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java52
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.java46
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java92
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java47
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java138
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java169
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.java184
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java388
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java30
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java34
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java26
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java27
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java43
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java1600
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java178
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/TransientMessageData.java127
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/UnauthorizedAccessException.java45
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java219
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java290
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java87
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java79
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLManager.java322
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPlugin.java70
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPluginFactory.java33
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessResult.java65
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessRights.java63
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Accessable.java27
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AuthorizationManager.java6
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.java38
-rwxr-xr-xqpid/java/broker/src/main/java/org/apache/qpid/server/security/access/PrincipalPermissions.java612
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/VirtualHostAccess.java68
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBean.java501
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/UserManagement.java121
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AbstractACLPlugin.java99
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java54
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/BasicACLPlugin.java129
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/DenyAll.java75
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/LegacyAccessPlugin.java71
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java432
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallFactory.java45
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallPlugin.java264
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java63
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java541
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java232
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java169
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java491
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java106
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java105
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java35
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java169
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java50
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java38
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java237
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java119
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java76
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/JCAProvider.java46
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java123
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipal.java44
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java38
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java132
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java60
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java50
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java105
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java61
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java71
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java38
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java151
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java60
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java36
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java263
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java52
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java35
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java30
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/store/DerbyMessageStore.java1464
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java235
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java277
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java36
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreContext.java71
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java29
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java28
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription.java96
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactory.java59
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactoryImpl.java103
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java617
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java247
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java705
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java293
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java213
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java58
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java179
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java109
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java55
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java131
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/util/ConcurrentLinkedQueueNoSize.java38
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java105
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java86
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java45
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java593
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java70
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/MessageStoreTool.java652
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/AbstractCommand.java66
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Clear.java85
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Command.java36
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Copy.java55
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Dump.java302
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Help.java98
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/List.java314
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Load.java94
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Move.java206
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Purge.java67
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Quit.java54
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Select.java233
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Show.java515
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/security/Passwd.java81
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/CommandParser.java51
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/Console.java90
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleCommandParser.java121
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleConsole.java363
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/AMQBrokerManagerMBeanTest.java91
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/ExtractResendAndRequeueTest.java255
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/RunBrokerWithCommand.java132
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/SelectorParserTest.java128
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/ack/AcknowledgeTest.java120
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/ack/TxAckTest.java283
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java136
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java867
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java50
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java124
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java562
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.java595
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/ExchangeMBeanTest.java145
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java199
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java106
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/logging/management/LoggingManagementMBeanTest.java413
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/MockPluginManager.java51
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/PluginTest.java53
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java124
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java180
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/MaxChannelsTest.java86
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/TestIoSession.java328
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java107
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java354
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java85
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java349
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AckTest.java420
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessage.java49
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessageHandle.java37
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java335
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockMessagePublishInfo.java52
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockProtocolSession.java262
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java197
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java435
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java60
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/registry/ApplicationRegistryShutdownTest.java120
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ACLManagerTest.java99
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ExchangeDenier.java62
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java156
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/QueueDenier.java68
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBeanTest.java271
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/plugins/network/FirewallPluginTest.java295
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java447
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java95
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java396
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java78
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java267
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java66
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java91
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java43
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java39
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreShutdownTest.java81
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreTest.java646
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java156
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java51
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestReferenceCounting.java169
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java92
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java180
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/QueueBrowserUsesNoAckTest.java77
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/txn/TxnBufferTest.java306
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/util/InternalBrokerBaseCase.java214
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/util/LoggingProxyTest.java88
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java137
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/util/MockChannel.java43
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java132
369 files changed, 60356 insertions, 66 deletions
diff --git a/qpid/java/broker/bin/create-example-ssl-stores.bat b/qpid/java/broker/bin/create-example-ssl-stores.bat
new file mode 100644
index 0000000000..5419c098d5
--- /dev/null
+++ b/qpid/java/broker/bin/create-example-ssl-stores.bat
@@ -0,0 +1,36 @@
+@REM
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM
+
+@REM Create example keystore for broker and trust store for client/management console.
+@REM
+@REM Use generated qpid.keystore as the brokers keystore
+@REM Use generated qpid.truststore as client/consoles truststore
+@REM All passwords have value: password
+
+@REM Create Broker Keystore:
+keytool -genkey -alias qpidBroker -keyalg RSA -validity 365 -keystore qpid.keystore -storepass password -keypass password -dname "CN=hostname, OU=OrgUnit, O=Org, L=City, C=US"
+
+@REM Export Self Signed Cert:
+keytool -export -alias qpidBroker -keystore qpid.keystore -file qpidBroker.cer -storepass password
+
+@REM Import Broker Cert Into MC TrustStore:
+keytool -import -alias qpidBrokerCert -file qpidBroker.cer -keystore qpid.truststore -storepass password -noprompt
+
+@REM Delete the cert
+del qpidBroker.cer \ No newline at end of file
diff --git a/qpid/java/broker/bin/create-example-ssl-stores.sh b/qpid/java/broker/bin/create-example-ssl-stores.sh
new file mode 100644
index 0000000000..bfcb3dfecf
--- /dev/null
+++ b/qpid/java/broker/bin/create-example-ssl-stores.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Create example keystore for broker and trust store for client/management console.
+#
+# Use generated qpid.keystore as the brokers keystore
+# Use generated qpid.truststore as client/consoles truststore
+# All passwords have value: password
+
+#Create Broker Keystore:
+keytool -genkey -alias qpidBroker -keyalg RSA -validity 365 -keystore qpid.keystore \
+-storepass password -keypass password -dname "CN=hostname, OU=OrgUnit, O=Org, L=City, C=US"
+
+#Export Self Signed Cert:
+keytool -export -alias qpidBroker -keystore qpid.keystore -file qpidBroker.cer -storepass password
+
+#Import Broker Cert Into MC TrustStore:
+keytool -import -alias qpidBrokerCert -file qpidBroker.cer -keystore qpid.truststore -storepass password -noprompt
+
+#Delete the cert
+rm qpidBroker.cer
diff --git a/qpid/java/broker/bin/msTool.sh b/qpid/java/broker/bin/msTool.sh
new file mode 100755
index 0000000000..e190a0a46a
--- /dev/null
+++ b/qpid/java/broker/bin/msTool.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+die() {
+ if [[ $1 = -usage ]]; then
+ shift
+ usage=true
+ else
+ usage=false
+ fi
+ echo "$@"
+ $usage && echo
+ $usage && usage
+ exit 1
+}
+
+cygwin=false
+if [[ "$(uname -a | fgrep Cygwin)" != "" ]]; then
+ cygwin=true
+fi
+
+if [ -z "$QPID_TOOLS" ]; then
+ if [ -z "$QPID_HOME" ]; then
+ die "QPID_TOOLS must be set"
+ else
+ QPID_TOOLS=$QPID_HOME
+ fi
+fi
+
+if $cygwin; then
+ QPID_TOOLS=$(cygpath -w $QPID_TOOLS)
+fi
+
+# Set classpath to include Qpid jar with all required jars in manifest
+QPID_LIBS=$QPID_TOOLS/lib/qpid-all.jar
+
+# Set other variables used by the qpid-run script before calling
+export JAVA=java \
+ JAVA_VM=-server \
+ JAVA_OPTS=-Dlog4j.configuration=file:$QPID_TOOLS/etc/mstool-log4j.xml \
+ QPID_CLASSPATH=$QPID_LIBS
+
+. qpid-run org.apache.qpid.tools.messagestore.MessageStoreTool "$@"
diff --git a/qpid/java/broker/bin/qpid-passwd b/qpid/java/broker/bin/qpid-passwd
new file mode 100755
index 0000000000..63b30b5e71
--- /dev/null
+++ b/qpid/java/broker/bin/qpid-passwd
@@ -0,0 +1,35 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+if [ -z "$QPID_HOME" ]; then
+ export QPID_HOME=$(dirname $(dirname $(readlink -f $0)))
+ export PATH=${PATH}:${QPID_HOME}/bin
+fi
+
+# Set classpath to include Qpid jar with all required jars in manifest
+QPID_LIBS=$QPID_HOME/lib/qpid-all.jar
+
+# Set other variables used by the qpid-run script before calling
+export JAVA=java \
+ JAVA_VM=-server \
+ JAVA_MEM=-Xmx1024m \
+ QPID_CLASSPATH=$QPID_LIBS
+
+. qpid-run org.apache.qpid.tools.security.Passwd "$@"
diff --git a/qpid/java/broker/bin/qpid-server b/qpid/java/broker/bin/qpid-server
new file mode 100755
index 0000000000..e5a9e998e2
--- /dev/null
+++ b/qpid/java/broker/bin/qpid-server
@@ -0,0 +1,39 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+if [ -z "$QPID_HOME" ]; then
+ export QPID_HOME=$(dirname $(dirname $(readlink -f $0)))
+ export PATH=${PATH}:${QPID_HOME}/bin
+fi
+
+# Set classpath to include Qpid jar with all required jars in manifest
+QPID_LIBS=$QPID_HOME/lib/qpid-all.jar:$QPID_HOME/lib/bdbstore-launch.jar
+
+# Set other variables used by the qpid-run script before calling
+export JAVA=java \
+ JAVA_VM=-server \
+ JAVA_MEM=-Xmx1024m \
+ JAVA_GC="-XX:+UseConcMarkSweepGC -XX:+HeapDumpOnOutOfMemoryError" \
+ QPID_CLASSPATH=$QPID_LIBS \
+ QPID_RUN_LOG=2
+
+QPID_OPTS="$QPID_OPTS -Damqj.read_write_pool_size=32"
+
+. qpid-run org.apache.qpid.server.Main "$@"
diff --git a/qpid/java/broker/bin/qpid-server-bdb.bat b/qpid/java/broker/bin/qpid-server-bdb.bat
new file mode 100755
index 0000000000..8964e577df
--- /dev/null
+++ b/qpid/java/broker/bin/qpid-server-bdb.bat
@@ -0,0 +1,22 @@
+@REM
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM
+
+set BDBSTORE_HOME=c:\qpid\trunk\java\bdbstore
+set QPID_MODULE_JARS=%BDBSTORE_HOME%\target\qpid-bdbstore-1.0-incubating-M2-SNAPSHOT.jar;%BDBSTORE_HOME%\lib\bdb\je-3.1.0.jar
+.\qpid-server.bat \ No newline at end of file
diff --git a/qpid/java/broker/bin/qpid-server.bat b/qpid/java/broker/bin/qpid-server.bat
new file mode 100755
index 0000000000..2687baa111
--- /dev/null
+++ b/qpid/java/broker/bin/qpid-server.bat
@@ -0,0 +1,203 @@
+@REM
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM
+
+@echo off
+REM Script to run the Qpid Java Broker
+
+rem Guess QPID_HOME if not defined
+set CURRENT_DIR=%cd%
+if not "%QPID_HOME%" == "" goto gotHome
+set QPID_HOME=%CURRENT_DIR%
+echo %QPID_HOME%
+if exist "%QPID_HOME%\bin\qpid-server.bat" goto okHome
+cd ..
+set QPID_HOME=%cd%
+cd %CURRENT_DIR%
+:gotHome
+if exist "%QPID_HOME%\bin\qpid-server.bat" goto okHome
+echo The QPID_HOME environment variable is not defined correctly
+echo This environment variable is needed to run this program
+goto end
+:okHome
+
+REM set QPID_WORK if not set
+if not "%QPID_WORK%" == "" goto okQpidWork
+if "%HOME%" == "" goto noHome
+set QPID_WOKR=%HOME%
+goto okQpidWork
+
+:noHome
+set QPID_WORK=c:\Temp
+if not exist %QPID_WORK% md %QPID_WORK%
+:okQpidWork
+
+if not "%JAVA_HOME%" == "" goto gotJavaHome
+echo The JAVA_HOME environment variable is not defined
+echo This environment variable is needed to run this program
+goto end
+:gotJavaHome
+if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome
+goto okJavaHome
+:noJavaHome
+echo The JAVA_HOME environment variable is not defined correctly
+echo This environment variable is needed to run this program.
+goto end
+:okJavaHome
+
+REM set loggin level if not set
+if "%AMQJ_LOGGING_LEVEL%" == "" set AMQJ_LOGGING_LEVEL=info
+
+REM Set the default system properties that we'll use now that they have
+REM all been initialised
+set SYSTEM_PROPS=-Damqj.logging.level=%AMQJ_LOGGING_LEVEL% -DQPID_HOME=%QPID_HOME% -DQPID_WORK=%QPID_WORK%
+
+if "%EXTERNAL_CLASSPATH%" == "" set EXTERNAL_CLASSPATH=%CLASSPATH%
+
+REM Use QPID_CLASSPATH if set
+if "%QPID_CLASSPATH%" == "" goto noQpidClasspath
+set CLASSPATH=%QPID_CLASSPATH%
+echo Using CLASSPATH: %CLASSPATH%
+goto afterQpidClasspath
+
+:noQpidClasspath
+echo Warning: Qpid classpath not set. CLASSPATH set to %QPID_HOME%\lib\qpid-all.jar
+set CLASSPATH=%QPID_HOME%\lib\qpid-all.jar
+:afterQpidClasspath
+
+REM start parsing -run arguments
+set QPID_ARGS=
+if "%1" == "" goto endRunArgs
+:runLoop
+set var=%1
+if "%var:~0,5%" == "-run:" goto runFound
+set QPID_ARGS=%QPID_ARGS% %1
+:beforeRunShift
+shift
+if not "%1"=="" goto runLoop
+goto endRunArgs
+
+:runFound
+if "%var%" == "-run:debug" goto runDebug
+if "%var%" == "-run:jpda" goto runJdpa
+if "%var:~0,24%" == "-run:external-classpath-" goto runExternalClasspath
+if "%var%" == "-run:print-classpath" goto runPrintCP
+if "%var%" == "-run:help" goto runHelp
+echo "unrecognized -run option '%var%'. For using external classpaths use -run:external-classpath-option"
+goto end
+
+:runDebug
+REM USAGE: print the classpath and command before running it
+set debug=true
+goto beforeRunShift
+
+:runJdpa
+REM USAGE: adds debugging options to the java command, use
+REM USAGE: JDPA_TRANSPORT and JPDA_ADDRESS to customize the debugging
+REM USAGE: behavior and use JPDA_OPTS to override it entirely
+if "%JPDA_OPTS%" == "" goto beforeRunShift
+if "%JPDA_TRANSPORT%" == "" set JPDA_TRANSPORT=-dt_socket
+if "%JPDA_ADDRESS%" == "" set JPDA_ADDRESS=8000
+set JPDA_OPTS="-Xdebug -Xrunjdwp:transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=n"
+set QPID_OPTS="%QPID_OPTS% %JPDA_OPTS%"
+goto beforeRunShift
+
+:runExternalClasspath
+echo Using external classpath %var%
+REM USAGE: Format is -run:external-classpath-first/last/ignore/only as equals special in DOS
+REM USAGE: controls how the CLASSPATH environment variable is used by
+REM USAGE: this script, value can be one of ignore (the default), first,
+REM USAGE: last, and only
+if "%var%" == "-run:external-classpath-ignore" goto beforeRunShift
+if "%var%" == "-run:external-classpath-first" goto extCPFirst
+if "%var%" == "-run:external-classpath-last" goto extCPLast
+if "%var%" == "-run:external-classpath-only" goto extCPOnly
+echo Invalid value provided for external classpath.
+goto end
+
+:extCPFirst
+set CLASSPATH=%EXTERNAL_CLASSPATH%;%CLASSPATH%
+goto beforeRunShift
+
+:extCPLast
+set CLASSPATH=%CLASSPATH%;%EXTERNAL_CLASSPATH%
+goto beforeRunShift
+
+:extCPonly
+set CLASSPATH=%EXTERNAL_CLASSPATH%
+goto beforeRunShift
+
+:runPrintCP
+REM USAGE: print the classpath
+echo %CLASSPATH%
+goto beforeRunShift
+
+:runHelp
+REM USAGE: print this message
+echo -------------------------------------------------------------------------------------------
+echo -run:option where option can be the following.
+echo debug : Prints classpath and command before running it
+echo jpda : Adds remote debugging info using JPDA_OPTS. Use JPDA_TRANSPORT and JPDA_ADDRESS to
+echo customize, JPDA_OPTS to override
+echo external-classpath : Valid values are: ignore, first, last and only.
+echo print-classpath : Prints classpath before running command
+echo help : Prints this message
+echo --------------------------------------------------------------------------------------------
+goto end
+
+REM end parsing -run arguments
+:endRunArgs
+
+set JAVA_VM=-server
+set JAVA_MEM=-Xmx1024m
+set JAVA_GC=-XX:+UseConcMarkSweepGC
+rem removing the following vm arg from JAVA_GC as it is supported on ly in Java 1.6
+rem -XX:+HeapDumpOnOutOfMemoryError"
+
+REM Use QPID_JAVA_GC if set
+if "%QPID_JAVA_GC%" == "" goto noQpidJavaGC
+set JAVA_GC=%QPID_JAVA_GC%
+echo Using QPID_JAVA_GC setting: %QPID_JAVA_GC%
+goto afteQpidJavaGC
+
+:noQPidJavaGC
+echo Info: QPID_JAVA_GC not set. Defaulting to JAVA_GC %JAVA_GC%
+:afterQpidJavaGC
+
+REM Use QPID_JAVA_MEM if set
+if "%QPID_JAVA_MEM%" == "" goto noQpidJavaMem
+set JAVA_MEM=%QPID_JAVA_MEM%
+echo Using QPID_JAVA_MEM setting: %QPID_JAVA_MEM%
+goto afterQpidJavaMem
+
+:noQpidJavaMem
+echo Info: QPID_JAVA_MEM not set. Defaulting to JAVA_MEM %JAVA_MEM%
+:after QpidJavaMem
+
+
+rem QPID_OPTS intended to hold any -D props for use
+rem user must enclose any value for QPID_OPTS in double quotes
+:runCommand
+set MODULE_JARS=%QPID_MODULE_JARS%
+set COMMAND="%JAVA_HOME%\bin\java" %JAVA_VM% %JAVA_MEM% %JAVA_GC% %QPID_OPTS% %SYSTEM_PROPS% -cp "%CLASSPATH%;%MODULE_JARS%" org.apache.qpid.server.Main %QPID_ARGS%
+
+if "%debug%" == "true" echo %CLASSPATH%;%LAUNCH_JAR%;%MODULE_JARS%
+if "%debug%" == "true" echo %COMMAND%
+%COMMAND%
+
+:end
diff --git a/qpid/java/broker/bin/qpid.start b/qpid/java/broker/bin/qpid.start
new file mode 100755
index 0000000000..78c34e70b4
--- /dev/null
+++ b/qpid/java/broker/bin/qpid.start
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+exec qpid-server -run:debug "$@" \ No newline at end of file
diff --git a/qpid/java/broker/bin/qpid.stop b/qpid/java/broker/bin/qpid.stop
new file mode 100755
index 0000000000..316f8dff46
--- /dev/null
+++ b/qpid/java/broker/bin/qpid.stop
@@ -0,0 +1,178 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# qpid.stop Script
+#
+# Script checks for a given pid running DEFAULT_SEARCH and attempts to quit it
+#
+
+MAX_ATTEMPTS=2
+SLEEP_DELAY=1
+DEFAULT_SEARCH="PNAME=QPBRKR"
+
+if [ -z "$QPID_STOP_SEARCH" ]; then
+ SEARCH=$DEFAULT_SEARCH;
+else
+ SEARCH=$QPID_STOP_SEARCH;
+fi
+
+#
+# Forcably Quit the specified PID($1)
+#
+forceQuit()
+{
+kill -9 $1
+}
+
+#
+# Gracefully ask the PID($1) to quit
+#
+quit()
+{
+kill $1
+}
+
+#
+# grep for the session ID ($1) and return 0 for successful quit and 1 for process alive
+#
+lookup_pid()
+{
+result=`ps -e | grep $1 | wc -l`
+}
+
+#
+# grep ps for all instances of $SEARCH for the current user and collect PIDs
+#
+lookup_all_pids()
+{
+pids=`pgrep -f -U $USER $SEARCH`
+result_all=`echo -n $pids | wc -w`
+}
+
+#
+# check that the PID passed in is for a Qpid broker owned by this user and alive
+#
+validate_pid()
+{
+result=`pgrep -fl $SEARCH | grep $1 | wc -l`
+}
+
+#
+# Show the PS output for given set of pids
+#
+showPids()
+{
+ps -o user,pid,args -p $pids
+}
+
+#
+# Sleep and then check then lookup the PID($1) to ensure it has quit
+#
+check()
+{
+echo "Waiting $SLEEP_DELAY second for $1 to exit"
+sleep $SLEEP_DELAY
+lookup_pid $1
+}
+
+#
+# Verify the PID($1) is available
+#
+verifyPid()
+{
+validate_pid $1
+if [[ $[$result] == 1 ]] ; then
+ brokerspid=$1
+else
+ echo "Unable to locate Qpid Broker Process with PID $1. Check PID and try again."
+ exit -1
+fi
+}
+
+#
+# Stops all Qpid brokers for current user
+#
+qpid_stopall_brokers()
+{
+for pid in $pids ; do
+ lookup_pid $pid;
+ brokerspid=$pid;
+ stop_broker $pid;
+done
+}
+
+#
+# Stops Qpid broker with brokerspid id
+#
+stop_broker()
+{
+# Attempt to quit the process MAX_ATTEMPTS Times
+attempt=0
+while [[ $[$result] > 0 && $[$attempt] < $[$MAX_ATTEMPTS] ]] ; do
+ quit $brokerspid
+ check $brokerspid
+ attempt=$[$attempt + 1]
+done
+
+# Check that it has quit
+if [[ $[$result] == 0 ]] ; then
+ echo "Process quit"
+else
+
+ attempt=0
+ # Now attempt to force quit the process
+ while [[ $[$result] > 0 && $[$attempt] < $[$MAX_ATTEMPTS] ]] ; do
+ forceQuit $brokerspid
+ check $brokerspid
+ attempt=$[$attempt + 1]
+ done
+
+ # Output final status
+ if [[ $[$result] > 0 && $[$attempt] == $[$MAX_ATTEMPTS] ]] ; then
+ echo "Stopped trying to kill process: $brokerspid"
+ echo "Attempted to stop $attempt times"
+ else
+ echo "Done "
+ fi
+fi
+
+}
+
+#
+# Main Run
+#
+
+# Check if we are killing all qpid pids or just one.
+# Now uses local function qpid_stopall_brokers
+if [[ $# == 0 ]] ; then
+ lookup_all_pids
+ if [[ $[$result_all] > 0 ]] ; then
+ echo "Killing All Qpid Brokers for user: '$USER'"
+ qpid_stopall_brokers
+ else
+ echo "No Qpid Brokers found running for user: " $USER
+ fi
+ exit $result
+else
+ verifyPid $1
+ stop_broker
+ exit $result
+fi
+
diff --git a/qpid/java/broker/bin/qpid.stopall b/qpid/java/broker/bin/qpid.stopall
new file mode 100755
index 0000000000..b0ad506629
--- /dev/null
+++ b/qpid/java/broker/bin/qpid.stopall
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# qpid.stopall script
+#
+# Script attempts to stop all PROGRAMs running under the current user
+# Utilises qpid.stop to perform the actual stopping
+#
+
+qpid.stop $*
diff --git a/qpid/java/broker/build.xml b/qpid/java/broker/build.xml
new file mode 100644
index 0000000000..26dcde2918
--- /dev/null
+++ b/qpid/java/broker/build.xml
@@ -0,0 +1,57 @@
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ - KIND, either express or implied. See the License for the
+ - specific language governing permissions and limitations
+ - under the License.
+ -
+ -->
+<project name="AMQ Broker" default="build">
+
+ <property name="module.depends" value="common"/>
+ <property name="module.main" value="org.apache.qpid.server.Main"/>
+
+ <import file="../module.xml"/>
+
+ <property name="output.dir" value="${module.precompiled}/org/apache/qpid/server/filter/jms/selector"/>
+
+
+ <target name="precompile">
+ <mkdir dir="${output.dir}"/>
+ <javacc target="src/main/grammar/SelectorParser.jj"
+ outputdirectory="${output.dir}"
+ javacchome="${project.root}/lib"/>
+ </target>
+
+ <target name="copy-etc-release" if="module.etc.exists" description="copy etc directory if it exists to build tree">
+ <copy todir="${module.release}/etc" failonerror="false" flatten="true">
+ <fileset dir="${module.etc}" excludes="*.conf,*.jpp"/>
+ </copy>
+ </target>
+
+ <target name="copy-bin-release" description="copy dependencies into module release">
+ <copy todir="${module.release}/bin" failonerror="true">
+ <fileset dir="${module.bin}"/>
+ </copy>
+ <copy todir="${module.release}/bin" failonerror="true" flatten="true">
+ <fileset dir="${basedir}/../common/bin"/>
+ </copy>
+ <chmod dir="${module.release}/bin" perm="ugo+rx" includes="**/*"/>
+
+ </target>
+
+ <target name="release-bin" depends="release-bin-tasks"/>
+
+</project>
diff --git a/qpid/java/broker/etc/access b/qpid/java/broker/etc/access
new file mode 100644
index 0000000000..58b7443fa9
--- /dev/null
+++ b/qpid/java/broker/etc/access
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+guest:localhost(rw),test(rw) \ No newline at end of file
diff --git a/qpid/java/broker/etc/acl.config.xml b/qpid/java/broker/etc/acl.config.xml
new file mode 100644
index 0000000000..a2b723fc63
--- /dev/null
+++ b/qpid/java/broker/etc/acl.config.xml
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ - KIND, either express or implied. See the License for the
+ - specific language governing permissions and limitations
+ - under the License.
+ -
+ -->
+<broker>
+ <prefix>${QPID_HOME}</prefix>
+ <work>${QPID_WORK}</work>
+ <conf>${prefix}/etc</conf>
+ <connector>
+ <!-- Uncomment out this block and edit the keystorePath and keystorePassword
+ to enable SSL support
+ <ssl>
+ <enabled>true</enabled>
+ <sslOnly>true</sslOnly>
+ <keystorePath>/path/to/keystore.ks</keystorePath>
+ <keystorePassword>keystorepass</keystorePassword>
+ </ssl>-->
+ <qpidnio>false</qpidnio>
+ <!-- I've had the 0.0 and 0.1 Reader threads continually throwing IOException when client closes-->
+ <protectio>false</protectio>
+ <transport>nio</transport>
+ <port>5672</port>
+ <sslport>8672</sslport>
+ <socketReceiveBuffer>32768</socketReceiveBuffer>
+ <socketSendBuffer>32768</socketSendBuffer>
+ </connector>
+ <management>
+ <enabled>false</enabled>
+ <jmxport>8999</jmxport>
+ <security-enabled>false</security-enabled>
+ </management>
+ <advanced>
+ <filterchain enableExecutorPool="true"/>
+ <enablePooledAllocator>false</enablePooledAllocator>
+ <enableDirectBuffers>false</enableDirectBuffers>
+ <framesize>65535</framesize>
+ <compressBufferOnQueue>false</compressBufferOnQueue>
+ <enableJMSXUserID>false</enableJMSXUserID>
+ </advanced>
+
+ <security>
+ <principal-databases>
+ <!-- Example use of Base64 encoded MD5 hashes for authentication via CRAM-MD5-Hashed -->
+ <principal-database>
+ <name>passwordfile</name>
+ <class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>
+ <attributes>
+ <attribute>
+ <name>passwordFile</name>
+ <value>${conf}/passwd</value>
+ </attribute>
+ </attributes>
+ </principal-database>
+ </principal-databases>
+
+ <access>
+ <class>org.apache.qpid.server.security.access.plugins.DenyAll</class>
+ </access>
+
+ <jmx>
+ <access>${conf}/jmxremote.access</access>
+ <principal-database>passwordfile</principal-database>
+ </jmx>
+ </security>
+
+ <virtualhosts>
+ <directory>${conf}/virtualhosts</directory>
+
+ <virtualhost>
+ <name>test</name>
+ <test>
+ <store>
+ <class>org.apache.qpid.server.store.MemoryMessageStore</class>
+ </store>
+
+ <queues>
+ <exchange>amq.direct</exchange>
+ <!-- 4Mb -->
+ <maximumQueueDepth>4235264</maximumQueueDepth>
+ <!-- 2Mb -->
+ <maximumMessageSize>2117632</maximumMessageSize>
+ <!-- 10 mins -->
+ <maximumMessageAge>600000</maximumMessageAge>
+ </queues>
+
+
+ <security>
+ <access>
+ <class>org.apache.qpid.server.security.access.plugins.SimpleXML</class>
+ </access>
+ <access_control_list>
+ <!-- This section grants pubish rights to an exchange + routing key pair -->
+ <publish>
+ <exchanges>
+ <exchange>
+ <name>amq.direct</name>
+ <routing_keys>
+
+ <!-- Allow clients to publish requests -->
+ <routing_key>
+ <value>example.RequestQueue</value>
+ <users>
+ <user>client</user>
+ </users>
+ </routing_key>
+
+ <!-- Allow the processor to respond to a client on their Temporary Topic -->
+ <routing_key>
+ <value>tmp_*</value>
+ <users>
+ <user>server</user>
+ </users>
+ </routing_key>
+ <routing_key>
+ <value>TempQueue*</value>
+ <users>
+ <user>server</user>
+ </users>
+ </routing_key>
+ </routing_keys>
+
+ </exchange>
+ </exchanges>
+ </publish>
+
+ <!-- This section grants users the ability to consume from the broker -->
+ <consume>
+ <queues>
+
+ <!-- Allow the clients to consume from their temporary queues-->
+ <queue>
+ <temporary/>
+ <users>
+ <user>client</user>
+ </users>
+ </queue>
+
+
+ <!-- Only allow the server to consume from the Request Queue-->
+ <queue>
+ <name>example.RequestQueue</name>
+ <users>
+ <user>server</user>
+ </users>
+ </queue>
+
+
+ </queues>
+ </consume>
+
+ <!-- This section grants clients the ability to create queues and exchanges -->
+ <create>
+ <queues>
+ <!-- Allow clients to create temporary queues-->
+ <queue>
+ <temporary/>
+ <exchanges>
+ <exchange>
+ <name>amq.direct</name>
+ <users>
+ <user>client</user>
+ </users>
+ </exchange>
+ </exchanges>
+ </queue>
+ <!-- Allow the server to create the Request Queue-->
+ <queue>
+ <name>example.RequestQueue</name>
+ <users>
+ <user>server</user>
+ </users>
+ </queue>
+
+ </queues>
+ </create>
+
+
+ </access_control_list>
+
+ </security>
+ </test>
+ </virtualhost>
+
+
+ <virtualhost>
+ <name>development</name>
+ <development>
+ <store>
+ <class>org.apache.qpid.server.store.MemoryMessageStore</class>
+ </store>
+ </development>
+ </virtualhost>
+
+ <virtualhost>
+ <name>localhost</name>
+ <localhost>
+ <store>
+ <class>org.apache.qpid.server.store.MemoryMessageStore</class>
+ </store>
+ </localhost>
+ </virtualhost>
+
+ </virtualhosts>
+
+ <heartbeat>
+ <delay>0</delay>
+ <timeoutFactor>2.0</timeoutFactor>
+ </heartbeat>
+
+ <virtualhosts>${conf}/virtualhosts.xml</virtualhosts>
+</broker>
+
+
diff --git a/qpid/java/broker/etc/config.xml b/qpid/java/broker/etc/config.xml
new file mode 100644
index 0000000000..e0045c1e74
--- /dev/null
+++ b/qpid/java/broker/etc/config.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ - KIND, either express or implied. See the License for the
+ - specific language governing permissions and limitations
+ - under the License.
+ -
+ -->
+<broker>
+ <prefix>${QPID_HOME}</prefix>
+ <work>${QPID_WORK}</work>
+ <conf>${prefix}/etc</conf>
+ <connector>
+ <!-- Uncomment out this block and edit the keystorePath and keystorePassword
+ to enable SSL support
+ <ssl>
+ <enabled>true</enabled>
+ <sslOnly>true</sslOnly>
+ <keystorePath>/path/to/keystore.ks</keystorePath>
+ <keystorePassword>keystorepass</keystorePassword>
+ </ssl>-->
+ <qpidnio>false</qpidnio>
+ <protectio>
+ <enabled>false</enabled>
+ </protectio>
+ <transport>nio</transport>
+ <port>5672</port>
+ <sslport>8672</sslport>
+ <socketReceiveBuffer>32768</socketReceiveBuffer>
+ <socketSendBuffer>32768</socketSendBuffer>
+ </connector>
+ <management>
+ <enabled>true</enabled>
+ <jmxport>8999</jmxport>
+ <ssl>
+ <enabled>true</enabled>
+ <!-- Update below path to your keystore location, eg ${conf}/qpid.keystore -->
+ <keyStorePath>${prefix}/../test_resources/ssl/keystore.jks</keyStorePath>
+ <keyStorePassword>password</keyStorePassword>
+ </ssl>
+ </management>
+ <advanced>
+ <filterchain enableExecutorPool="true"/>
+ <enablePooledAllocator>false</enablePooledAllocator>
+ <enableDirectBuffers>false</enableDirectBuffers>
+ <framesize>65535</framesize>
+ <compressBufferOnQueue>false</compressBufferOnQueue>
+ <enableJMSXUserID>false</enableJMSXUserID>
+ </advanced>
+
+ <security>
+ <principal-databases>
+ <!-- Example use of Base64 encoded MD5 hashes for authentication via CRAM-MD5-Hashed -->
+ <principal-database>
+ <name>passwordfile</name>
+ <class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>
+ <attributes>
+ <attribute>
+ <name>passwordFile</name>
+ <value>${conf}/passwd</value>
+ </attribute>
+ </attributes>
+ </principal-database>
+ </principal-databases>
+
+ <access>
+ <class>org.apache.qpid.server.security.access.plugins.AllowAll</class>
+ </access>
+
+ <msg-auth>false</msg-auth>
+
+ <jmx>
+ <access>${conf}/jmxremote.access</access>
+ <principal-database>passwordfile</principal-database>
+ </jmx>
+ </security>
+
+ <virtualhosts>
+ <directory>${conf}/virtualhosts</directory>
+
+ <virtualhost>
+ <name>localhost</name>
+ <localhost>
+ <store>
+ <class>org.apache.qpid.server.store.MemoryMessageStore</class>
+ </store>
+
+ <housekeeping>
+ <expiredMessageCheckPeriod>20000</expiredMessageCheckPeriod>
+ </housekeeping>
+
+ </localhost>
+ </virtualhost>
+
+ <virtualhost>
+ <name>development</name>
+ <development>
+ <store>
+ <class>org.apache.qpid.server.store.MemoryMessageStore</class>
+ </store>
+ </development>
+ </virtualhost>
+
+ <virtualhost>
+ <name>test</name>
+ <test>
+ <store>
+ <class>org.apache.qpid.server.store.MemoryMessageStore</class>
+ </store>
+ </test>
+ </virtualhost>
+
+ </virtualhosts>
+ <heartbeat>
+ <delay>0</delay>
+ <timeoutFactor>2.0</timeoutFactor>
+ </heartbeat>
+ <queue>
+ <auto_register>true</auto_register>
+ </queue>
+
+ <virtualhosts>${conf}/virtualhosts.xml</virtualhosts>
+</broker>
+
+
diff --git a/qpid/java/broker/etc/debug.log4j.xml b/qpid/java/broker/etc/debug.log4j.xml
new file mode 100644
index 0000000000..fc0bd9f34f
--- /dev/null
+++ b/qpid/java/broker/etc/debug.log4j.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ - KIND, either express or implied. See the License for the
+ - specific language governing permissions and limitations
+ - under the License.
+ -
+ --><!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="null" threshold="null">
+ <appender class="org.apache.log4j.QpidCompositeRollingAppender" name="ArchivingFileAppender">
+ <!-- Ensure that logs allways have the dateFormat set-->
+ <param name="StaticLogFileName" value="false"/>
+ <param name="File" value="${QPID_WORK}/log/${logprefix}qpid${logsuffix}.log"/>
+ <param name="Append" value="false"/>
+ <!-- Change the direction so newer files have bigger numbers -->
+ <!-- So log.1 is written then log.2 etc This prevents a lot of file renames at log rollover -->
+ <param name="CountDirection" value="1"/>
+ <!-- Use default 10MB -->
+ <!--param name="MaxFileSize" value="100000"/-->
+ <param name="DatePattern" value="'.'yyyy-MM-dd-HH-mm"/>
+ <!-- Unlimited number of backups -->
+ <param name="MaxSizeRollBackups" value="-1"/>
+ <!-- Compress(gzip) the backup files-->
+ <param name="CompressBackupFiles" value="true"/>
+ <!-- Compress the backup files using a second thread -->
+ <param name="CompressAsync" value="true"/>
+ <!-- Start at zero numbered files-->
+ <param name="ZeroBased" value="true"/>
+ <!-- Backup Location -->
+ <param name="backupFilesToPath" value="${QPID_WORK}/backup/log"/>
+
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
+ </layout>
+ </appender>
+
+ <appender class="org.apache.log4j.FileAppender" name="FileAppender">
+ <param name="File" value="${QPID_WORK}/log/${logprefix}qpid${logsuffix}.log"/>
+ <param name="Append" value="false"/>
+
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
+ </layout>
+ </appender>
+
+ <appender class="org.apache.log4j.FileAppender" name="AlertFile">
+ <param name="File" value="${QPID_WORK}/log/alert.log"/>
+ <param name="Append" value="false"/>
+
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
+ </layout>
+ </appender>
+
+ <appender class="org.apache.log4j.ConsoleAppender" name="STDOUT">
+
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
+ </layout>
+ </appender>
+
+ <category additivity="true" name="Qpid.Broker">
+ <priority value="debug"/>
+ <appender-ref ref="AlertFile"/>
+ <!--appender-ref ref="STDOUT"/-->
+ </category>
+
+
+ <category additivity="true" name="org.apache.qpid.server.queue.AMQQueueMBean">
+ <priority value="info"/>
+ <appender-ref ref="AlertFile"/>
+ </category>
+
+
+ <!-- Provide warnings to standard output -->
+ <!--category additivity="true" name="org.apache.qpid">
+ <priority value="warn"/>
+ <appender-ref ref="STDOUT"/>
+ </category-->
+
+
+ <!-- Additional level settings for debugging -->
+ <!-- Each class in the Broker is a category that can have its logging level adjusted. -->
+ <!-- This will provide more details if available about that classes processing. -->
+ <!--category additivity="true" name="org.apache.qpid.server.txn">
+ <priority value="debug"/>
+ </category>-->
+
+ <!--<category additivity="true" name="org.apache.qpid.server.store">
+ <priority value="debug"/>
+ </category-->
+
+ <!-- Log all info events to file -->
+ <root>
+ <priority value="debug"/>
+ <appender-ref ref="STDOUT"/>
+ <!--appender-ref ref="FileAppender"/-->
+ </root>
+
+</log4j:configuration>
diff --git a/qpid/java/broker/etc/jmxremote.access b/qpid/java/broker/etc/jmxremote.access
new file mode 100644
index 0000000000..1a51a6991b
--- /dev/null
+++ b/qpid/java/broker/etc/jmxremote.access
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+#Generated by JMX Console : Last edited by user:admin
+#Tue Jun 12 16:46:39 BST 2007
+admin=admin
+guest=readonly
+user=readwrite
diff --git a/qpid/java/broker/etc/log4j.xml b/qpid/java/broker/etc/log4j.xml
new file mode 100644
index 0000000000..a395d0fd56
--- /dev/null
+++ b/qpid/java/broker/etc/log4j.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ - KIND, either express or implied. See the License for the
+ - specific language governing permissions and limitations
+ - under the License.
+ -
+ --><!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="null" threshold="null">
+ <appender class="org.apache.log4j.QpidCompositeRollingAppender" name="ArchivingFileAppender">
+ <!-- Ensure that logs allways have the dateFormat set-->
+ <param name="StaticLogFileName" value="false"/>
+ <param name="File" value="${QPID_WORK}/log/${logprefix}qpid${logsuffix}.log"/>
+ <param name="Append" value="false"/>
+ <!-- Change the direction so newer files have bigger numbers -->
+ <!-- So log.1 is written then log.2 etc This prevents a lot of file renames at log rollover -->
+ <param name="CountDirection" value="1"/>
+ <!-- Use default 10MB -->
+ <!--param name="MaxFileSize" value="100000"/-->
+ <param name="DatePattern" value="'.'yyyy-MM-dd-HH-mm"/>
+ <!-- Unlimited number of backups -->
+ <param name="MaxSizeRollBackups" value="-1"/>
+ <!-- Compress(gzip) the backup files-->
+ <param name="CompressBackupFiles" value="true"/>
+ <!-- Compress the backup files using a second thread -->
+ <param name="CompressAsync" value="true"/>
+ <!-- Start at zero numbered files-->
+ <param name="ZeroBased" value="true"/>
+ <!-- Backup Location -->
+ <param name="backupFilesToPath" value="${QPID_WORK}/backup/log"/>
+
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
+ </layout>
+ </appender>
+
+ <appender class="org.apache.log4j.FileAppender" name="FileAppender">
+ <param name="File" value="${QPID_WORK}/log/${logprefix}qpid${logsuffix}.log"/>
+ <param name="Append" value="false"/>
+
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
+ </layout>
+ </appender>
+
+ <appender class="org.apache.log4j.ConsoleAppender" name="STDOUT">
+
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
+ </layout>
+ </appender>
+
+ <!-- Qpid.Broker log is a special log category used to log only useful broker startup details -->
+ <category additivity="true" name="Qpid.Broker">
+ <priority value="debug"/>
+ <appender-ref ref="STDOUT"/>
+ </category>
+
+ <category additivity="true" name="org.apache.qpid.server.queue.AMQQueueMBean">
+ <priority value="info"/>
+ </category>
+
+ <!-- Provide warnings to standard output -->
+ <category additivity="true" name="org.apache.qpid">
+ <priority value="warn"/>
+ </category>
+
+
+ <!-- Examples of additional logging settings -->
+ <!-- Used to generate extra debug. See debug.log4j.xml -->
+
+ <!--<category additivity="true" name="org.apache.qpid.server.store">
+ <priority value="debug"/>
+ </category-->
+
+
+ <!-- Log all info events to file -->
+ <root>
+ <priority value="info"/>
+ <appender-ref ref="FileAppender"/>
+ <!--appender-ref ref="ArchivingFileAppender"/-->
+ </root>
+
+</log4j:configuration>
diff --git a/qpid/java/broker/etc/md5passwd b/qpid/java/broker/etc/md5passwd
new file mode 100644
index 0000000000..6a149919de
--- /dev/null
+++ b/qpid/java/broker/etc/md5passwd
@@ -0,0 +1,21 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+guest:CE4DQ6BIb/BVMN9scFyLtA==
+admin:ISMvKXpXpadDiUoOSoAfww==
+user:aBzonUodYLhwSa8s9A10sA==
diff --git a/qpid/java/broker/etc/mstool-log4j.xml b/qpid/java/broker/etc/mstool-log4j.xml
new file mode 100644
index 0000000000..8c46010e2d
--- /dev/null
+++ b/qpid/java/broker/etc/mstool-log4j.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ - KIND, either express or implied. See the License for the
+ - specific language governing permissions and limitations
+ - under the License.
+ -
+ -->
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+ <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
+
+ <layout class="org.apache.log4j.PatternLayout">
+ <!--param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/-->
+ <param name="ConversionPattern" value="%d %-5p [%t] (%F:%L) - %m%n"/>
+ </layout>
+ </appender>
+
+ <category name="org.apache.qpid.tools">
+ <priority value="info"/>
+ </category>
+
+ <category name="org.apache.qpid">
+ <priority value="error"/>
+ </category>
+
+ <category name="org.apache.qpid.server.security">
+ <priority value="error"/>
+ </category>
+
+ <category name="org.apache.qpid.server.management">
+ <priority value="error"/>
+ </category>
+
+
+ <root>
+ <priority value="info"/>
+ <appender-ref ref="STDOUT"/>
+ </root>
+</log4j:configuration>
diff --git a/qpid/java/broker/etc/passwd b/qpid/java/broker/etc/passwd
new file mode 100644
index 0000000000..7aca438551
--- /dev/null
+++ b/qpid/java/broker/etc/passwd
@@ -0,0 +1,22 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+guest:guest
+client:guest
+server:guest
+
diff --git a/qpid/java/broker/etc/passwdVhost b/qpid/java/broker/etc/passwdVhost
new file mode 100644
index 0000000000..48ce8299b6
--- /dev/null
+++ b/qpid/java/broker/etc/passwdVhost
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+guest:guest:localhost,test
diff --git a/qpid/java/broker/etc/persistent_config.xml b/qpid/java/broker/etc/persistent_config.xml
new file mode 100644
index 0000000000..67ef28117d
--- /dev/null
+++ b/qpid/java/broker/etc/persistent_config.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ - KIND, either express or implied. See the License for the
+ - specific language governing permissions and limitations
+ - under the License.
+ -
+
+ This is an example config using the BDBMessageStore available from
+ the Red Hat Messaging project at etp.108.redhat.com and distributed under GPL.
+ -->
+
+<broker>
+ <prefix>${QPID_HOME}</prefix>
+ <work>${QPID_WORK}</work>
+ <conf>${prefix}/etc</conf>
+ <connector>
+ <transport>nio</transport>
+ <port>5672</port>
+ <sslport>8672</sslport>
+ <socketReceiveBuffer>32768</socketReceiveBuffer>
+ <socketSendBuffer>32768</socketSendBuffer>
+ </connector>
+ <management>
+ <enabled>true</enabled>
+ <jmxport>8999</jmxport>
+ <ssl>
+ <enabled>true</enabled>
+ <!-- Update below path to your keystore location, eg ${conf}/qpid.keystore -->
+ <keyStorePath>${prefix}/../test_resources/ssl/keystore.jks</keyStorePath>
+ <keyStorePassword>password</keyStorePassword>
+ </ssl>
+ </management>
+ <advanced>
+ <filterchain enableExecutorPool="true"/>
+ <enablePooledAllocator>false</enablePooledAllocator>
+ <enableDirectBuffers>false</enableDirectBuffers>
+ <framesize>65535</framesize>
+ <compressBufferOnQueue>false</compressBufferOnQueue>
+ </advanced>
+
+ <security>
+ <principal-databases>
+ <principal-database>
+ <name>passwordfile</name>
+ <class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>
+ <attributes>
+ <attribute>
+ <name>passwordFile</name>
+ <value>${conf}/passwd</value>
+ </attribute>
+ </attributes>
+ </principal-database>
+ </principal-databases>
+
+ <access>
+ <class>org.apache.qpid.server.security.access.plugins.AllowAll</class>
+ </access>
+ <jmx>
+ <access>${conf}/jmxremote.access</access>
+ <principal-database>passwordfile</principal-database>
+ </jmx>
+ </security>
+
+ <virtualhosts>
+ <virtualhost>
+ <name>localhost</name>
+ <localhost>
+ <store>
+ <class>org.apache.qpid.server.store.berkeleydb.BDBMessageStore</class>
+ <environment-path>${work}/bdbstore/localhost-store</environment-path>
+ </store>
+ </localhost>
+ </virtualhost>
+
+ <virtualhost>
+ <name>development</name>
+ <development>
+ <store>
+ <class>org.apache.qpid.server.store.berkeleydb.BDBMessageStore</class>
+ <environment-path>${work}/bdbstore/development-store</environment-path>
+ </store>
+ </development>
+ </virtualhost>
+
+ <virtualhost>
+ <name>test</name>
+ <test>
+ <store>
+ <class>org.apache.qpid.server.store.berkeleydb.BDBMessageStore</class>
+ <environment-path>${work}/bdbstore/test-store</environment-path>
+ </store>
+ </test>
+ </virtualhost>
+
+ </virtualhosts>
+ <heartbeat>
+ <delay>0</delay>
+ <timeoutFactor>2.0</timeoutFactor>
+ </heartbeat>
+ <queue>
+ <auto_register>true</auto_register>
+ </queue>
+
+ <virtualhosts>${conf}/virtualhosts.xml</virtualhosts>
+</broker>
+
+
diff --git a/qpid/java/broker/etc/qpid-server.conf b/qpid/java/broker/etc/qpid-server.conf
new file mode 100644
index 0000000000..8a16849b04
--- /dev/null
+++ b/qpid/java/broker/etc/qpid-server.conf
@@ -0,0 +1,25 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+QPID_LIBS=$QPID_HOME/lib/qpid-all.jar:$QPID_HOME/lib/bdbstore-launch.jar
+
+export JAVA=java \
+ JAVA_VM=-server \
+ JAVA_MEM=-Xmx1024m \
+ CLASSPATH=$QPID_LIBS
diff --git a/qpid/java/broker/etc/qpid-server.conf.jpp b/qpid/java/broker/etc/qpid-server.conf.jpp
new file mode 100644
index 0000000000..3ed2431ef3
--- /dev/null
+++ b/qpid/java/broker/etc/qpid-server.conf.jpp
@@ -0,0 +1,49 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+QPID_LIBS=$(build-classpath backport-util-concurrent \
+ commons-beanutils \
+ commons-beanutils-core \
+ commons-cli \
+ commons-codec \
+ commons-collections \
+ commons-configuration \
+ commons-digester \
+ commons-lang \
+ commons-logging \
+ commons-logging-api \
+ dom4j \
+ geronimo-jms-1.1-api \
+ isorelax \
+ jaxen \
+ log4j \
+ mina/core \
+ mina/filter-ssl \
+ mina/java5 \
+ msv-msv \
+ qpid-broker \
+ qpid-client \
+ qpid-common \
+ relaxngDatatype \
+ slf4j)
+
+export JAVA=java \
+ JAVA_VM=-server \
+ JAVA_MEM=-Xmx1024m \
+ CLASSPATH=$QPID_LIBS
diff --git a/qpid/java/broker/etc/qpid.passwd b/qpid/java/broker/etc/qpid.passwd
new file mode 100644
index 0000000000..dbfb9d1923
--- /dev/null
+++ b/qpid/java/broker/etc/qpid.passwd
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+guest:CE4DQ6BIb/BVMN9scFyLtA==
+admin:ISMvKXpXpadDiUoOSoAfww==
+user:CE4DQ6BIb/BVMN9scFyLtA==
+server:CE4DQ6BIb/BVMN9scFyLtA==
+client:CE4DQ6BIb/BVMN9scFyLtA==
diff --git a/qpid/java/broker/etc/transient_config.xml b/qpid/java/broker/etc/transient_config.xml
new file mode 100644
index 0000000000..a21afe7d21
--- /dev/null
+++ b/qpid/java/broker/etc/transient_config.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ - KIND, either express or implied. See the License for the
+ - specific language governing permissions and limitations
+ - under the License.
+ -
+
+ This is an example config file that uses the MemoryMessageStore.
+ As a result it is aimed at brokers sending transient messages.
+
+ -->
+<broker>
+ <prefix>${QPID_HOME}</prefix>
+ <work>${QPID_WORK}</work>
+ <conf>${prefix}/etc</conf>
+ <connector>
+ <transport>nio</transport>
+ <port>5672</port>
+ <sslport>8672</sslport>
+ <socketReceiveBuffer>32768</socketReceiveBuffer>
+ <socketSendBuffer>32768</socketSendBuffer>
+ </connector>
+ <management>
+ <enabled>true</enabled>
+ <jmxport>8999</jmxport>
+ <ssl>
+ <enabled>true</enabled>
+ <!-- Update below path to your keystore location, eg ${conf}/qpid.keystore -->
+ <keyStorePath>${prefix}/../test_resources/ssl/keystore.jks</keyStorePath>
+ <keyStorePassword>password</keyStorePassword>
+ </ssl>
+ </management>
+ <advanced>
+ <filterchain enableExecutorPool="true"/>
+ <enablePooledAllocator>false</enablePooledAllocator>
+ <enableDirectBuffers>false</enableDirectBuffers>
+ <framesize>65535</framesize>
+ <compressBufferOnQueue>false</compressBufferOnQueue>
+ </advanced>
+
+ <security>
+ <principal-databases>
+ <principal-database>
+ <name>passwordfile</name>
+ <class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>
+ <attributes>
+ <attribute>
+ <name>passwordFile</name>
+ <value>${conf}/passwd</value>
+ </attribute>
+ </attributes>
+ </principal-database>
+ </principal-databases>
+
+ <access>
+ <class>org.apache.qpid.server.security.access.plugins.AllowAll</class>
+ </access>
+ <jmx>
+ <access>${conf}/jmxremote.access</access>
+ <principal-database>passwordfile</principal-database>
+ </jmx>
+ </security>
+
+ <virtualhosts>
+ <virtualhost>
+ <name>localhost</name>
+ <localhost>
+ <store>
+ <class>org.apache.qpid.server.store.MemoryMessageStore</class>
+ </store>
+ </localhost>
+ </virtualhost>
+
+ <virtualhost>
+ <name>development</name>
+ <development>
+ <store>
+ <class>org.apache.qpid.server.store.MemoryMessageStore</class>
+ </store>
+ </development>
+ </virtualhost>
+
+ <virtualhost>
+ <name>test</name>
+ <test>
+ <store>
+ <class>org.apache.qpid.server.store.MemoryMessageStore</class>
+ </store>
+ </test>
+ </virtualhost>
+
+ </virtualhosts>
+ <heartbeat>
+ <delay>0</delay>
+ <timeoutFactor>2.0</timeoutFactor>
+ </heartbeat>
+ <queue>
+ <auto_register>true</auto_register>
+ </queue>
+
+ <virtualhosts>${conf}/virtualhosts.xml</virtualhosts>
+</broker>
+
+
diff --git a/qpid/java/broker/etc/virtualhosts.xml b/qpid/java/broker/etc/virtualhosts.xml
new file mode 100644
index 0000000000..f62ec3f5d7
--- /dev/null
+++ b/qpid/java/broker/etc/virtualhosts.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ - KIND, either express or implied. See the License for the
+ - specific language governing permissions and limitations
+ - under the License.
+ -
+ -->
+<virtualhosts>
+ <default>test</default>
+ <virtualhost>
+ <name>localhost</name>
+ <localhost>
+ <exchanges>
+ <exchange>
+ <type>direct</type>
+ <name>test.direct</name>
+ <durable>true</durable>
+ </exchange>
+ <exchange>
+ <type>topic</type>
+ <name>test.topic</name>
+ </exchange>
+ </exchanges>
+ <queues>
+ <exchange>amq.direct</exchange>
+ <maximumQueueDepth>4235264</maximumQueueDepth> <!-- 4Mb -->
+ <maximumMessageSize>2117632</maximumMessageSize> <!-- 2Mb -->
+ <maximumMessageAge>600000</maximumMessageAge> <!-- 10 mins -->
+
+ <queue>
+ <name>queue</name>
+ </queue>
+ <queue>
+ <name>ping</name>
+ </queue>
+ <queue>
+ <name>test-queue</name>
+ <test-queue>
+ <exchange>test.direct</exchange>
+ <durable>true</durable>
+ </test-queue>
+ </queue>
+ <queue>
+ <name>test-ping</name>
+ <test-ping>
+ <exchange>test.direct</exchange>
+ </test-ping>
+ </queue>
+
+ </queues>
+ </localhost>
+ </virtualhost>
+
+
+ <virtualhost>
+ <name>development</name>
+ <development>
+ <queues>
+ <minimumAlertRepeatGap>30000</minimumAlertRepeatGap>
+ <maximumMessageCount>5000</maximumMessageCount>
+ <queue>
+ <name>queue</name>
+ <queue>
+ <exchange>amq.direct</exchange>
+ <maximumQueueDepth>4235264</maximumQueueDepth> <!-- 4Mb -->
+ <maximumMessageSize>2117632</maximumMessageSize> <!-- 2Mb -->
+ <maximumMessageAge>600000</maximumMessageAge> <!-- 10 mins -->
+ </queue>
+ </queue>
+ <queue>
+ <name>ping</name>
+ <ping>
+ <exchange>amq.direct</exchange>
+ <maximumQueueDepth>4235264</maximumQueueDepth> <!-- 4Mb -->
+ <maximumMessageSize>2117632</maximumMessageSize> <!-- 2Mb -->
+ <maximumMessageAge>600000</maximumMessageAge> <!-- 10 mins -->
+ </ping>
+ </queue>
+ </queues>
+ </development>
+ </virtualhost>
+ <virtualhost>
+ <name>test</name>
+ <test>
+ <queues>
+ <minimumAlertRepeatGap>30000</minimumAlertRepeatGap>
+ <maximumMessageCount>5000</maximumMessageCount>
+ <queue>
+ <name>queue</name>
+ <queue>
+ <exchange>amq.direct</exchange>
+ <maximumQueueDepth>4235264</maximumQueueDepth> <!-- 4Mb -->
+ <maximumMessageSize>2117632</maximumMessageSize> <!-- 2Mb -->
+ <maximumMessageAge>600000</maximumMessageAge> <!-- 10 mins -->
+ </queue>
+ </queue>
+ <queue>
+ <name>ping</name>
+ <ping>
+ <exchange>amq.direct</exchange>
+ <maximumQueueDepth>4235264</maximumQueueDepth> <!-- 4Mb -->
+ <maximumMessageSize>2117632</maximumMessageSize> <!-- 2Mb -->
+ <maximumMessageAge>600000</maximumMessageAge> <!-- 10 mins -->
+ </ping>
+ </queue>
+ </queues>
+ </test>
+ </virtualhost>
+</virtualhosts>
diff --git a/qpid/java/broker/python-test.xml b/qpid/java/broker/python-test.xml
new file mode 100755
index 0000000000..5c263e3169
--- /dev/null
+++ b/qpid/java/broker/python-test.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<!-- ====================================================================== -->
+<!-- Ant build file (http://ant.apache.org/) for Ant 1.6.2 or above. -->
+<!-- ====================================================================== -->
+
+<project basedir="." default="default">
+
+ <target name="default" >
+ <echo message="Used via maven to run python tests."/>
+ </target>
+
+ <dirname property="broker.dir" file="${ant.file.python-test}"/>
+
+ <property name="pythondir" value="${broker.dir}/../../python"/>
+
+ <target name="run-tests" unless="skip-python-tests">
+
+ <echo message="Starting Broker with command"/>
+
+ <java classname="org.apache.qpid.server.RunBrokerWithCommand"
+ fork="true"
+ dir="${pythondir}"
+ failonerror="true"
+ >
+ <arg value="${command}"/>
+ <arg value="-p"/>
+ <arg value="2110"/>
+ <arg value="-m"/>
+ <arg value="2111"/>
+
+ <classpath refid="maven.test.classpath"/>
+ <sysproperty key="QPID_HOME" value="${broker.dir}"/>
+ <sysproperty key="QPID_WORK" value="${broker.dir}${file.separator}target"/>
+ </java>
+
+ </target>
+</project>
diff --git a/qpid/java/broker/scripts/resetAlerting.sh b/qpid/java/broker/scripts/resetAlerting.sh
new file mode 100644
index 0000000000..57a38f3ed1
--- /dev/null
+++ b/qpid/java/broker/scripts/resetAlerting.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+#
+# Alerting Rest Scripts to renabled the alerts on the queue.
+#
+# Defaults to Localhost broker
+#
+
+if [ -z "$QPID_ALERT_HOME" ]; then
+ export QPID_ALERT_HOME=$(dirname $(dirname $(readlink -f $0)))
+ export PATH=${PATH}:${QPID_ALERT_HOME}/bin
+fi
+
+USERNAME=$1
+PASSWORD=$2
+HOSTNAME=$3
+PORT=$4
+
+CLI="$QPID_ALERT_HOME/bin/qpid-cli -h ${HOSTNAME:-localhost} -p ${PORT:-8999}"
+AUTH=
+if [ -n $USERNAME ] ; then
+ if [ "$USERNAME" == "-h" ] ; then
+ echo "resetAlerting.sh: [<username> <password> [<hostname> [<port>]]]"
+ exit 0
+ fi
+ if [ -n $PASSWORD ] ; then
+ AUTH="-u $USERNAME -w $PASSWORD"
+ else
+ echo "Password must be specified with username"
+ fi
+fi
+
+
+OUTPUT=0
+
+runCommand()
+{
+ RET=`$CLI $1 $AUTH`
+}
+
+resetQueue()
+{
+ vhost=$1
+ queue=$2
+ runCommand "get -o queue -v $vhost -n $queue -a MaximumQueueDepth"
+ rawQDepth=$RET
+ # Note that MaxQueueDepth is returned as Kb but set as b!
+ queueDepth=$[ $rawQDepth * 1024 ]
+ runCommand "get -o queue -v $vhost -n $queue -a MaximumMessageAge"
+ messageAge=$RET
+ runCommand "get -o queue -v $vhost -n $queue -a MaximumMessageCount"
+ messageCount=$RET
+ runCommand "get -o queue -v $vhost -n $queue -a MaximumMessageSize"
+ messageSize=$RET
+
+ if [ $OUTPUT == 1 ] ; then
+ echo Current Values:
+ echo MaximumQueueDepth : $queueDepth
+ echo MaximumMessageAge : $messageAge
+ echo MaximumMessageCount : $messageCount
+ echo MaximumMessageSize : $messageSize
+ fi
+
+ runCommand "set -o queue -v $vhost -n $queue -a MaximumMessageSize -s $messageSize"
+ runCommand "set -o queue -v $vhost -n $queue -a MaximumMessageAge -s $messageAge"
+ runCommand "set -o queue -v $vhost -n $queue -a MaximumMessageCount -s $messageCount"
+ runCommand "set -o queue -v $vhost -n $queue -a MaximumQueueDepth -s $queueDepth"
+}
+
+resetVirtualHost()
+{
+ vhost=$1
+ ignore=0
+ for queue in `$CLI list -o queue -v $vhost $AUTH |grep '|' | cut -d '|' -f 1 ` ; do
+
+ if [ $ignore == 0 ] ; then
+ ignore=1
+ else
+ resetQueue $vhost $queue
+ fi
+
+ done
+}
+
+VHOST=`$CLI list -o virtualhost $AUTH`
+COUNT=`echo $VHOST | grep -c VirtualHost`
+if [ $COUNT -gt 0 ] ; then
+ for vhost in `echo $VHOST |grep VirtualHost|cut -d '=' -f 3` ; do
+
+ echo "Resetting alert levels for $vhost";
+ resetVirtualHost $vhost;
+ done
+ echo "Alerting levels reset"
+else
+ echo $VHOST
+fi
diff --git a/qpid/java/broker/src/main/grammar/SelectorParser.jj b/qpid/java/broker/src/main/grammar/SelectorParser.jj
new file mode 100644
index 0000000000..c9e01cd01f
--- /dev/null
+++ b/qpid/java/broker/src/main/grammar/SelectorParser.jj
@@ -0,0 +1,621 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+ //
+ // Original File from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+ //
+
+// ----------------------------------------------------------------------------
+// OPTIONS
+// ----------------------------------------------------------------------------
+options {
+ STATIC = false;
+ UNICODE_INPUT = true;
+
+ // some performance optimizations
+ OPTIMIZE_TOKEN_MANAGER = true;
+ ERROR_REPORTING = false;
+}
+
+// ----------------------------------------------------------------------------
+// PARSER
+// ----------------------------------------------------------------------------
+
+PARSER_BEGIN(SelectorParser)
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.filter.jms.selector;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+
+import org.apache.qpid.AMQInvalidArgumentException;
+import org.apache.qpid.server.filter.ArithmeticExpression;
+import org.apache.qpid.server.filter.BooleanExpression;
+import org.apache.qpid.server.filter.ComparisonExpression;
+import org.apache.qpid.server.filter.ConstantExpression;
+import org.apache.qpid.server.filter.Expression;
+import org.apache.qpid.server.filter.LogicExpression;
+import org.apache.qpid.server.filter.PropertyExpression;
+import org.apache.qpid.server.filter.UnaryExpression;
+
+/**
+ * JMS Selector Parser generated by JavaCC
+ *
+ * Do not edit this .java file directly - it is autogenerated from SelectorParser.jj
+ */
+public class SelectorParser {
+
+ public SelectorParser() {
+ this(new StringReader(""));
+ }
+
+ public BooleanExpression parse(String sql) throws AMQInvalidArgumentException {
+ this.ReInit(new StringReader(sql));
+
+ try {
+ return this.JmsSelector();
+ }
+ catch (Throwable e) {
+ throw (AMQInvalidArgumentException)new AMQInvalidArgumentException(sql,e);
+ }
+
+ }
+
+ private BooleanExpression asBooleanExpression(Expression value) throws ParseException {
+ if (value instanceof BooleanExpression) {
+ return (BooleanExpression) value;
+ }
+ if (value instanceof PropertyExpression) {
+ return UnaryExpression.createBooleanCast( value );
+ }
+ throw new ParseException("Expression will not result in a boolean value: " + value);
+ }
+
+
+}
+
+PARSER_END(SelectorParser)
+
+// ----------------------------------------------------------------------------
+// Tokens
+// ----------------------------------------------------------------------------
+
+/* White Space */
+SPECIAL_TOKEN :
+{
+ " " | "\t" | "\n" | "\r" | "\f"
+}
+
+/* Comments */
+SKIP:
+{
+ <LINE_COMMENT: "--" (~["\n","\r"])* ("\n"|"\r"|"\r\n") >
+}
+
+SKIP:
+{
+ <BLOCK_COMMENT: "/*" (~["*"])* "*" ("*" | (~["*","/"] (~["*"])* "*"))* "/">
+}
+
+/* Reserved Words */
+TOKEN [IGNORE_CASE] :
+{
+ < NOT : "NOT">
+ | < AND : "AND">
+ | < OR : "OR">
+ | < BETWEEN : "BETWEEN">
+ | < LIKE : "LIKE">
+ | < ESCAPE : "ESCAPE">
+ | < IN : "IN">
+ | < IS : "IS">
+ | < TRUE : "TRUE" >
+ | < FALSE : "FALSE" >
+ | < NULL : "NULL" >
+ | < XPATH : "XPATH" >
+ | < XQUERY : "XQUERY" >
+}
+
+/* Literals */
+TOKEN [IGNORE_CASE] :
+{
+
+ < DECIMAL_LITERAL: ["1"-"9"] (["0"-"9"])* (["l","L"])? >
+ | < HEX_LITERAL: "0" ["x","X"] (["0"-"9","a"-"f","A"-"F"])+ >
+ | < OCTAL_LITERAL: "0" (["0"-"7"])* >
+ | < FLOATING_POINT_LITERAL:
+ (["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)? // matches: 5.5 or 5. or 5.5E10 or 5.E10
+ | "." (["0"-"9"])+ (<EXPONENT>)? // matches: .5 or .5E10
+ | (["0"-"9"])+ <EXPONENT> // matches: 5E10
+ >
+ | < #EXPONENT: "E" (["+","-"])? (["0"-"9"])+ >
+ | < STRING_LITERAL: "'" ( ("''") | ~["'"] )* "'" >
+}
+
+TOKEN [IGNORE_CASE] :
+{
+ < ID : ["a"-"z", "_", "$"] (["a"-"z","0"-"9","_", "$"])* >
+ | < QUOTED_ID : "\"" ( ("\"\"") | ~["\""] )* "\"" >
+}
+
+// ----------------------------------------------------------------------------
+// Grammer
+// ----------------------------------------------------------------------------
+BooleanExpression JmsSelector() :
+{
+ Expression left=null;
+}
+{
+ (
+ left = orExpression()
+ )
+ {
+ return asBooleanExpression(left);
+ }
+
+}
+
+Expression orExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ (
+ left = andExpression()
+ (
+ <OR> right = andExpression()
+ {
+ left = LogicExpression.createOR(asBooleanExpression(left), asBooleanExpression(right));
+ }
+ )*
+ )
+ {
+ return left;
+ }
+
+}
+
+
+Expression andExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ (
+ left = equalityExpression()
+ (
+ <AND> right = equalityExpression()
+ {
+ left = LogicExpression.createAND(asBooleanExpression(left), asBooleanExpression(right));
+ }
+ )*
+ )
+ {
+ return left;
+ }
+}
+
+Expression equalityExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ (
+ left = comparisonExpression()
+ (
+
+ "=" right = comparisonExpression()
+ {
+ left = ComparisonExpression.createEqual(left, right);
+ }
+ |
+ "<>" right = comparisonExpression()
+ {
+ left = ComparisonExpression.createNotEqual(left, right);
+ }
+ |
+ LOOKAHEAD(2)
+ <IS> <NULL>
+ {
+ left = ComparisonExpression.createIsNull(left);
+ }
+ |
+ <IS> <NOT> <NULL>
+ {
+ left = ComparisonExpression.createIsNotNull(left);
+ }
+ )*
+ )
+ {
+ return left;
+ }
+}
+
+Expression comparisonExpression() :
+{
+ Expression left;
+ Expression right;
+ Expression low;
+ Expression high;
+ String t, u;
+ boolean not;
+ ArrayList list;
+}
+{
+ (
+ left = addExpression()
+ (
+
+ ">" right = addExpression()
+ {
+ left = ComparisonExpression.createGreaterThan(left, right);
+ }
+ |
+ ">=" right = addExpression()
+ {
+ left = ComparisonExpression.createGreaterThanEqual(left, right);
+ }
+ |
+ "<" right = addExpression()
+ {
+ left = ComparisonExpression.createLessThan(left, right);
+ }
+ |
+ "<=" right = addExpression()
+ {
+ left = ComparisonExpression.createLessThanEqual(left, right);
+ }
+ |
+ {
+ u=null;
+ }
+ <LIKE> t = stringLitteral()
+ [ <ESCAPE> u = stringLitteral() ]
+ {
+ left = ComparisonExpression.createLike(left, t, u);
+ }
+ |
+ LOOKAHEAD(2)
+ {
+ u=null;
+ }
+ <NOT> <LIKE> t = stringLitteral() [ <ESCAPE> u = stringLitteral() ]
+ {
+ left = ComparisonExpression.createNotLike(left, t, u);
+ }
+ |
+ <BETWEEN> low = addExpression() <AND> high = addExpression()
+ {
+ left = ComparisonExpression.createBetween(left, low, high);
+ }
+ |
+ LOOKAHEAD(2)
+ <NOT> <BETWEEN> low = addExpression() <AND> high = addExpression()
+ {
+ left = ComparisonExpression.createNotBetween(left, low, high);
+ }
+ |
+ <IN>
+ "("
+ t = stringLitteral()
+ {
+ list = new ArrayList();
+ list.add( t );
+ }
+ (
+ ","
+ t = stringLitteral()
+ {
+ list.add( t );
+ }
+
+ )*
+ ")"
+ {
+ left = ComparisonExpression.createInFilter(left, list);
+ }
+ |
+ LOOKAHEAD(2)
+ <NOT> <IN>
+ "("
+ t = stringLitteral()
+ {
+ list = new ArrayList();
+ list.add( t );
+ }
+ (
+ ","
+ t = stringLitteral()
+ {
+ list.add( t );
+ }
+
+ )*
+ ")"
+ {
+ left = ComparisonExpression.createNotInFilter(left, list);
+ }
+
+ )*
+ )
+ {
+ return left;
+ }
+}
+
+Expression addExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ left = multExpr()
+ (
+ LOOKAHEAD( ("+"|"-") multExpr())
+ (
+ "+" right = multExpr()
+ {
+ left = ArithmeticExpression.createPlus(left, right);
+ }
+ |
+ "-" right = multExpr()
+ {
+ left = ArithmeticExpression.createMinus(left, right);
+ }
+ )
+
+ )*
+ {
+ return left;
+ }
+}
+
+Expression multExpr() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ left = unaryExpr()
+ (
+ "*" right = unaryExpr()
+ {
+ left = ArithmeticExpression.createMultiply(left, right);
+ }
+ |
+ "/" right = unaryExpr()
+ {
+ left = ArithmeticExpression.createDivide(left, right);
+ }
+ |
+ "%" right = unaryExpr()
+ {
+ left = ArithmeticExpression.createMod(left, right);
+ }
+
+ )*
+ {
+ return left;
+ }
+}
+
+
+Expression unaryExpr() :
+{
+ String s=null;
+ Expression left=null;
+}
+{
+ (
+ LOOKAHEAD( "+" unaryExpr() )
+ "+" left=unaryExpr()
+ |
+ "-" left=unaryExpr()
+ {
+ left = UnaryExpression.createNegate(left);
+ }
+ |
+ <NOT> left=unaryExpr()
+ {
+ left = UnaryExpression.createNOT( asBooleanExpression(left) );
+ }
+ |
+ <XPATH> s=stringLitteral()
+ {
+ left = UnaryExpression.createXPath( s );
+ }
+ |
+ <XQUERY> s=stringLitteral()
+ {
+ left = UnaryExpression.createXQuery( s );
+ }
+ |
+ left = primaryExpr()
+ )
+ {
+ return left;
+ }
+
+}
+
+Expression primaryExpr() :
+{
+ Expression left=null;
+}
+{
+ (
+ left = literal()
+ |
+ left = variable()
+ |
+ "(" left = orExpression() ")"
+ )
+ {
+ return left;
+ }
+}
+
+
+
+ConstantExpression literal() :
+{
+ Token t;
+ String s;
+ ConstantExpression left=null;
+}
+{
+ (
+ (
+ s = stringLitteral()
+ {
+ left = new ConstantExpression(s);
+ }
+ )
+ |
+ (
+ t = <DECIMAL_LITERAL>
+ {
+ left = ConstantExpression.createFromDecimal(t.image);
+ }
+ )
+ |
+ (
+ t = <HEX_LITERAL>
+ {
+ left = ConstantExpression.createFromHex(t.image);
+ }
+ )
+ |
+ (
+ t = <OCTAL_LITERAL>
+ {
+ left = ConstantExpression.createFromOctal(t.image);
+ }
+ )
+ |
+ (
+ t = <FLOATING_POINT_LITERAL>
+ {
+ left = ConstantExpression.createFloat(t.image);
+ }
+ )
+ |
+ (
+ <TRUE>
+ {
+ left = ConstantExpression.TRUE;
+ }
+ )
+ |
+ (
+ <FALSE>
+ {
+ left = ConstantExpression.FALSE;
+ }
+ )
+ |
+ (
+ <NULL>
+ {
+ left = ConstantExpression.NULL;
+ }
+ )
+ )
+ {
+ return left;
+ }
+}
+
+String stringLitteral() :
+{
+ Token t;
+ StringBuffer rc = new StringBuffer();
+ boolean first=true;
+}
+{
+ t = <STRING_LITERAL>
+ {
+ // Decode the sting value.
+ String image = t.image;
+ for( int i=1; i < image.length()-1; i++ ) {
+ char c = image.charAt(i);
+ if( c == '\'' )
+ i++;
+ rc.append(c);
+ }
+ return rc.toString();
+ }
+}
+
+PropertyExpression variable() :
+{
+ Token t;
+ StringBuffer rc = new StringBuffer();
+ PropertyExpression left=null;
+}
+{
+ (
+ t = <ID>
+ {
+ left = new PropertyExpression(t.image);
+ }
+ |
+ t = <QUOTED_ID>
+ {
+ // Decode the sting value.
+ String image = t.image;
+ for( int i=1; i < image.length()-1; i++ ) {
+ char c = image.charAt(i);
+ if( c == '"' )
+ i++;
+ rc.append(c);
+ }
+ return new PropertyExpression(rc.toString());
+ }
+
+
+ )
+ {
+ return left;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/log4j.properties b/qpid/java/broker/src/main/java/log4j.properties
new file mode 100644
index 0000000000..6788c65463
--- /dev/null
+++ b/qpid/java/broker/src/main/java/log4j.properties
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+log4j.rootCategory=${amqj.logging.level}, console
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.Threshold=all
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%t %d %p [%c{4}] %m%n
diff --git a/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java b/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java
new file mode 100644
index 0000000000..7e0c4defe1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java
@@ -0,0 +1,1007 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.log4j;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.log4j.helpers.CountingQuietWriter;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * <p>CompositeRollingAppender combines RollingFileAppender and DailyRollingFileAppender<br> It can function as either
+ * or do both at the same time (making size based rolling files like RollingFileAppender until a data/time boundary is
+ * crossed at which time it rolls all of those files as per the DailyRollingFileAppender) based on the setting for
+ * <code>rollingStyle</code>.<br> <br> To use CompositeRollingAppender to roll log files as they reach a certain size
+ * (like RollingFileAppender), set rollingStyle=1 (@see config.size)<br> To use CompositeRollingAppender to roll log
+ * files at certain time intervals (daily for example), set rollingStyle=2 and a datePattern (@see config.time)<br> To
+ * have CompositeRollingAppender roll log files at a certain size AND rename those according to time intervals, set
+ * rollingStyle=3 (@see config.composite)<br>
+ *
+ * <p>A of few additional optional features have been added:<br> -- Attach date pattern for current log file (@see
+ * staticLogFileName)<br> -- Backup number increments for newer files (@see countDirection)<br> -- Infinite number of
+ * backups by file size (@see maxSizeRollBackups)<br> <br> <p>A few notes and warnings: For large or infinite number of
+ * backups countDirection > 0 is highly recommended, with staticLogFileName = false if time based rolling is also used
+ * -- this will reduce the number of file renamings to few or none. Changing staticLogFileName or countDirection
+ * without clearing the directory could have nasty side effects. If Date/Time based rolling is enabled,
+ * CompositeRollingAppender will attempt to roll existing files in the directory without a date/time tag based on the
+ * last modified date of the base log files last modification.<br> <br> <p>A maximum number of backups based on
+ * date/time boundries would be nice but is not yet implemented.<br>
+ *
+ * @author Kevin Steppe
+ * @author Heinz Richter
+ * @author Eirik Lygre
+ * @author Ceki G&uuml;lc&uuml;
+ * @author Martin Ritchie
+ */
+public class QpidCompositeRollingAppender extends FileAppender
+{
+ // The code assumes that the following 'time' constants are in a increasing
+ // sequence.
+ static final int TOP_OF_TROUBLE = -1;
+ static final int TOP_OF_MINUTE = 0;
+ static final int TOP_OF_HOUR = 1;
+ static final int HALF_DAY = 2;
+ static final int TOP_OF_DAY = 3;
+ static final int TOP_OF_WEEK = 4;
+ static final int TOP_OF_MONTH = 5;
+
+ /** Style of rolling to use */
+ static final int BY_SIZE = 1;
+ static final int BY_DATE = 2;
+ static final int BY_COMPOSITE = 3;
+
+ // Not currently used
+ static final String S_BY_SIZE = "Size";
+ static final String S_BY_DATE = "Date";
+ static final String S_BY_COMPOSITE = "Composite";
+
+ /** The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" meaning daily rollover. */
+ private String datePattern = "'.'yyyy-MM-dd";
+
+ /**
+ * The actual formatted filename that is currently being written to or will be the file transferred to on roll over
+ * (based on staticLogFileName).
+ */
+ private String scheduledFilename = null;
+
+ /** The timestamp when we shall next recompute the filename. */
+ private long nextCheck = System.currentTimeMillis() - 1;
+
+ /** Holds date of last roll over */
+ Date now = new Date();
+
+ SimpleDateFormat sdf;
+
+ /** Helper class to determine next rollover time */
+ RollingCalendar rc = new RollingCalendar();
+
+ /** Current period for roll overs */
+ int checkPeriod = TOP_OF_TROUBLE;
+
+ /** The default maximum file size is 10MB. */
+ protected long maxFileSize = 10 * 1024 * 1024;
+
+ /** There is zero backup files by default. */
+ protected int maxSizeRollBackups = 0;
+ /** How many sized based backups have been made so far */
+ protected int curSizeRollBackups = 0;
+
+ /** not yet implemented */
+ protected int maxTimeRollBackups = -1;
+ protected int curTimeRollBackups = 0;
+
+ /**
+ * By default newer files have lower numbers. (countDirection < 0) ie. log.1 is most recent, log.5 is the 5th
+ * backup, etc... countDirection > 0 does the opposite ie. log.1 is the first backup made, log.5 is the 5th backup
+ * made, etc. For infinite backups use countDirection > 0 to reduce rollOver costs.
+ */
+ protected int countDirection = -1;
+
+ /** Style of rolling to Use. BY_SIZE (1), BY_DATE(2), BY COMPOSITE(3) */
+ protected int rollingStyle = BY_COMPOSITE;
+ protected boolean rollDate = true;
+ protected boolean rollSize = true;
+
+ /**
+ * By default file.log is always the current file. Optionally file.log.yyyy-mm-dd for current formated datePattern
+ * can by the currently logging file (or file.log.curSizeRollBackup or even file.log.yyyy-mm-dd.curSizeRollBackup)
+ * This will make time based roll overs with a large number of backups much faster -- it won't have to rename all
+ * the backups!
+ */
+ protected boolean staticLogFileName = true;
+
+ /** FileName provided in configuration. Used for rolling properly */
+ protected String baseFileName;
+
+ /** Do we want to .gz our backup files. */
+ protected boolean compress = false;
+
+ /** Do we want to use a second thread when compressing our backup files. */
+ protected boolean compressAsync = false;
+
+ /** Do we want to start numbering files at zero. */
+ protected boolean zeroBased = false;
+
+ /** Path provided in configuration. Used for moving backup files to */
+ protected String backupFilesToPath = null;
+ private final ConcurrentLinkedQueue<CompressJob> _compress = new ConcurrentLinkedQueue<CompressJob>();
+ private AtomicBoolean _compressing = new AtomicBoolean(false);
+
+ /** The default constructor does nothing. */
+ public QpidCompositeRollingAppender()
+ { }
+
+ /**
+ * Instantiate a <code>CompositeRollingAppender</code> and open the file designated by <code>filename</code>. The
+ * opened filename will become the ouput destination for this appender.
+ */
+ public QpidCompositeRollingAppender(Layout layout, String filename, String datePattern) throws IOException
+ {
+ this(layout, filename, datePattern, true);
+ }
+
+ /**
+ * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename
+ * will become the ouput destination for this appender.
+ *
+ * <p>If the <code>append</code> parameter is true, the file will be appended to. Otherwise, the file desginated by
+ * <code>filename</code> will be truncated before being opened.
+ */
+ public QpidCompositeRollingAppender(Layout layout, String filename, boolean append) throws IOException
+ {
+ super(layout, filename, append);
+ }
+
+ /**
+ * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename
+ * will become the ouput destination for this appender.
+ */
+ public QpidCompositeRollingAppender(Layout layout, String filename, String datePattern, boolean append)
+ throws IOException
+ {
+ super(layout, filename, append);
+ this.datePattern = datePattern;
+ activateOptions();
+ }
+
+ /**
+ * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename
+ * will become the output destination for this appender.
+ *
+ * <p>The file will be appended to. DatePattern is default.
+ */
+ public QpidCompositeRollingAppender(Layout layout, String filename) throws IOException
+ {
+ super(layout, filename);
+ }
+
+ /**
+ * The <b>DatePattern</b> takes a string in the same format as expected by {@link java.text.SimpleDateFormat}. This
+ * options determines the rollover schedule.
+ */
+ public void setDatePattern(String pattern)
+ {
+ datePattern = pattern;
+ }
+
+ /** Returns the value of the <b>DatePattern</b> option. */
+ public String getDatePattern()
+ {
+ return datePattern;
+ }
+
+ /** Returns the value of the <b>maxSizeRollBackups</b> option. */
+ public int getMaxSizeRollBackups()
+ {
+ return maxSizeRollBackups;
+ }
+
+ /**
+ * Get the maximum size that the output file is allowed to reach before being rolled over to backup files.
+ *
+ * @since 1.1
+ */
+ public long getMaximumFileSize()
+ {
+ return maxFileSize;
+ }
+
+ /**
+ * <p>Set the maximum number of backup files to keep around based on file size.
+ *
+ * <p>The <b>MaxSizeRollBackups</b> option determines how many backup files are kept before the oldest is erased.
+ * This option takes an integer value. If set to zero, then there will be no backup files and the log file will be
+ * truncated when it reaches <code>MaxFileSize</code>. If a negative number is supplied then no deletions will be
+ * made. Note that this could result in very slow performance as a large number of files are rolled over unless
+ * {@link #setCountDirection} up is used.
+ *
+ * <p>The maximum applys to -each- time based group of files and -not- the total. Using a daily roll the maximum
+ * total files would be (#days run) * (maxSizeRollBackups)
+ */
+ public void setMaxSizeRollBackups(int maxBackups)
+ {
+ maxSizeRollBackups = maxBackups;
+ }
+
+ /**
+ * Set the maximum size that the output file is allowed to reach before being rolled over to backup files.
+ *
+ * <p>This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter
+ * taking a <code>long</code> argument from the setter taking a <code>String</code> argument by the JavaBeans {@link
+ * java.beans.Introspector Introspector}.
+ *
+ * @see #setMaxFileSize(String)
+ */
+ public void setMaxFileSize(long maxFileSize)
+ {
+ this.maxFileSize = maxFileSize;
+ }
+
+ /**
+ * Set the maximum size that the output file is allowed to reach before being rolled over to backup files.
+ *
+ * <p>This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter
+ * taking a <code>long</code> argument from the setter taking a <code>String</code> argument by the JavaBeans {@link
+ * java.beans.Introspector Introspector}.
+ *
+ * @see #setMaxFileSize(String)
+ */
+ public void setMaximumFileSize(long maxFileSize)
+ {
+ this.maxFileSize = maxFileSize;
+ }
+
+ /**
+ * Set the maximum size that the output file is allowed to reach before being rolled over to backup files.
+ *
+ * <p>In configuration files, the <b>MaxFileSize</b> option takes an long integer in the range 0 - 2^63. You can
+ * specify the value with the suffixes "KB", "MB" or "GB" so that the integer is interpreted being expressed
+ * respectively in kilobytes, megabytes or gigabytes. For example, the value "10KB" will be interpreted as 10240.
+ */
+ public void setMaxFileSize(String value)
+ {
+ maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1);
+ }
+
+ protected void setQWForFiles(Writer writer)
+ {
+ qw = new CountingQuietWriter(writer, errorHandler);
+ }
+
+ // Taken verbatum from DailyRollingFileAppender
+ int computeCheckPeriod()
+ {
+ RollingCalendar c = new RollingCalendar();
+ // set sate to 1970-01-01 00:00:00 GMT
+ Date epoch = new Date(0);
+ if (datePattern != null)
+ {
+ for (int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++)
+ {
+ String r0 = sdf.format(epoch);
+ c.setType(i);
+ Date next = new Date(c.getNextCheckMillis(epoch));
+ String r1 = sdf.format(next);
+ // LogLog.debug("Type = "+i+", r0 = "+r0+", r1 = "+r1);
+ if ((r0 != null) && (r1 != null) && !r0.equals(r1))
+ {
+ return i;
+ }
+ }
+ }
+
+ return TOP_OF_TROUBLE; // Deliberately head for trouble...
+ }
+
+ // Now for the new stuff
+ /**
+ * Handles append time behavior for CompositeRollingAppender. This checks if a roll over either by date (checked
+ * first) or time (checked second) is need and then appends to the file last.
+ */
+ protected void subAppend(LoggingEvent event)
+ {
+
+ if (rollDate)
+ {
+ long n = System.currentTimeMillis();
+ if (n >= nextCheck)
+ {
+ now.setTime(n);
+ nextCheck = rc.getNextCheckMillis(now);
+
+ rollOverTime();
+ }
+ }
+
+ if (rollSize)
+ {
+ if ((fileName != null) && (((CountingQuietWriter) qw).getCount() >= maxFileSize))
+ {
+ rollOverSize();
+ }
+ }
+
+ super.subAppend(event);
+ }
+
+ public void setFile(String file)
+ {
+ baseFileName = file.trim();
+ fileName = file.trim();
+ }
+
+ /**
+ * Creates and opens the file for logging. If <code>staticLogFileName</code> is false then the fully qualified name
+ * is determined and used.
+ */
+ public synchronized void setFile(String fileName, boolean append) throws IOException
+ {
+ if (!staticLogFileName)
+ {
+ scheduledFilename = fileName = fileName.trim() + sdf.format(now);
+ if (countDirection > 0)
+ {
+ scheduledFilename = fileName = fileName + '.' + (++curSizeRollBackups);
+ }
+ }
+
+ super.setFile(fileName, append, bufferedIO, bufferSize);
+
+ if (append)
+ {
+ File f = new File(fileName);
+ ((CountingQuietWriter) qw).setCount(f.length());
+ }
+ }
+
+ public int getCountDirection()
+ {
+ return countDirection;
+ }
+
+ public void setCountDirection(int direction)
+ {
+ countDirection = direction;
+ }
+
+ public int getRollingStyle()
+ {
+ return rollingStyle;
+ }
+
+ public void setRollingStyle(int style)
+ {
+ rollingStyle = style;
+ switch (rollingStyle)
+ {
+
+ case BY_SIZE:
+ rollDate = false;
+ rollSize = true;
+ break;
+
+ case BY_DATE:
+ rollDate = true;
+ rollSize = false;
+ break;
+
+ case BY_COMPOSITE:
+ rollDate = true;
+ rollSize = true;
+ break;
+
+ default:
+ errorHandler.error("Invalid rolling Style, use 1 (by size only), 2 (by date only) or 3 (both)");
+ }
+ }
+
+ /*
+ public void setRollingStyle(String style) {
+ if (style == S_BY_SIZE) {
+ rollingStyle = BY_SIZE;
+ }
+ else if (style == S_BY_DATE) {
+ rollingStyle = BY_DATE;
+ }
+ else if (style == S_BY_COMPOSITE) {
+ rollingStyle = BY_COMPOSITE;
+ }
+ }
+ */
+ public boolean getStaticLogFileName()
+ {
+ return staticLogFileName;
+ }
+
+ public void setStaticLogFileName(boolean s)
+ {
+ staticLogFileName = s;
+ }
+
+ public void setStaticLogFileName(String value)
+ {
+ setStaticLogFileName(OptionConverter.toBoolean(value, true));
+ }
+
+ public boolean getCompressBackupFiles()
+ {
+ return compress;
+ }
+
+ public void setCompressBackupFiles(boolean c)
+ {
+ compress = c;
+ }
+
+ public boolean getCompressAsync()
+ {
+ return compressAsync;
+ }
+
+ public void setCompressAsync(boolean c)
+ {
+ compressAsync = c;
+ if (compressAsync)
+ {
+ executor = Executors.newFixedThreadPool(1);
+
+ compressor = new Compressor();
+ }
+ }
+
+ public boolean getZeroBased()
+ {
+ return zeroBased;
+ }
+
+ public void setZeroBased(boolean z)
+ {
+ zeroBased = z;
+ }
+
+ public String getBackupFilesToPath()
+ {
+ return backupFilesToPath;
+ }
+
+ public void setbackupFilesToPath(String path)
+ {
+ File td = new File(path);
+ if (!td.exists())
+ {
+ td.mkdirs();
+ }
+
+ backupFilesToPath = path;
+ }
+
+ /**
+ * Initializes based on exisiting conditions at time of <code> activateOptions</code>. The following is done:<br>
+ * <br> A) determine curSizeRollBackups<br> B) determine curTimeRollBackups (not implemented)<br> C) initiates a
+ * roll over if needed for crossing a date boundary since the last run.
+ */
+ protected void existingInit()
+ {
+
+ if (zeroBased)
+ {
+ curSizeRollBackups = -1;
+ }
+
+ curTimeRollBackups = 0;
+
+ // part A starts here
+ String filter;
+ if (staticLogFileName || !rollDate)
+ {
+ filter = baseFileName + ".*";
+ }
+ else
+ {
+ filter = scheduledFilename + ".*";
+ }
+
+ File f = new File(baseFileName);
+ f = f.getParentFile();
+ if (f == null)
+ {
+ f = new File(".");
+ }
+
+ LogLog.debug("Searching for existing files in: " + f);
+ String[] files = f.list();
+
+ if (files != null)
+ {
+ for (int i = 0; i < files.length; i++)
+ {
+ if (!files[i].startsWith(baseFileName))
+ {
+ continue;
+ }
+
+ int index = files[i].lastIndexOf(".");
+
+ if (staticLogFileName)
+ {
+ int endLength = files[i].length() - index;
+ if ((baseFileName.length() + endLength) != files[i].length())
+ {
+ // file is probably scheduledFilename + .x so I don't care
+ continue;
+ }
+ }
+
+ try
+ {
+ int backup = Integer.parseInt(files[i].substring(index + 1, files[i].length()));
+ LogLog.debug("From file: " + files[i] + " -> " + backup);
+ if (backup > curSizeRollBackups)
+ {
+ curSizeRollBackups = backup;
+ }
+ }
+ catch (Exception e)
+ {
+ // this happens when file.log -> file.log.yyyy-mm-dd which is normal
+ // when staticLogFileName == false
+ LogLog.debug("Encountered a backup file not ending in .x " + files[i]);
+ }
+ }
+ }
+
+ LogLog.debug("curSizeRollBackups starts at: " + curSizeRollBackups);
+ // part A ends here
+
+ // part B not yet implemented
+
+ // part C
+ if (staticLogFileName && rollDate)
+ {
+ File old = new File(baseFileName);
+ if (old.exists())
+ {
+ Date last = new Date(old.lastModified());
+ if (!(sdf.format(last).equals(sdf.format(now))))
+ {
+ scheduledFilename = baseFileName + sdf.format(last);
+ LogLog.debug("Initial roll over to: " + scheduledFilename);
+ rollOverTime();
+ }
+ }
+ }
+
+ LogLog.debug("curSizeRollBackups after rollOver at: " + curSizeRollBackups);
+ // part C ends here
+
+ }
+
+ /**
+ * Sets initial conditions including date/time roll over information, first check, scheduledFilename, and calls
+ * <code>existingInit</code> to initialize the current # of backups.
+ */
+ public void activateOptions()
+ {
+
+ // REMOVE removed rollDate from boolean to enable Alex's change
+ if (datePattern != null)
+ {
+ now.setTime(System.currentTimeMillis());
+ sdf = new SimpleDateFormat(datePattern);
+ int type = computeCheckPeriod();
+ // printPeriodicity(type);
+ rc.setType(type);
+ // next line added as this removes the name check in rollOver
+ nextCheck = rc.getNextCheckMillis(now);
+ }
+ else
+ {
+ if (rollDate)
+ {
+ LogLog.error("Either DatePattern or rollingStyle options are not set for [" + name + "].");
+ }
+ }
+
+ existingInit();
+
+ if (rollDate && (fileName != null) && (scheduledFilename == null))
+ {
+ scheduledFilename = fileName + sdf.format(now);
+ }
+
+ try
+ {
+ this.setFile(fileName, true);
+ }
+ catch (IOException e)
+ {
+ errorHandler.error("Cannot set file name:" + fileName);
+ }
+
+ super.activateOptions();
+ }
+
+ /**
+ * Rollover the file(s) to date/time tagged file(s). Opens the new file (through setFile) and resets
+ * curSizeRollBackups.
+ */
+ protected void rollOverTime()
+ {
+
+ curTimeRollBackups++;
+
+ this.closeFile(); // keep windows happy.
+
+ // delete the old stuff here
+
+ if (staticLogFileName)
+ {
+ /* Compute filename, but only if datePattern is specified */
+ if (datePattern == null)
+ {
+ errorHandler.error("Missing DatePattern option in rollOver().");
+
+ return;
+ }
+
+ // is the new file name equivalent to the 'current' one
+ // something has gone wrong if we hit this -- we should only
+ // roll over if the new file will be different from the old
+ String dateFormat = sdf.format(now);
+ if (scheduledFilename.equals(fileName + dateFormat))
+ {
+ errorHandler.error("Compare " + scheduledFilename + " : " + fileName + dateFormat);
+
+ return;
+ }
+
+ // close current file, and rename it to datedFilename
+ this.closeFile();
+
+ // we may have to roll over a large number of backups here
+ String from, to;
+ for (int i = 1; i <= curSizeRollBackups; i++)
+ {
+ from = fileName + '.' + i;
+ to = scheduledFilename + '.' + i;
+ rollFile(from, to, false);
+ }
+
+ rollFile(fileName, scheduledFilename, compress);
+ }
+ else
+ {
+ if (compress)
+ {
+ compress(fileName);
+ }
+ }
+
+ try
+ {
+ // This will also close the file. This is OK since multiple
+ // close operations are safe.
+ curSizeRollBackups = 0; // We're cleared out the old date and are ready for the new
+
+ // new scheduled name
+ scheduledFilename = fileName + sdf.format(now);
+ this.setFile(baseFileName, false);
+ }
+ catch (IOException e)
+ {
+ errorHandler.error("setFile(" + fileName + ", false) call failed.");
+ }
+
+ }
+
+ /**
+ * Renames file <code>from</code> to file <code>to</code>. It also checks for existence of target file and deletes
+ * if it does.
+ */
+ protected void rollFile(String from, String to, boolean compress)
+ {
+ if (from.equals(to))
+ {
+ if (compress)
+ {
+ LogLog.debug("Attempting to compress file with same output name.");
+ }
+
+ return;
+ }
+
+ File target = new File(to);
+ if (target.exists())
+ {
+ LogLog.debug("deleting existing target file: " + target);
+ target.delete();
+ }
+
+ File file = new File(from);
+ if (compress)
+ {
+ compress(file, target);
+ }
+ else
+ {
+ if (!file.getPath().equals(target.getPath()))
+ {
+ file.renameTo(target);
+ }
+ }
+
+ LogLog.debug(from + " -> " + to);
+ }
+
+ protected void compress(String file)
+ {
+ File f = new File(file);
+ compress(f, f);
+ }
+
+ private void compress(File from, File target)
+ {
+ if (compressAsync)
+ {
+ synchronized (_compress)
+ {
+ _compress.offer(new CompressJob(from, target));
+ }
+
+ startCompression();
+ }
+ else
+ {
+ doCompress(from, target);
+ }
+ }
+
+ private void startCompression()
+ {
+ if (_compressing.compareAndSet(false, true))
+ {
+ executor.execute(compressor);
+ }
+ }
+
+ /** Delete's the specified file if it exists */
+ protected static void deleteFile(String fileName)
+ {
+ File file = new File(fileName);
+ if (file.exists())
+ {
+ file.delete();
+ }
+ }
+
+ /**
+ * Implements roll overs base on file size.
+ *
+ * <p>If the maximum number of size based backups is reached (<code>curSizeRollBackups == maxSizeRollBackups</code)
+ * then the oldest file is deleted -- it's index determined by the sign of countDirection.<br> If
+ * <code>countDirection</code> < 0, then files {<code>File.1</code>, ..., <code>File.curSizeRollBackups -1</code>}
+ * are renamed to {<code>File.2</code>, ..., <code>File.curSizeRollBackups</code>}. Moreover, <code>File</code> is
+ * renamed <code>File.1</code> and closed.<br>
+ *
+ * A new file is created to receive further log output.
+ *
+ * <p>If <code>maxSizeRollBackups</code> is equal to zero, then the <code>File</code> is truncated with no backup
+ * files created.
+ *
+ * <p>If <code>maxSizeRollBackups</code> < 0, then <code>File</code> is renamed if needed and no files are deleted.
+ */
+
+ // synchronization not necessary since doAppend is alreasy synched
+ protected void rollOverSize()
+ {
+ File file;
+
+ this.closeFile(); // keep windows happy.
+
+ LogLog.debug("rolling over count=" + ((CountingQuietWriter) qw).getCount());
+ LogLog.debug("maxSizeRollBackups = " + maxSizeRollBackups);
+ LogLog.debug("curSizeRollBackups = " + curSizeRollBackups);
+ LogLog.debug("countDirection = " + countDirection);
+
+ // If maxBackups <= 0, then there is no file renaming to be done.
+ if (maxSizeRollBackups != 0)
+ {
+
+ if (countDirection < 0)
+ {
+ // Delete the oldest file, to keep Windows happy.
+ if (curSizeRollBackups == maxSizeRollBackups)
+ {
+ deleteFile(fileName + '.' + maxSizeRollBackups);
+ curSizeRollBackups--;
+ }
+
+ // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2}
+ for (int i = curSizeRollBackups; i >= 1; i--)
+ {
+ rollFile((fileName + "." + i), (fileName + '.' + (i + 1)), false);
+ }
+
+ curSizeRollBackups++;
+ // Rename fileName to fileName.1
+ rollFile(fileName, fileName + ".1", compress);
+
+ } // REMOVE This code branching for Alexander Cerna's request
+ else if (countDirection == 0)
+ {
+ // rollFile based on date pattern
+ curSizeRollBackups++;
+ now.setTime(System.currentTimeMillis());
+ scheduledFilename = fileName + sdf.format(now);
+ rollFile(fileName, scheduledFilename, compress);
+ }
+ else
+ { // countDirection > 0
+ if ((curSizeRollBackups >= maxSizeRollBackups) && (maxSizeRollBackups > 0))
+ {
+ // delete the first and keep counting up.
+ int oldestFileIndex = curSizeRollBackups - maxSizeRollBackups + 1;
+ deleteFile(fileName + '.' + oldestFileIndex);
+ }
+
+ if (staticLogFileName)
+ {
+ curSizeRollBackups++;
+ rollFile(fileName, fileName + '.' + curSizeRollBackups, compress);
+ }
+ else
+ {
+ if (compress)
+ {
+ compress(fileName);
+ }
+ }
+ }
+ }
+
+ try
+ {
+ // This will also close the file. This is OK since multiple
+ // close operations are safe.
+ this.setFile(baseFileName, false);
+ }
+ catch (IOException e)
+ {
+ LogLog.error("setFile(" + fileName + ", false) call failed.", e);
+ }
+ }
+
+ protected synchronized void doCompress(File from, File to)
+ {
+ String toFile;
+ if (backupFilesToPath == null)
+ {
+ toFile = to.getPath() + ".gz";
+ }
+ else
+ {
+ toFile = backupFilesToPath + System.getProperty("file.separator") + to.getName() + ".gz";
+ }
+
+ File target = new File(toFile);
+ if (target.exists())
+ {
+ LogLog.debug("deleting existing target file: " + target);
+ target.delete();
+ }
+
+ try
+ {
+ // Create the GZIP output stream
+ GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(target));
+
+ // Open the input file
+ FileInputStream in = new FileInputStream(from);
+
+ // Transfer bytes from the input file to the GZIP output stream
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = in.read(buf)) > 0)
+ {
+ out.write(buf, 0, len);
+ }
+
+ in.close();
+
+ // Complete the GZIP file
+ out.finish();
+ out.close();
+ // Remove old file.
+ from.delete();
+ }
+ catch (IOException e)
+ {
+ if (target.exists())
+ {
+ target.delete();
+ }
+
+ rollFile(from.getPath(), to.getPath(), false);
+ }
+ }
+
+ private class CompressJob
+ {
+ File _from, _to;
+
+ CompressJob(File from, File to)
+ {
+ _from = from;
+ _to = to;
+ }
+
+ File getFrom()
+ {
+ return _from;
+ }
+
+ File getTo()
+ {
+ return _to;
+ }
+ }
+
+ Compressor compressor = null;
+
+ Executor executor;
+
+ private class Compressor implements Runnable
+ {
+ public void run()
+ {
+ boolean running = true;
+ while (running)
+ {
+ CompressJob job = _compress.poll();
+
+ doCompress(job.getFrom(), job.getTo());
+
+ synchronized (_compress)
+ {
+ if (_compress.isEmpty())
+ {
+ running = false;
+ _compressing.set(false);
+ }
+ }
+ }
+
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/configuration/Configuration.java b/qpid/java/broker/src/main/java/org/apache/qpid/configuration/Configuration.java
new file mode 100644
index 0000000000..40ff590a0a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/configuration/Configuration.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.configuration;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.PosixParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+public class Configuration
+{
+ public static final String QPID_HOME = "QPID_HOME";
+
+ final String QPIDHOME = System.getProperty(QPID_HOME);
+
+ private static Logger _devlog = LoggerFactory.getLogger(Configuration.class);
+
+ public static final String DEFAULT_LOG_CONFIG_FILENAME = "log4j.xml";
+ public static final String DEFAULT_CONFIG_FILE = "etc/config.xml";
+
+ protected final Options _options = new Options();
+ protected CommandLine _commandLine;
+ protected File _configFile;
+
+
+ public Configuration()
+ {
+
+ }
+
+ public void processCommandline(String[] args) throws InitException
+ {
+ try
+ {
+ _commandLine = new PosixParser().parse(_options, args);
+ }
+ catch (ParseException e)
+ {
+ throw new InitException("Unable to parse commmandline", e);
+ }
+
+ final File defaultConfigFile = new File(QPIDHOME, DEFAULT_CONFIG_FILE);
+ setConfig(new File(_commandLine.getOptionValue("c", defaultConfigFile.getPath())));
+ }
+
+ public void setConfig(File file)
+ {
+ _configFile = file;
+ }
+
+ /**
+ * @param option The option to set.
+ */
+ public void setOption(Option option)
+ {
+ _options.addOption(option);
+ }
+
+ /**
+ * getOptionValue from the configuration
+ * @param option variable argument, first string is option to get, second if present is the default value.
+ * @return the String for the given option or null if not present (if default value not specified)
+ */
+ public String getOptionValue(String... option)
+ {
+ if (option.length == 1)
+ {
+ return _commandLine.getOptionValue(option[0]);
+ }
+ else if (option.length == 2)
+ {
+ return _commandLine.getOptionValue(option[0], option[1]);
+ }
+ return null;
+ }
+
+ public void loadConfig(File file) throws InitException
+ {
+ setConfig(file);
+ loadConfig();
+ }
+
+ private void loadConfig() throws InitException
+ {
+ 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, null);
+ }
+ else
+ {
+ _devlog.debug("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);
+// }
+ }
+
+
+// private void configureLogging(File logConfigFile, String logWatchConfig)
+// {
+// int logWatchTime = 0;
+// try
+// {
+// logWatchTime = Integer.parseInt(logWatchConfig);
+// }
+// catch (NumberFormatException e)
+// {
+// _devlog.error("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())
+// {
+// _devlog.info("Configuring logger using configuration file " + logConfigFile.getAbsolutePath());
+// if (logWatchTime > 0)
+// {
+// _devlog.info("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();
+// }
+// }
+
+ public File getConfigFile()
+ {
+ return _configFile;
+ }
+
+
+ public class InitException extends Exception
+ {
+ InitException(String msg, Throwable cause)
+ {
+ super(msg, cause);
+ }
+ }
+} \ No newline at end of file
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQBrokerManagerMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQBrokerManagerMBean.java
new file mode 100644
index 0000000000..11d2c27eab
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQBrokerManagerMBean.java
@@ -0,0 +1,235 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+/*
+ *
+ * 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 javax.management.MBeanException;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.management.AMQManagedObject;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.management.ManagedBroker;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.AMQQueueFactory;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+/**
+ * This MBean 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")
+public class AMQBrokerManagerMBean extends AMQManagedObject implements ManagedBroker
+{
+ private final QueueRegistry _queueRegistry;
+ private final ExchangeRegistry _exchangeRegistry;
+ private final ExchangeFactory _exchangeFactory;
+ private final MessageStore _messageStore;
+
+ private final VirtualHost.VirtualHostMBean _virtualHostMBean;
+
+ @MBeanConstructor("Creates the Broker Manager MBean")
+ public AMQBrokerManagerMBean(VirtualHost.VirtualHostMBean virtualHostMBean) throws JMException
+ {
+ super(ManagedBroker.class, ManagedBroker.TYPE, ManagedBroker.VERSION);
+
+ _virtualHostMBean = virtualHostMBean;
+ VirtualHost virtualHost = virtualHostMBean.getVirtualHost();
+
+ _queueRegistry = virtualHost.getQueueRegistry();
+ _exchangeRegistry = virtualHost.getExchangeRegistry();
+ _messageStore = virtualHost.getMessageStore();
+ _exchangeFactory = virtualHost.getExchangeFactory();
+ }
+
+ public String getObjectInstanceName()
+ {
+ return _virtualHostMBean.getVirtualHost().getName();
+ }
+
+ /**
+ * Creates new exchange and registers it with the registry.
+ *
+ * @param exchangeName
+ * @param type
+ * @param durable
+ * @throws JMException
+ */
+ public void createNewExchange(String exchangeName, String type, boolean durable) throws JMException
+ {
+ try
+ {
+ synchronized (_exchangeRegistry)
+ {
+ Exchange exchange = _exchangeRegistry.getExchange(new AMQShortString(exchangeName));
+ if (exchange == null)
+ {
+ exchange = _exchangeFactory.createExchange(new AMQShortString(exchangeName), new AMQShortString(type),
+ durable, false, 0);
+ _exchangeRegistry.registerExchange(exchange);
+ }
+ else
+ {
+ throw new JMException("The exchange \"" + exchangeName + "\" already exists.");
+ }
+ }
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex, "Error in creating exchange " + exchangeName);
+ }
+ }
+
+ /**
+ * Unregisters the exchange from registry.
+ *
+ * @param exchangeName
+ * @throws JMException
+ */
+ public void unregisterExchange(String exchangeName) throws JMException
+ {
+ // TODO
+ // Check if the exchange is in use.
+ // boolean inUse = false;
+ // Check if there are queue-bindings with the exchange and unregister
+ // when there are no bindings.
+ try
+ {
+ _exchangeRegistry.unregisterExchange(new AMQShortString(exchangeName), false);
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex, "Error in unregistering exchange " + exchangeName);
+ }
+ }
+
+ /**
+ * 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
+ * @throws JMException
+ */
+ public void createNewQueue(String queueName, String owner, boolean durable) throws JMException
+ {
+ AMQQueue queue = _queueRegistry.getQueue(new AMQShortString(queueName));
+ if (queue != null)
+ {
+ throw new JMException("The queue \"" + queueName + "\" already exists.");
+ }
+
+ try
+ {
+ AMQShortString ownerShortString = null;
+ if (owner != null)
+ {
+ ownerShortString = new AMQShortString(owner);
+ }
+
+ queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString(queueName), durable, ownerShortString, false, getVirtualHost(),
+ null);
+ if (queue.isDurable() && !queue.isAutoDelete())
+ {
+ _messageStore.createQueue(queue);
+ }
+
+ _queueRegistry.registerQueue(queue);
+ }
+ catch (AMQException ex)
+ {
+ JMException jme = new JMException(ex.getMessage());
+ jme.initCause(ex);
+ throw new MBeanException(jme, "Error in creating queue " + queueName);
+ }
+ }
+
+ private VirtualHost getVirtualHost()
+ {
+ return _virtualHostMBean.getVirtualHost();
+ }
+
+ /**
+ * Deletes the queue from queue registry and persistant storage.
+ *
+ * @param queueName
+ * @throws JMException
+ */
+ public void deleteQueue(String queueName) throws JMException
+ {
+ AMQQueue queue = _queueRegistry.getQueue(new AMQShortString(queueName));
+ if (queue == null)
+ {
+ throw new JMException("The Queue " + queueName + " is not a registerd queue.");
+ }
+
+ try
+ {
+ queue.delete();
+ _messageStore.removeQueue(queue);
+
+ }
+ catch (AMQException ex)
+ {
+ JMException jme = new JMException(ex.getMessage());
+ jme.initCause(ex);
+ throw new MBeanException(jme, "Error in deleting queue " + queueName);
+ }
+ }
+
+ public ManagedObject getParentObject()
+ {
+ return _virtualHostMBean;
+ }
+
+ // This will have a single instance for a virtual host, so not having the name property in the ObjectName
+ public ObjectName getObjectName() throws MalformedObjectNameException
+ {
+ return getObjectNameForSingleInstanceMBean();
+ }
+} // End of MBean class
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java
new file mode 100644
index 0000000000..aa390d6c26
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java
@@ -0,0 +1,909 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMapImpl;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.NoRouteException;
+import org.apache.qpid.server.flow.FlowCreditManager;
+import org.apache.qpid.server.flow.Pre0_10CreditManager;
+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.IncomingMessage;
+import org.apache.qpid.server.queue.MessageHandleFactory;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.queue.UnauthorizedAccessException;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.subscription.SubscriptionFactoryImpl;
+import org.apache.qpid.server.subscription.ClientDeliveryMethod;
+import org.apache.qpid.server.subscription.RecordDeliveryMethod;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.txn.LocalTransactionalContext;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+import org.apache.qpid.server.txn.TransactionalContext;
+
+public class AMQChannel
+{
+ public static final int DEFAULT_PREFETCH = 5000;
+
+ private static final Logger _log = Logger.getLogger(AMQChannel.class);
+
+ private final int _channelId;
+
+
+ private final Pre0_10CreditManager _creditManager = new Pre0_10CreditManager(0l,0l);
+
+ /**
+ * 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 long _deliveryTag = 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 IncomingMessage _currentMessage;
+
+ /** Maps from consumer tag to subscription instance. Allows us to unsubscribe from a queue. */
+ protected final Map<AMQShortString, Subscription> _tag2SubscriptionMap = new HashMap<AMQShortString, Subscription>();
+
+ private final MessageStore _messageStore;
+
+ private UnacknowledgedMessageMap _unacknowledgedMessageMap = new UnacknowledgedMessageMapImpl(DEFAULT_PREFETCH);
+
+ private final AtomicBoolean _suspended = new AtomicBoolean(false);
+
+ private TransactionalContext _txnContext;
+
+ /**
+ * A context used by the message store enabling it to track context for a given channel even across thread
+ * boundaries
+ */
+ private final StoreContext _storeContext;
+
+ private final List<RequiredDeliveryException> _returnMessages = new LinkedList<RequiredDeliveryException>();
+
+ private MessageHandleFactory _messageHandleFactory = new MessageHandleFactory();
+
+ // Why do we need this reference ? - ritchiem
+ private final AMQProtocolSession _session;
+ private boolean _closing;
+
+ public AMQChannel(AMQProtocolSession session, int channelId, MessageStore messageStore)
+ throws AMQException
+ {
+ _session = session;
+ _channelId = channelId;
+ _storeContext = new StoreContext("Session: " + session.getClientIdentifier() + "; channel: " + channelId);
+
+
+ _messageStore = messageStore;
+
+ // by default the session is non-transactional
+ _txnContext = new NonTransactionalContext(_messageStore, _storeContext, this, _returnMessages);
+ }
+
+ /** Sets this channel to be part of a local transaction */
+ public void setLocalTransactional()
+ {
+ _txnContext = new LocalTransactionalContext(this);
+ }
+
+ public boolean isTransactional()
+ {
+ // this does not look great but there should only be one "non-transactional"
+ // transactional context, while there could be several transactional ones in
+ // theory
+ return !(_txnContext instanceof NonTransactionalContext);
+ }
+
+ public int getChannelId()
+ {
+ return _channelId;
+ }
+
+ public void setPublishFrame(MessagePublishInfo info, final Exchange e) throws AMQException
+ {
+
+ _currentMessage = new IncomingMessage(_messageStore.getNewMessageId(), info, _txnContext, _session);
+ _currentMessage.setMessageStore(_messageStore);
+ _currentMessage.setExchange(e);
+ }
+
+ public void publishContentHeader(ContentHeaderBody contentHeaderBody)
+ throws AMQException
+ {
+ if (_currentMessage == null)
+ {
+ throw new AMQException("Received content header without previously receiving a BasicPublish frame");
+ }
+ else
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Content header received on channel " + _channelId);
+ }
+
+ _currentMessage.setContentHeaderBody(contentHeaderBody);
+
+ _currentMessage.setExpiration();
+
+ routeCurrentMessage();
+
+ _currentMessage.routingComplete(_messageStore, _messageHandleFactory);
+
+ deliverCurrentMessageIfComplete();
+
+ }
+ }
+
+ private void deliverCurrentMessageIfComplete()
+ throws AMQException
+ {
+ // check and deliver if header says body length is zero
+ if (_currentMessage.allContentReceived())
+ {
+ try
+ {
+ _currentMessage.deliverToQueues();
+ }
+ catch (NoRouteException e)
+ {
+ _returnMessages.add(e);
+ }
+ catch(UnauthorizedAccessException ex)
+ {
+ _returnMessages.add(ex);
+ }
+ finally
+ {
+ // callback to allow the context to do any post message processing
+ // primary use is to allow message return processing in the non-tx case
+ _txnContext.messageProcessed(_session);
+ _currentMessage = null;
+ }
+ }
+
+ }
+
+ public void publishContentBody(ContentBody contentBody) throws AMQException
+ {
+ if (_currentMessage == null)
+ {
+ throw new AMQException("Received content body without previously receiving a JmsPublishBody");
+ }
+
+ if (_log.isDebugEnabled())
+ {
+ _log.debug(debugIdentity() + "Content body received on channel " + _channelId);
+ }
+
+ try
+ {
+
+ // returns true iff the message was delivered (i.e. if all data was
+ // received
+ _currentMessage.addContentBodyFrame(
+ _session.getMethodRegistry().getProtocolVersionMethodConverter().convertToContentChunk(
+ contentBody));
+
+ deliverCurrentMessageIfComplete();
+ }
+ 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
+ {
+ try
+ {
+ _currentMessage.route();
+ }
+ catch (NoRouteException e)
+ {
+ //_currentMessage.incrementReference();
+ _returnMessages.add(e);
+ }
+ }
+
+ public long getNextDeliveryTag()
+ {
+ return ++_deliveryTag;
+ }
+
+ 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 acks Are acks enabled for this subscriber
+ * @param filters Filters to apply to this subscriber
+ *
+ * @param noLocal Flag stopping own messages being receivied.
+ * @param exclusive Flag requesting exclusive access to the queue
+ * @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 AMQShortString subscribeToQueue(AMQShortString tag, AMQQueue queue, boolean acks,
+ FieldTable filters, boolean noLocal, boolean exclusive) throws AMQException, ConsumerTagNotUniqueException
+ {
+ if (tag == null)
+ {
+ tag = new AMQShortString("sgen_" + getNextConsumerTag());
+ }
+
+ if (_tag2SubscriptionMap.containsKey(tag))
+ {
+ throw new ConsumerTagNotUniqueException();
+ }
+
+ Subscription subscription =
+ SubscriptionFactoryImpl.INSTANCE.createSubscription(_channelId, _session, tag, acks, filters, noLocal, _creditManager);
+
+
+ // So to keep things straight we put before the call and catch all exceptions from the register and tidy up.
+ // We add before we register as the Async Delivery process may AutoClose the subscriber
+ // so calling _cT2QM.remove before we have done put which was after the register succeeded.
+ // So to keep things straight we put before the call and catch all exceptions from the register and tidy up.
+
+ _tag2SubscriptionMap.put(tag, subscription);
+
+ try
+ {
+ queue.registerSubscription(subscription, exclusive);
+ }
+ catch (AMQException e)
+ {
+ _tag2SubscriptionMap.remove(tag);
+ throw e;
+ }
+ return tag;
+ }
+
+ /**
+ * Unsubscribe a consumer from a queue.
+ * @param consumerTag
+ * @return true if the consumerTag had a mapped queue that could be unregistered.
+ * @throws AMQException
+ */
+ public boolean unsubscribeConsumer(AMQShortString consumerTag) throws AMQException
+ {
+
+ Subscription sub = _tag2SubscriptionMap.remove(consumerTag);
+ if (sub != null)
+ {
+ try
+ {
+ sub.getSendLock();
+ sub.getQueue().unregisterSubscription(sub);
+ }
+ finally
+ {
+ sub.releaseSendLock();
+ }
+ return true;
+ }
+ else
+ {
+ _log.warn("Attempt to unsubscribe consumer with tag '"+consumerTag+"' which is not registered.");
+ }
+ return false;
+ }
+
+ /**
+ * Called from the protocol session to close this channel and clean up. T
+ *
+ * @throws AMQException if there is an error during closure
+ */
+ public void close() throws AMQException
+ {
+ _txnContext.rollback();
+ unsubscribeAllConsumers();
+ try
+ {
+ requeue();
+ }
+ catch (AMQException e)
+ {
+ _log.error("Caught AMQException whilst attempting to reque:" + e);
+ }
+
+ setClosing(true);
+ }
+
+ private void setClosing(boolean closing)
+ {
+ _closing = closing;
+ }
+
+ private void unsubscribeAllConsumers() throws AMQException
+ {
+ if (_log.isInfoEnabled())
+ {
+ if (!_tag2SubscriptionMap.isEmpty())
+ {
+ _log.info("Unsubscribing all consumers on channel " + toString());
+ }
+ else
+ {
+ _log.info("No consumers to unsubscribe on channel " + toString());
+ }
+ }
+
+ for (Map.Entry<AMQShortString, Subscription> me : _tag2SubscriptionMap.entrySet())
+ {
+ if (_log.isInfoEnabled())
+ {
+ _log.info("Unsubscribing consumer '" + me.getKey() + "' on channel " + toString());
+ }
+
+ Subscription sub = me.getValue();
+
+ try
+ {
+ sub.getSendLock();
+ sub.getQueue().unregisterSubscription(sub);
+ }
+ finally
+ {
+ sub.releaseSendLock();
+ }
+
+ }
+
+ _tag2SubscriptionMap.clear();
+ }
+
+ /**
+ * Add a message to the channel-based list of unacknowledged messages
+ *
+ * @param entry the record of the message on the queue that was delivered
+ * @param deliveryTag the delivery tag used when delivering the message (see protocol spec for description of the
+ * delivery tag)
+ * @param subscription The consumer that is to acknowledge this message.
+ */
+ public void addUnacknowledgedMessage(QueueEntry entry, long deliveryTag, Subscription subscription)
+ {
+ if (_log.isDebugEnabled())
+ {
+ if (entry.getQueue() == null)
+ {
+ _log.debug("Adding unacked message with a null queue:" + entry.debugIdentity());
+ }
+ else
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug(debugIdentity() + " Adding unacked message(" + entry.getMessage().toString() + " DT:" + deliveryTag
+ + ") with a queue(" + entry.getQueue() + ") for " + subscription);
+ }
+ }
+ }
+
+ _unacknowledgedMessageMap.add(deliveryTag, entry);
+
+ }
+
+ private final String id = "(" + System.identityHashCode(this) + ")";
+
+ public String debugIdentity()
+ {
+ return _channelId + id;
+ }
+
+ /**
+ * Called to attempt re-delivery 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<QueueEntry> messagesToBeDelivered = _unacknowledgedMessageMap.cancelAllMessages();
+
+ // Deliver these messages out of the transaction as their delivery was never
+ // part of the transaction only the receive.
+ TransactionalContext deliveryContext = null;
+
+ if (!messagesToBeDelivered.isEmpty())
+ {
+ if (_log.isInfoEnabled())
+ {
+ _log.info("Requeuing " + messagesToBeDelivered.size() + " unacked messages. for " + toString());
+ }
+
+ if (!(_txnContext instanceof NonTransactionalContext))
+ {
+
+ deliveryContext =
+ new NonTransactionalContext(_messageStore, _storeContext, this, _returnMessages);
+ }
+ else
+ {
+ deliveryContext = _txnContext;
+ }
+ }
+
+ for (QueueEntry unacked : messagesToBeDelivered)
+ {
+ if (!unacked.isQueueDeleted())
+ {
+ // Mark message redelivered
+ unacked.getMessage().setRedelivered(true);
+
+ // Ensure message is released for redelivery
+ unacked.release();
+
+ // Deliver Message
+ deliveryContext.requeue(unacked);
+
+ }
+ else
+ {
+ unacked.discard(_storeContext);
+ }
+ }
+
+ }
+
+ /**
+ * Requeue a single message
+ *
+ * @param deliveryTag The message to requeue
+ *
+ * @throws AMQException If something goes wrong.
+ */
+ public void requeue(long deliveryTag) throws AMQException
+ {
+ QueueEntry unacked = _unacknowledgedMessageMap.remove(deliveryTag);
+
+ if (unacked != null)
+ {
+ // Mark message redelivered
+ unacked.getMessage().setRedelivered(true);
+
+ // Ensure message is released for redelivery
+ if (!unacked.isQueueDeleted())
+ {
+ unacked.release();
+ }
+
+
+ // Deliver these messages out of the transaction as their delivery was never
+ // part of the transaction only the receive.
+ TransactionalContext deliveryContext;
+ if (!(_txnContext instanceof NonTransactionalContext))
+ {
+
+ deliveryContext =
+ new NonTransactionalContext(_messageStore, _storeContext, this, _returnMessages);
+
+ }
+ else
+ {
+ deliveryContext = _txnContext;
+ }
+
+ if (!unacked.isQueueDeleted())
+ {
+ // Redeliver the messages to the front of the queue
+ deliveryContext.requeue(unacked);
+ // Deliver increments the message count but we have already deliverted this once so don't increment it again
+ // this was because deliver did an increment changed this.
+ }
+ else
+ {
+ _log.warn(System.identityHashCode(this) + " Requested requeue of message(" + unacked.getMessage().debugIdentity()
+ + "):" + deliveryTag + " but no queue defined and no DeadLetter queue so DROPPING message.");
+
+ unacked.discard(_storeContext);
+ }
+ }
+ else
+ {
+ _log.warn("Requested requeue of message:" + deliveryTag + " but no such delivery tag exists."
+ + _unacknowledgedMessageMap.size());
+
+ }
+
+ }
+
+ /**
+ * Called to resend all outstanding unacknowledged messages to this same channel.
+ *
+ * @param requeue Are the messages to be requeued or dropped.
+ *
+ * @throws AMQException When something goes wrong.
+ */
+ public void resend(final boolean requeue) throws AMQException
+ {
+
+
+ final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>();
+ final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>();
+
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("unacked map Size:" + _unacknowledgedMessageMap.size());
+ }
+
+ // Process the Unacked-Map.
+ // Marking messages who still have a consumer for to be resent
+ // and those that don't to be requeued.
+ _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue,
+ msgToResend, requeue, _storeContext));
+
+
+ // Process Messages to Resend
+ if (_log.isDebugEnabled())
+ {
+ if (!msgToResend.isEmpty())
+ {
+ _log.debug("Preparing (" + msgToResend.size() + ") message to resend.");
+ }
+ else
+ {
+ _log.debug("No message to resend.");
+ }
+ }
+
+ for (Map.Entry<Long, QueueEntry> entry : msgToResend.entrySet())
+ {
+ QueueEntry message = entry.getValue();
+ long deliveryTag = entry.getKey();
+
+
+
+ AMQMessage msg = message.getMessage();
+ AMQQueue queue = message.getQueue();
+
+ // Our Java Client will always suspend the channel when resending!
+ // If the client has requested the messages be resent then it is
+ // their responsibility to ensure that thay are capable of receiving them
+ // i.e. The channel hasn't been server side suspended.
+ // if (isSuspended())
+ // {
+ // _log.info("Channel is suspended so requeuing");
+ // //move this message to requeue
+ // msgToRequeue.add(message);
+ // }
+ // else
+ // {
+ // release to allow it to be delivered
+
+ // Without any details from the client about what has been processed we have to mark
+ // all messages in the unacked map as redelivered.
+ msg.setRedelivered(true);
+
+ Subscription sub = message.getDeliveredSubscription();
+
+ if (sub != null)
+ {
+
+ if(!queue.resend(message, sub))
+ {
+ msgToRequeue.put(deliveryTag, message);
+ }
+ }
+ else
+ {
+
+ if (_log.isInfoEnabled())
+ {
+ _log.info("DeliveredSubscription not recorded so just requeueing(" + message.toString()
+ + ")to prevent loss");
+ }
+ // move this message to requeue
+ msgToRequeue.put(deliveryTag, message);
+ }
+ } // for all messages
+ // } else !isSuspend
+
+ if (_log.isInfoEnabled())
+ {
+ if (!msgToRequeue.isEmpty())
+ {
+ _log.info("Preparing (" + msgToRequeue.size() + ") message to requeue to.");
+ }
+ }
+
+ // Deliver these messages out of the transaction as their delivery was never
+ // part of the transaction only the receive.
+ TransactionalContext deliveryContext;
+ if (!(_txnContext instanceof NonTransactionalContext))
+ {
+
+ deliveryContext =
+ new NonTransactionalContext(_messageStore, _storeContext, this, _returnMessages);
+ }
+ else
+ {
+ deliveryContext = _txnContext;
+ }
+
+ // Process Messages to Requeue at the front of the queue
+ for (Map.Entry<Long, QueueEntry> entry : msgToRequeue.entrySet())
+ {
+ QueueEntry message = entry.getValue();
+ long deliveryTag = entry.getKey();
+
+ message.release();
+ message.setRedelivered(true);
+
+ deliveryContext.requeue(message);
+
+ _unacknowledgedMessageMap.remove(deliveryTag);
+ }
+ }
+
+ /**
+ * Callback indicating that a queue has been deleted. We must update the structure of unacknowledged messages to
+ * remove the queue reference and also decrement any message reference counts, without actually removing the item
+ * 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
+ *
+ */
+ /* public void queueDeleted(final AMQQueue queue)
+ {
+ try
+ {
+ _unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor()
+ {
+ public boolean callback(UnacknowledgedMessage message)
+ {
+ if (message.getQueue() == queue)
+ {
+ try
+ {
+ message.discard(_storeContext);
+ message.setQueueDeleted(true);
+
+ }
+ catch (AMQException e)
+ {
+ _log.error(
+ "Error decrementing ref count on message " + message.getMessage().getMessageId() + ": " + e, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ return false;
+ }
+
+ public void visitComplete()
+ {
+ }
+ });
+ }
+ catch (AMQException e)
+ {
+ _log.error("Unexpected Error while handling deletion of queue", e);
+ throw new RuntimeException(e);
+ }
+
+ }
+*/
+ /**
+ * Acknowledge one or more messages.
+ *
+ * @param deliveryTag the last delivery tag
+ * @param multiple if true will acknowledge all messages up to an including the delivery tag. if false only
+ * acknowledges the single message specified by the delivery tag
+ *
+ * @throws AMQException if the delivery tag is unknown (e.g. not outstanding) on this channel
+ */
+ public void acknowledgeMessage(long deliveryTag, boolean multiple) throws AMQException
+ {
+ _unacknowledgedMessageMap.acknowledgeMessage(deliveryTag, multiple, _txnContext);
+ }
+
+ /**
+ * Used only for testing purposes.
+ *
+ * @return the map of unacknowledged messages
+ */
+ public UnacknowledgedMessageMap getUnacknowledgedMessageMap()
+ {
+ return _unacknowledgedMessageMap;
+ }
+
+
+ public void setSuspended(boolean suspended)
+ {
+
+
+ boolean wasSuspended = _suspended.getAndSet(suspended);
+ if (wasSuspended != suspended)
+ {
+ if (wasSuspended)
+ {
+ // may need to deliver queued messages
+ for (Subscription s : _tag2SubscriptionMap.values())
+ {
+ s.getQueue().deliverAsync(s);
+ }
+ }
+ }
+ }
+
+ public boolean isSuspended()
+ {
+ return _suspended.get();
+ }
+
+ public void commit() throws AMQException
+ {
+ if (!isTransactional())
+ {
+ throw new AMQException("Fatal error: commit called on non-transactional channel");
+ }
+
+ _txnContext.commit();
+ }
+
+ public void rollback() throws AMQException
+ {
+ _txnContext.rollback();
+ }
+
+ public String toString()
+ {
+ return "["+_session.toString()+":"+_channelId+"]";
+ }
+
+ public void setDefaultQueue(AMQQueue queue)
+ {
+ _defaultQueue = queue;
+ }
+
+ public AMQQueue getDefaultQueue()
+ {
+ return _defaultQueue;
+ }
+
+ public StoreContext getStoreContext()
+ {
+ return _storeContext;
+ }
+
+ public void processReturns() throws AMQException
+ {
+ if (!_returnMessages.isEmpty())
+ {
+ for (RequiredDeliveryException bouncedMessage : _returnMessages)
+ {
+ AMQMessage message = bouncedMessage.getAMQMessage();
+ _session.getProtocolOutputConverter().writeReturn(message, _channelId, bouncedMessage.getReplyCode().getCode(),
+ new AMQShortString(bouncedMessage.getMessage()));
+
+ message.decrementReference(_storeContext);
+ }
+
+ _returnMessages.clear();
+ }
+ }
+
+
+ public TransactionalContext getTransactionalContext()
+ {
+ return _txnContext;
+ }
+
+ public boolean isClosing()
+ {
+ return _closing;
+ }
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _session;
+ }
+
+ public FlowCreditManager getCreditManager()
+ {
+ return _creditManager;
+ }
+
+ public void setCredit(final long prefetchSize, final int prefetchCount)
+ {
+ _creditManager.setCreditLimits(prefetchSize, prefetchCount);
+ }
+
+ public List<RequiredDeliveryException> getReturnMessages()
+ {
+ return _returnMessages;
+ }
+
+ public MessageStore getMessageStore()
+ {
+ return _messageStore;
+ }
+
+ private final ClientDeliveryMethod _clientDeliveryMethod = new ClientDeliveryMethod()
+ {
+
+ public void deliverToClient(final Subscription sub, final QueueEntry entry, final long deliveryTag)
+ throws AMQException
+ {
+ getProtocolSession().getProtocolOutputConverter().writeDeliver(entry.getMessage(), getChannelId(), deliveryTag, sub.getConsumerTag());
+ }
+ };
+
+ public ClientDeliveryMethod getClientDeliveryMethod()
+ {
+ return _clientDeliveryMethod;
+ }
+
+ private final RecordDeliveryMethod _recordDeliveryMethod = new RecordDeliveryMethod()
+ {
+
+ public void recordMessageDelivery(final Subscription sub, final QueueEntry entry, final long deliveryTag)
+ {
+ addUnacknowledgedMessage(entry, deliveryTag, sub);
+ }
+ };
+
+ public RecordDeliveryMethod getRecordDeliveryMethod()
+ {
+ return _recordDeliveryMethod;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java
new file mode 100644
index 0000000000..9a98af5689
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java
@@ -0,0 +1,25 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server;
+
+public class ConsumerTagNotUniqueException extends Exception
+{
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ExtractResendAndRequeue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ExtractResendAndRequeue.java
new file mode 100644
index 0000000000..29494c4118
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ExtractResendAndRequeue.java
@@ -0,0 +1,108 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server;
+
+import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMapImpl;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.AMQException;
+import org.apache.log4j.Logger;
+
+import java.util.Map;
+
+public class ExtractResendAndRequeue implements UnacknowledgedMessageMap.Visitor
+{
+ private static final Logger _log = Logger.getLogger(ExtractResendAndRequeue.class);
+
+ private Map<Long, QueueEntry> _msgToRequeue;
+ private Map<Long, QueueEntry> _msgToResend;
+ private boolean _requeueIfUnabletoResend;
+ private StoreContext _storeContext;
+ private UnacknowledgedMessageMap _unacknowledgedMessageMap;
+
+ public ExtractResendAndRequeue(UnacknowledgedMessageMap unacknowledgedMessageMap,
+ Map<Long, QueueEntry> msgToRequeue,
+ Map<Long, QueueEntry> msgToResend,
+ boolean requeueIfUnabletoResend,
+ StoreContext storeContext)
+ {
+ _unacknowledgedMessageMap = unacknowledgedMessageMap;
+ _msgToRequeue = msgToRequeue;
+ _msgToResend = msgToResend;
+ _requeueIfUnabletoResend = requeueIfUnabletoResend;
+ _storeContext = storeContext;
+ }
+
+ public boolean callback(final long deliveryTag, QueueEntry message) throws AMQException
+ {
+
+ AMQMessage msg = message.getMessage();
+ msg.setRedelivered(true);
+ final Subscription subscription = message.getDeliveredSubscription();
+ if (subscription != null)
+ {
+ // Consumer exists
+ if (!subscription.isClosed())
+ {
+ _msgToResend.put(deliveryTag, message);
+ }
+ else // consumer has gone
+ {
+ _msgToRequeue.put(deliveryTag, message);
+ }
+ }
+ else
+ {
+ // Message has no consumer tag, so was "delivered" to a GET
+ // or consumer no longer registered
+ // cannot resend, so re-queue.
+ if (!message.isQueueDeleted())
+ {
+ if (_requeueIfUnabletoResend)
+ {
+ _msgToRequeue.put(deliveryTag, message);
+ }
+ else
+ {
+ message.discard(_storeContext);
+ _log.info("No DeadLetter Queue and requeue not requested so dropping message:" + message);
+ }
+ }
+ else
+ {
+ message.discard(_storeContext);
+ _log.warn("Message.queue is null and no DeadLetter Queue so dropping message:" + message);
+ }
+ }
+
+ // false means continue processing
+ return false;
+ }
+
+ public void visitComplete()
+ {
+ _unacknowledgedMessageMap.clear();
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java
new file mode 100644
index 0000000000..49619ac5b0
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java
@@ -0,0 +1,509 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.BindException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+
+import javax.management.NotCompliantMBeanException;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.PosixParser;
+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.FixedSizeByteBufferAllocator;
+import org.apache.mina.common.IoAcceptor;
+import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
+import org.apache.mina.transport.socket.nio.SocketSessionConfig;
+import org.apache.mina.util.NewThreadExecutor;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.common.QpidProperties;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.pool.ReadWriteThreadModel;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.configuration.management.ConfigurationManagementMBean;
+import org.apache.qpid.server.logging.management.LoggingManagementMBean;
+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;
+
+/**
+ * Main entry point for AMQPD.
+ *
+ */
+@SuppressWarnings({"AccessStaticViaInstance"})
+public class Main
+{
+ private static final Logger _logger = Logger.getLogger(Main.class);
+ public static final Logger _brokerLogger = Logger.getLogger("Qpid.Broker");
+
+ private static final String DEFAULT_CONFIG_FILE = "etc/config.xml";
+
+ private static final String DEFAULT_LOG_CONFIG_FILENAME = "log4j.xml";
+ public static final String QPID_HOME = "QPID_HOME";
+ private static final int IPV4_ADDRESS_LENGTH = 4;
+
+ private static final char IPV4_LITERAL_SEPARATOR = '.';
+
+ protected static class InitException extends Exception
+ {
+ InitException(String msg, Throwable cause)
+ {
+ super(msg, cause);
+ }
+ }
+
+ 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 mport =
+ OptionBuilder.withArgName("mport").hasArg()
+ .withDescription("listen on the specified management port. Overrides any value in the config file")
+ .withLongOpt("mport").create("m");
+
+
+ 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(mport);
+ 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 = QpidProperties.getVersionString();
+
+ StringBuilder protocol = new StringBuilder("AMQP version(s) [major.minor]: ");
+
+ boolean first = true;
+ for (ProtocolVersion pv : ProtocolVersion.getSupportedProtocolVersions())
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ protocol.append(", ");
+ }
+
+ protocol.append(pv.getMajorVersion()).append('-').append(pv.getMinorVersion());
+
+ }
+
+ System.out.println(ver + " (" + protocol + ")");
+ }
+ else
+ {
+ try
+ {
+ startup();
+ }
+ catch (InitException e)
+ {
+ System.out.println(e.getMessage());
+ _brokerLogger.error("Initialisation Error : " + e.getMessage());
+ shutdown(1);
+ }
+ catch (Throwable e)
+ {
+ System.out.println("Error initialising message broker: " + e);
+ _brokerLogger.error("Error initialising message broker: " + e);
+ e.printStackTrace();
+ shutdown(1);
+ }
+ }
+ }
+
+ protected void shutdown(int status)
+ {
+ ApplicationRegistry.removeAll();
+ System.exit(status);
+ }
+
+ protected void startup() throws 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, null);
+ }
+ else
+ {
+ System.out.println("Using configuration file " + configFile.getAbsolutePath());
+ }
+
+ String logConfig = commandLine.getOptionValue("l");
+ String logWatchConfig = commandLine.getOptionValue("w", "0");
+
+ 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");
+ }
+
+ File logConfigFile;
+ if (logConfig != null)
+ {
+ logConfigFile = new File(logConfig);
+ configureLogging(logConfigFile, logWatchTime);
+ }
+ else
+ {
+ File configFileDirectory = configFile.getParentFile();
+ logConfigFile = new File(configFileDirectory, DEFAULT_LOG_CONFIG_FILENAME);
+ configureLogging(logConfigFile, logWatchTime);
+ }
+
+ ConfigurationFileApplicationRegistry config = new ConfigurationFileApplicationRegistry(configFile);
+ ServerConfiguration serverConfig = config.getConfiguration();
+ updateManagementPort(serverConfig, commandLine.getOptionValue("m"));
+
+ ApplicationRegistry.initialise(config);
+
+ configureLoggingManagementMBean(logConfigFile, logWatchTime);
+
+ ConfigurationManagementMBean configMBean = new ConfigurationManagementMBean();
+ configMBean.register();
+
+ //fixme .. use QpidProperties.getVersionString when we have fixed the classpath issues
+ // that are causing the broker build to pick up the wrong properties file and hence say
+ // Starting Qpid Client
+ _brokerLogger.info("Starting Qpid Broker " + QpidProperties.getReleaseVersion()
+ + " build: " + QpidProperties.getBuildVersion());
+
+ ByteBuffer.setUseDirectBuffers(serverConfig.getEnableDirectBuffers());
+
+ // 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 (!serverConfig.getEnablePooledAllocator())
+ {
+ ByteBuffer.setAllocator(new FixedSizeByteBufferAllocator());
+ }
+
+ if(serverConfig.getUseBiasedWrites())
+ {
+ System.setProperty("org.apache.qpid.use_write_biased_pool","true");
+ }
+
+ int port = serverConfig.getPort();
+
+ String portStr = commandLine.getOptionValue("p");
+ if (portStr != null)
+ {
+ try
+ {
+ port = Integer.parseInt(portStr);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new InitException("Invalid port: " + portStr, e);
+ }
+ }
+
+ bind(port, serverConfig);
+ }
+
+ /**
+ * Update the configuration data with the management port.
+ * @param configuration
+ * @param managementPort The string from the command line
+ */
+ private void updateManagementPort(ServerConfiguration configuration, String managementPort)
+ {
+ if (managementPort != null)
+ {
+ try
+ {
+ configuration.setJMXManagementPort(Integer.parseInt(managementPort));
+ }
+ catch (NumberFormatException e)
+ {
+ _logger.warn("Invalid management port: " + managementPort + " will use:" + configuration.getJMXManagementPort(), e);
+ }
+ }
+ }
+
+ protected void bind(int port, ServerConfiguration config) throws BindException
+ {
+ String bindAddr = commandLine.getOptionValue("b");
+ if (bindAddr == null)
+ {
+ bindAddr = config.getBind();
+ }
+
+ try
+ {
+ IoAcceptor acceptor;
+
+ if (ApplicationRegistry.getInstance().getConfiguration().getQpidNIO())
+ {
+ _logger.warn("Using Qpid Multithreaded IO Processing");
+ acceptor = new org.apache.mina.transport.socket.nio.MultiThreadSocketAcceptor(config.getProcessors(), new NewThreadExecutor());
+ }
+ else
+ {
+ _logger.warn("Using Mina IO Processing");
+ acceptor = new org.apache.mina.transport.socket.nio.SocketAcceptor(config.getProcessors(), new NewThreadExecutor());
+ }
+
+ SocketAcceptorConfig sconfig = (SocketAcceptorConfig) acceptor.getDefaultConfig();
+ SocketSessionConfig sc = (SocketSessionConfig) sconfig.getSessionConfig();
+
+ sc.setReceiveBufferSize(config.getReceiveBufferSize());
+ sc.setSendBufferSize(config.getWriteBufferSize());
+ sc.setTcpNoDelay(config.getTcpNoDelay());
+
+ // if we do not use the executor pool threading model we get the default leader follower
+ // implementation provided by MINA
+ if (config.getEnableExecutorPool())
+ {
+ sconfig.setThreadModel(ReadWriteThreadModel.getInstance());
+ }
+
+ if (!config.getEnableSSL() || !config.getSSLOnly())
+ {
+ AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler();
+ InetSocketAddress bindAddress;
+ if (bindAddr.equals("wildcard"))
+ {
+ bindAddress = new InetSocketAddress(port);
+ }
+ else
+ {
+ bindAddress = new InetSocketAddress(InetAddress.getByAddress(parseIP(bindAddr)), port);
+ }
+
+ bind(acceptor, bindAddress, handler, sconfig);
+
+ //fixme qpid.AMQP should be using qpidproperties to get value
+ _brokerLogger.info("Qpid.AMQP listening on non-SSL address " + bindAddress);
+ }
+
+ if (config.getEnableSSL())
+ {
+ AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler();
+ try
+ {
+
+ bind(acceptor, new InetSocketAddress(config.getSSLPort()), handler, sconfig);
+
+ //fixme qpid.AMQP should be using qpidproperties to get value
+ _brokerLogger.info("Qpid.AMQP listening on SSL port " + config.getSSLPort());
+
+ }
+ catch (IOException e)
+ {
+ _brokerLogger.error("Unable to listen on SSL port: " + e, e);
+ }
+ }
+
+ //fixme qpid.AMQP should be using qpidproperties to get value
+ _brokerLogger.info("Qpid Broker Ready :" + QpidProperties.getReleaseVersion()
+ + " build: " + QpidProperties.getBuildVersion());
+ }
+ catch (Exception e)
+ {
+ _logger.error("Unable to bind service to registry: " + e, e);
+ //fixme this need tidying up
+ throw new BindException(e.getMessage());
+ }
+ }
+
+ /**
+ * Ensure that any bound Acceptors are recorded in the registry so they can be closed later.
+ *
+ * @param acceptor
+ * @param bindAddress
+ * @param handler
+ * @param sconfig
+ *
+ * @throws IOException from the acceptor.bind command
+ */
+ private void bind(IoAcceptor acceptor, InetSocketAddress bindAddress, AMQPFastProtocolHandler handler, SocketAcceptorConfig sconfig) throws IOException
+ {
+ acceptor.bind(bindAddress, handler, sconfig);
+
+ ApplicationRegistry.getInstance().addAcceptor(bindAddress, acceptor);
+ }
+
+ public static void main(String[] args)
+ {
+
+ new Main(args);
+ }
+
+ private byte[] parseIP(String address) throws Exception
+ {
+ char[] literalBuffer = address.toCharArray();
+ int byteCount = 0;
+ int currByte = 0;
+ byte[] ip = new byte[IPV4_ADDRESS_LENGTH];
+ for (int i = 0; i < literalBuffer.length; i++)
+ {
+ char currChar = literalBuffer[i];
+ if ((currChar >= '0') && (currChar <= '9'))
+ {
+ currByte = (currByte * 10) + (Character.digit(currChar, 10) & 0xFF);
+ }
+
+ if (currChar == IPV4_LITERAL_SEPARATOR || (i + 1 == literalBuffer.length))
+ {
+ ip[byteCount++] = (byte) currByte;
+ currByte = 0;
+ }
+ }
+
+ if (byteCount != 4)
+ {
+ throw new Exception("Invalid IP address: " + address);
+ }
+ return ip;
+ }
+
+ private void configureLogging(File logConfigFile, int logWatchTime)
+ {
+ 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 configureLoggingManagementMBean(File logConfigFile, int logWatchTime) throws Exception
+ {
+ LoggingManagementMBean blm = new LoggingManagementMBean(logConfigFile.getPath(),logWatchTime);
+
+ try
+ {
+ blm.register();
+ }
+ catch (AMQException e)
+ {
+ throw new InitException("Unable to initialise the Logging Management MBean: ", e);
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java
new file mode 100644
index 0000000000..e76f9c3f6c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java
@@ -0,0 +1,68 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server;
+
+import java.io.IOException;
+
+import javax.management.JMException;
+
+/**
+ * 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/qpid/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java
new file mode 100644
index 0000000000..3f1947d65a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java
@@ -0,0 +1,79 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+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.
+ *
+ * <p/>The failed message is associated with this error condition, by taking a reference to it. This enables the
+ * correct compensating action to be taken against the message, for example, bouncing it back to the sender.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represent failure to deliver a message that must be delivered.
+ * <tr><td> Associate the failed message with the error condition. <td> {@link AMQMessage}
+ * </table>
+ */
+public abstract class RequiredDeliveryException extends AMQException
+{
+ private AMQMessage _amqMessage;
+
+ public RequiredDeliveryException(String message, AMQMessage payload)
+ {
+ super(message);
+
+ setMessage(payload);
+ }
+
+
+ public RequiredDeliveryException(String message)
+ {
+ super(message);
+ }
+
+ public void setMessage(final AMQMessage payload)
+ {
+
+ // Increment the reference as this message is in the routing phase
+ // and so will have the ref decremented as routing fails.
+ // we need to keep this message around so we can return it in the
+ // handler. So increment here.
+ _amqMessage = payload.takeReference();
+
+ }
+
+ public AMQMessage getAMQMessage()
+ {
+ return _amqMessage;
+ }
+
+ public AMQConstant getErrorCode()
+ {
+ return getReplyCode();
+ }
+
+ public abstract AMQConstant getReplyCode();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java
new file mode 100644
index 0000000000..db3a05eb52
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java
@@ -0,0 +1,148 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.ack;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.txn.TxnOp;
+import org.apache.qpid.server.queue.QueueEntry;
+
+/**
+ * A TxnOp implementation for handling accumulated acks
+ */
+public class TxAck implements TxnOp
+{
+ private final UnacknowledgedMessageMap _map;
+ private final Map<Long, QueueEntry> _unacked = new HashMap<Long,QueueEntry>();
+ private List<Long> _individual;
+ private long _deliveryTag;
+ private boolean _multiple;
+
+ public TxAck(UnacknowledgedMessageMap map)
+ {
+ _map = map;
+ }
+
+ public void update(long deliveryTag, boolean multiple)
+ {
+ _unacked.clear();
+ if (!multiple)
+ {
+ if(_individual == null)
+ {
+ _individual = new ArrayList<Long>();
+ }
+ //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()
+ {
+ if(_unacked.isEmpty())
+ {
+ //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);
+ }
+ if(_individual != null)
+ {
+ //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
+ {
+ consolidate();
+ //if any of the messages in unacked are persistent the txn
+ //buffer must be marked as persistent:
+ for (QueueEntry msg : _unacked.values())
+ {
+ if (msg.getMessage().isPersistent())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void prepare(StoreContext storeContext) throws AMQException
+ {
+ //make persistent changes, i.e. dequeue and decrementReference
+ for (QueueEntry msg : _unacked.values())
+ {
+ //Message has been ack so discard it. This will dequeue and decrement the reference.
+ msg.discard(storeContext);
+
+ }
+ }
+
+ 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 (QueueEntry msg : _unacked.values())
+ {
+ msg.getMessage().takeReference();
+ }
+ }
+
+ public void commit(StoreContext storeContext)
+ {
+ //remove the unacked messages from the channels map
+ _map.remove(_unacked);
+ }
+
+ public void rollback(StoreContext storeContext)
+ {
+ }
+}
+
+
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java
new file mode 100644
index 0000000000..c80a96f967
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java
@@ -0,0 +1,81 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.ack;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.Map;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.store.StoreContext;
+
+public interface UnacknowledgedMessageMap
+{
+ public interface Visitor
+ {
+ /**
+ * @param deliveryTag
+ *@param message the message being iterated over @return true to stop iteration, false to continue
+ * @throws AMQException
+ */
+ boolean callback(final long deliveryTag, QueueEntry message) throws AMQException;
+
+ void visitComplete();
+ }
+
+ void visit(Visitor visitor) throws AMQException;
+
+ void add(long deliveryTag, QueueEntry message);
+
+ void collect(long deliveryTag, boolean multiple, Map<Long, QueueEntry> msgs);
+
+ boolean contains(long deliveryTag) throws AMQException;
+
+ void remove(Map<Long,QueueEntry> msgs);
+
+ QueueEntry remove(long deliveryTag);
+
+ public void drainTo(long deliveryTag, StoreContext storeContext) throws AMQException;
+
+ Collection<QueueEntry> cancelAllMessages();
+
+ void acknowledgeMessage(long deliveryTag, boolean multiple, TransactionalContext txnContext) throws AMQException;
+
+ int size();
+
+ void clear();
+
+ QueueEntry get(long deliveryTag);
+
+ /**
+ * Get the set of delivery tags that are outstanding.
+ *
+ * @return a set of delivery tags
+ */
+ Set<Long> getDeliveryTags();
+
+ public long getUnacknowledgeBytes();
+}
+
+
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java
new file mode 100644
index 0000000000..c567387662
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java
@@ -0,0 +1,232 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.ack;
+
+import org.apache.qpid.server.store.StoreContext;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.txn.TransactionalContext;
+
+public class UnacknowledgedMessageMapImpl implements UnacknowledgedMessageMap
+{
+ private final Object _lock = new Object();
+
+ private long _unackedSize;
+
+ private Map<Long, QueueEntry> _map;
+
+ private long _lastDeliveryTag;
+
+ private final int _prefetchLimit;
+
+ public UnacknowledgedMessageMapImpl(int prefetchLimit)
+ {
+ _prefetchLimit = prefetchLimit;
+ _map = new LinkedHashMap<Long, QueueEntry>(prefetchLimit);
+ }
+
+ public void collect(long deliveryTag, boolean multiple, Map<Long, QueueEntry> msgs)
+ {
+ if (multiple)
+ {
+ collect(deliveryTag, msgs);
+ }
+ else
+ {
+ msgs.put(deliveryTag, get(deliveryTag));
+ }
+
+ }
+
+ public boolean contains(long deliveryTag) throws AMQException
+ {
+ synchronized (_lock)
+ {
+ return _map.containsKey(deliveryTag);
+ }
+ }
+
+ public void remove(Map<Long,QueueEntry> msgs)
+ {
+ synchronized (_lock)
+ {
+ for (Long deliveryTag : msgs.keySet())
+ {
+ remove(deliveryTag);
+ }
+ }
+ }
+
+ public QueueEntry remove(long deliveryTag)
+ {
+ synchronized (_lock)
+ {
+
+ QueueEntry message = _map.remove(deliveryTag);
+ if(message != null)
+ {
+ _unackedSize -= message.getMessage().getSize();
+
+ }
+
+ return message;
+ }
+ }
+
+ public void visit(Visitor visitor) throws AMQException
+ {
+ synchronized (_lock)
+ {
+ Set<Map.Entry<Long, QueueEntry>> currentEntries = _map.entrySet();
+ for (Map.Entry<Long, QueueEntry> entry : currentEntries)
+ {
+ visitor.callback(entry.getKey().longValue(), entry.getValue());
+ }
+ visitor.visitComplete();
+ }
+ }
+
+ public void add(long deliveryTag, QueueEntry message)
+ {
+ synchronized (_lock)
+ {
+ _map.put(deliveryTag, message);
+ _unackedSize += message.getMessage().getSize();
+ _lastDeliveryTag = deliveryTag;
+ }
+ }
+
+ public Collection<QueueEntry> cancelAllMessages()
+ {
+ synchronized (_lock)
+ {
+ Collection<QueueEntry> currentEntries = _map.values();
+ _map = new LinkedHashMap<Long, QueueEntry>(_prefetchLimit);
+ _unackedSize = 0l;
+ 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();
+ _unackedSize = 0l;
+ }
+ }
+
+ public void drainTo(long deliveryTag, StoreContext storeContext) throws AMQException
+
+ {
+ synchronized (_lock)
+ {
+ Iterator<Map.Entry<Long, QueueEntry>> it = _map.entrySet().iterator();
+ while (it.hasNext())
+ {
+ Map.Entry<Long, QueueEntry> 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());
+ }
+
+ //Message has been ack so discard it. This will dequeue and decrement the reference.
+ unacked.getValue().discard(storeContext);
+
+ it.remove();
+
+ _unackedSize -= unacked.getValue().getMessage().getSize();
+
+
+ if (unacked.getKey() == deliveryTag)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ public QueueEntry get(long key)
+ {
+ synchronized (_lock)
+ {
+ return _map.get(key);
+ }
+ }
+
+ public Set<Long> getDeliveryTags()
+ {
+ synchronized (_lock)
+ {
+ return _map.keySet();
+ }
+ }
+
+ private void collect(long key, Map<Long, QueueEntry> msgs)
+ {
+ synchronized (_lock)
+ {
+ for (Map.Entry<Long, QueueEntry> entry : _map.entrySet())
+ {
+ msgs.put(entry.getKey(),entry.getValue());
+ if (entry.getKey() == key)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ public long getUnacknowledgeBytes()
+ {
+ return _unackedSize;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java
new file mode 100644
index 0000000000..c7cf0c0892
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.configuration;
+
+import org.apache.commons.configuration.Configuration;
+
+
+public class ExchangeConfiguration
+{
+
+ private Configuration _config;
+ private String _name;
+
+ public ExchangeConfiguration(String exchName, Configuration subset)
+ {
+ _name = exchName;
+ _config = subset;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public String getType()
+ {
+ return _config.getString("type","direct");
+ }
+
+ public boolean getDurable()
+ {
+ return _config.getBoolean("durable", false);
+ }
+
+ public boolean getAutoDelete()
+ {
+ return _config.getBoolean("autodelete",false);
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java
new file mode 100644
index 0000000000..74bb7ee969
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java
@@ -0,0 +1,111 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.configuration;
+
+import java.util.List;
+
+import org.apache.commons.configuration.Configuration;
+
+public class QueueConfiguration
+{
+
+ private Configuration _config;
+ private String _name;
+ private VirtualHostConfiguration _vHostConfig;
+
+ public QueueConfiguration(String name, Configuration config, VirtualHostConfiguration virtualHostConfiguration)
+ {
+ _vHostConfig = virtualHostConfiguration;
+ _config = config;
+ _name = name;
+ }
+
+ public VirtualHostConfiguration getVirtualHostConfiguration()
+ {
+ return _vHostConfig;
+ }
+
+ public boolean getDurable()
+ {
+ return _config.getBoolean("durable" ,false);
+ }
+
+ public boolean getAutoDelete()
+ {
+ return _config.getBoolean("autodelete", false);
+ }
+
+ public String getOwner()
+ {
+ return _config.getString("owner", null);
+ }
+
+ public boolean getPriority()
+ {
+ return _config.getBoolean("priority", false);
+ }
+
+ public int getPriorities()
+ {
+ return _config.getInt("priorities", -1);
+ }
+
+ public String getExchange()
+ {
+ return _config.getString("exchange", null);
+ }
+
+ public List getRoutingKeys()
+ {
+ return _config.getList("routingKey");
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public int getMaximumMessageAge()
+ {
+ return _config.getInt("maximumMessageAge", _vHostConfig.getMaximumMessageAge());
+ }
+
+ public long getMaximumQueueDepth()
+ {
+ return _config.getLong("maximumQueueDepth", _vHostConfig.getMaximumQueueDepth());
+ }
+
+ public long getMaximumMessageSize()
+ {
+ return _config.getLong("maximumMessageSize", _vHostConfig.getMaximumMessageSize());
+ }
+
+ public long getMaximumMessageCount()
+ {
+ return _config.getLong("maximumMessageCount", _vHostConfig.getMaximumMessageCount());
+ }
+
+ public long getMinimumAlertRepeatGap()
+ {
+ return _config.getLong("minimumAlertRepeatGap", _vHostConfig.getMinimumAlertRepeatGap());
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SecurityConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SecurityConfiguration.java
new file mode 100644
index 0000000000..5d080f8df1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SecurityConfiguration.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+
+package org.apache.qpid.server.configuration;
+
+import org.apache.commons.configuration.Configuration;
+
+public class SecurityConfiguration
+{
+
+ private Configuration _conf;
+
+ public SecurityConfiguration(Configuration configuration)
+ {
+ _conf = configuration;
+ }
+
+ public Configuration getConfiguration()
+ {
+ return _conf;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java
new file mode 100644
index 0000000000..28a07e7ebf
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java
@@ -0,0 +1,519 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.configuration;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.ConfigurationFactory;
+import org.apache.commons.configuration.SystemConfiguration;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.qpid.server.configuration.management.ConfigurationManagementMBean;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import sun.misc.Signal;
+import sun.misc.SignalHandler;
+
+public class ServerConfiguration implements SignalHandler
+{
+
+ private static Configuration _config;
+
+ private static final int DEFAULT_FRAME_SIZE = 65536;
+ private static final int DEFAULT_BUFFER_READ_LIMIT_SIZE = 262144;
+ private static final int DEFAULT_BUFFER_WRITE_LIMIT_SIZE = 262144;
+ private static final int DEFAULT_PORT = 5672;
+ private static final int DEFAUL_SSL_PORT = 8672;
+ private static final long DEFAULT_HOUSEKEEPING_PERIOD = 30000L;
+ private static final int DEFAULT_JMXPORT = 8999;
+
+ private static int _jmxPort = DEFAULT_JMXPORT;
+
+ private Map<String, VirtualHostConfiguration> _virtualHosts = new HashMap<String, VirtualHostConfiguration>();
+ private SecurityConfiguration _securityConfiguration = null;
+
+ private File _configFile;
+
+ private Logger _log = LoggerFactory.getLogger(this.getClass());
+
+ private ConfigurationManagementMBean _mbean;
+
+
+ // Map of environment variables to config items
+ private static final Map<String, String> envVarMap = new HashMap<String, String>();
+
+ {
+ envVarMap.put("QPID_PORT", "connector.port");
+ envVarMap.put("QPID_ENABLEDIRECTBUFFERS", "advanced.enableDirectBuffers");
+ envVarMap.put("QPID_SSLPORT", "connector.ssl.port");
+ envVarMap.put("QPID_NIO", "connector.qpidnio");
+ envVarMap.put("QPID_WRITEBIASED", "advanced.useWriteBiasedPool");
+ envVarMap.put("QPID_JMXPORT", "management.jmxport");
+ envVarMap.put("QPID_FRAMESIZE", "advanced.framesize");
+ envVarMap.put("QPID_MSGAUTH", "security.msg-auth");
+ envVarMap.put("QPID_AUTOREGISTER", "auto_register");
+ envVarMap.put("QPID_MANAGEMENTENABLED", "management.enabled");
+ envVarMap.put("QPID_HEARTBEATDELAY", "heartbeat.delay");
+ envVarMap.put("QPID_HEARTBEATTIMEOUTFACTOR", "heartbeat.timeoutFactor");
+ envVarMap.put("QPID_MAXIMUMMESSAGEAGE", "maximumMessageAge");
+ envVarMap.put("QPID_MAXIMUMMESSAGECOUNT", "maximumMessageCount");
+ envVarMap.put("QPID_MAXIMUMQUEUEDEPTH", "maximumQueueDepth");
+ envVarMap.put("QPID_MAXIMUMMESSAGESIZE", "maximumMessageSize");
+ envVarMap.put("QPID_MINIMUMALERTREPEATGAP", "minimumAlertRepeatGap");
+ envVarMap.put("QPID_SOCKETRECEIVEBUFFER", "connector.socketReceiveBuffer");
+ envVarMap.put("QPID_SOCKETWRITEBUFFER", "connector.socketWriteBuffer");
+ envVarMap.put("QPID_TCPNODELAY", "connector.tcpNoDelay");
+ envVarMap.put("QPID_ENABLEPOOLEDALLOCATOR", "advanced.enablePooledAllocator");
+ }
+
+ public ServerConfiguration(File configurationURL) throws ConfigurationException
+ {
+ this(parseConfig(configurationURL));
+ _configFile = configurationURL;
+ sun.misc.Signal.handle(new sun.misc.Signal("HUP"), this);
+ }
+
+ public ServerConfiguration(Configuration conf) throws ConfigurationException
+ {
+ _config = conf;
+
+ substituteEnvironmentVariables();
+
+ _jmxPort = _config.getInt("management.jmxport", 8999);
+ _securityConfiguration = new SecurityConfiguration(conf.subset("security"));
+
+ setupVirtualHosts(conf);
+
+ }
+
+ private void setupVirtualHosts(Configuration conf) throws ConfigurationException
+ {
+ List vhosts = conf.getList("virtualhosts");
+ Iterator i = vhosts.iterator();
+ while (i.hasNext())
+ {
+ Object thing = i.next();
+ if (thing instanceof String)
+ {
+ XMLConfiguration vhostConfiguration = new XMLConfiguration((String) thing);
+ List hosts = vhostConfiguration.getList("virtualhost.name");
+ for (int j = 0; j < hosts.size(); j++)
+ {
+ String name = (String) hosts.get(j);
+ // Add the keys of the virtual host to the main config then bail out
+
+ Configuration myConf = vhostConfiguration.subset("virtualhost." + name);
+ Iterator k = myConf.getKeys();
+ while (k.hasNext())
+ {
+ String key = (String) k.next();
+ conf.setProperty("virtualhosts.virtualhost."+name+"."+key, myConf.getProperty(key));
+ }
+ VirtualHostConfiguration vhostConfig = new VirtualHostConfiguration(name, conf.subset("virtualhosts.virtualhost."+name));
+ _virtualHosts.put(vhostConfig.getName(), vhostConfig);
+ }
+ }
+ }
+ }
+
+ private void substituteEnvironmentVariables()
+ {
+ for (Entry<String, String> var : envVarMap.entrySet())
+ {
+ String val = System.getenv(var.getKey());
+ if (val != null)
+ {
+ _config.setProperty(var.getValue(), val);
+ }
+ }
+ }
+
+ private final static Configuration parseConfig(File file) throws ConfigurationException
+ {
+ ConfigurationFactory factory = new ConfigurationFactory();
+ factory.setConfigurationFileName(file.getAbsolutePath());
+ Configuration conf = factory.getConfiguration();
+ Iterator keys = conf.getKeys();
+ if (!keys.hasNext())
+ {
+ keys = null;
+ conf = flatConfig(file);
+ }
+ return conf;
+ }
+
+ // 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 final static Configuration flatConfig(File file) 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(file)
+ {
+ protected String interpolate(String o)
+ {
+ return conf.interpolate(o);
+ }
+ });
+ return conf;
+ }
+
+ @Override
+ public void handle(Signal arg0)
+ {
+ try
+ {
+ reparseConfigFile();
+ }
+ catch (ConfigurationException e)
+ {
+ _log.error("Could not reload configuration file", e);
+ }
+ }
+
+ public void reparseConfigFile() throws ConfigurationException
+ {
+ if (_configFile != null)
+ {
+ Configuration newConfig = parseConfig(_configFile);
+ _securityConfiguration = new SecurityConfiguration(newConfig.subset("security"));
+ ApplicationRegistry.getInstance().getAccessManager().configurePlugins(_securityConfiguration);
+
+ VirtualHostRegistry vhostRegistry = ApplicationRegistry.getInstance().getVirtualHostRegistry();
+ for (String hostname : _virtualHosts.keySet())
+ {
+ VirtualHost vhost = vhostRegistry.getVirtualHost(hostname);
+ SecurityConfiguration hostSecurityConfig = new SecurityConfiguration(newConfig.subset("virtualhosts.virtualhost."+hostname+".security"));
+ vhost.getAccessManager().configureHostPlugins(hostSecurityConfig);
+ }
+ }
+ }
+
+ public String getQpidWork()
+ {
+ return System.getProperty("QPID_WORK", System.getProperty("java.io.tmpdir"));
+ }
+
+ public void setJMXManagementPort(int mport)
+ {
+ _jmxPort = mport;
+ }
+
+ public int getJMXManagementPort()
+ {
+ return _jmxPort;
+ }
+
+ public boolean getPlatformMbeanserver()
+ {
+ return _config.getBoolean("management.platform-mbeanserver", true);
+ }
+
+ public String[] getVirtualHosts()
+ {
+ return _virtualHosts.keySet().toArray(new String[_virtualHosts.size()]);
+ }
+
+ public String getPluginDirectory()
+ {
+ return _config.getString("plugin-directory");
+ }
+
+ public VirtualHostConfiguration getVirtualHostConfig(String name)
+ {
+ return _virtualHosts.get(name);
+ }
+
+ public List<String> getPrincipalDatabaseNames()
+ {
+ return _config.getList("security.principal-databases.principal-database.name");
+ }
+
+ public List<String> getPrincipalDatabaseClass()
+ {
+ return _config.getList("security.principal-databases.principal-database.class");
+ }
+
+ public List<String> getPrincipalDatabaseAttributeNames(int index)
+ {
+ String name = "security.principal-databases.principal-database(" + index + ")." + "attributes.attribute.name";
+ return _config.getList(name);
+ }
+
+ public List<String> getPrincipalDatabaseAttributeValues(int index)
+ {
+ String name = "security.principal-databases.principal-database(" + index + ")." + "attributes.attribute.value";
+ return _config.getList(name);
+ }
+
+ public List<String> getManagementPrincipalDBs()
+ {
+ return _config.getList("security.jmx.principal-database");
+ }
+
+ public List<String> getManagementAccessList()
+ {
+ return _config.getList("security.jmx.access");
+ }
+
+ public int getFrameSize()
+ {
+ return _config.getInt("advanced.framesize", DEFAULT_FRAME_SIZE);
+ }
+
+ public boolean getProtectIOEnabled()
+ {
+ return _config.getBoolean("broker.connector.protectio.enabled", false);
+ }
+
+ public int getBufferReadLimit()
+ {
+ return _config.getInt("broker.connector.protectio.readBufferLimitSize", DEFAULT_BUFFER_READ_LIMIT_SIZE);
+ }
+
+ public int getBufferWriteLimit()
+ {
+ return _config.getInt("broker.connector.protectio.writeBufferLimitSize", DEFAULT_BUFFER_WRITE_LIMIT_SIZE);
+ }
+
+ public boolean getSynchedClocks()
+ {
+ return _config.getBoolean("advanced.synced-clocks", false);
+ }
+
+ public boolean getMsgAuth()
+ {
+ return _config.getBoolean("security.msg-auth", false);
+ }
+
+ public String getJMXPrincipalDatabase()
+ {
+ return _config.getString("security.jmx.principal-database");
+ }
+
+ public String getManagementKeyStorePath()
+ {
+ return _config.getString("management.ssl.keyStorePath", null);
+ }
+
+ public boolean getManagementSSLEnabled()
+ {
+ return _config.getBoolean("management.ssl.enabled", true);
+ }
+
+ public String getManagementKeyStorePassword()
+ {
+ return _config.getString("management.ssl.keyStorePassword");
+ }
+
+ public SecurityConfiguration getSecurityConfiguration()
+ {
+ return _securityConfiguration;
+ }
+
+ public boolean getQueueAutoRegister()
+ {
+ return _config.getBoolean("queue.auto_register", true);
+ }
+
+ public boolean getManagementEnabled()
+ {
+ return _config.getBoolean("management.enabled", true);
+ }
+
+ public void setManagementEnabled(boolean enabled)
+ {
+ _config.setProperty("management.enabled", enabled);
+ }
+
+ public int getHeartBeatDelay()
+ {
+ return _config.getInt("heartbeat.delay", 5);
+ }
+
+ public double getHeartBeatTimeout()
+ {
+ return _config.getDouble("heartbeat.timeoutFactor", 2.0);
+ }
+
+ public int getDeliveryPoolSize()
+ {
+ return _config.getInt("delivery.poolsize", 0);
+ }
+
+ public long getMaximumMessageAge()
+ {
+ return _config.getLong("maximumMessageAge", 0);
+ }
+
+ public long getMaximumMessageCount()
+ {
+ return _config.getLong("maximumMessageCount", 0);
+ }
+
+ public long getMaximumQueueDepth()
+ {
+ return _config.getLong("maximumQueueDepth", 0);
+ }
+
+ public long getMaximumMessageSize()
+ {
+ return _config.getLong("maximumMessageSize", 0);
+ }
+
+ public long getMinimumAlertRepeatGap()
+ {
+ return _config.getLong("minimumAlertRepeatGap", 0);
+ }
+
+ public int getProcessors()
+ {
+ return _config.getInt("connector.processors", 4);
+ }
+
+ public int getPort()
+ {
+ return _config.getInt("connector.port", DEFAULT_PORT);
+ }
+
+ public String getBind()
+ {
+ return _config.getString("connector.bind", "wildcard");
+ }
+
+ public int getReceiveBufferSize()
+ {
+ return _config.getInt("connector.socketReceiveBuffer", 32767);
+ }
+
+ public int getWriteBufferSize()
+ {
+ return _config.getInt("connector.socketWriteBuffer", 32767);
+ }
+
+ public boolean getTcpNoDelay()
+ {
+ return _config.getBoolean("connector.tcpNoDelay", true);
+ }
+
+ public boolean getEnableExecutorPool()
+ {
+ return _config.getBoolean("advanced.filterchain[@enableExecutorPool]", false);
+ }
+
+ public boolean getEnablePooledAllocator()
+ {
+ return _config.getBoolean("advanced.enablePooledAllocator", false);
+ }
+
+ public boolean getEnableDirectBuffers()
+ {
+ return _config.getBoolean("advanced.enableDirectBuffers", false);
+ }
+
+ public boolean getEnableSSL()
+ {
+ return _config.getBoolean("connector.ssl.enabled", false);
+ }
+
+ public boolean getSSLOnly()
+ {
+ return _config.getBoolean("connector.ssl.sslOnly", true);
+ }
+
+ public int getSSLPort()
+ {
+ return _config.getInt("connector.ssl.port", DEFAUL_SSL_PORT);
+ }
+
+ public String getKeystorePath()
+ {
+ return _config.getString("connector.ssl.keystorePath", "none");
+ }
+
+ public String getKeystorePassword()
+ {
+ return _config.getString("connector.ssl.keystorePassword", "none");
+ }
+
+ public String getCertType()
+ {
+ return _config.getString("connector.ssl.certType", "SunX509");
+ }
+
+ public boolean getQpidNIO()
+ {
+ return _config.getBoolean("connector.qpidnio", false);
+ }
+
+ public boolean getUseBiasedWrites()
+ {
+ return _config.getBoolean("advanced.useWriteBiasedPool", false);
+ }
+
+ public String getDefaultVirtualHost()
+ {
+ return _config.getString("virtualhosts.default");
+ }
+
+ public void setHousekeepingExpiredMessageCheckPeriod(long value)
+ {
+ _config.setProperty("housekeeping.expiredMessageCheckPeriod", value);
+ }
+
+ public long getHousekeepingCheckPeriod()
+ {
+ return _config.getLong("housekeeping.checkPeriod",
+ _config.getLong("housekeeping.expiredMessageCheckPeriod",
+ DEFAULT_HOUSEKEEPING_PERIOD));
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java
new file mode 100644
index 0000000000..0273a13262
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java
@@ -0,0 +1,169 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.configuration;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.store.MemoryMessageStore;
+
+public class VirtualHostConfiguration
+{
+ private Configuration _config;
+ private String _name;
+ private Map<String, QueueConfiguration> _queues = new HashMap<String, QueueConfiguration>();
+ private Map<String, ExchangeConfiguration> _exchanges = new HashMap<String, ExchangeConfiguration>();
+
+ public VirtualHostConfiguration(String name, Configuration config) throws ConfigurationException
+ {
+ _config = config;
+ _name = name;
+ Iterator i = _config.getList("queues.queue.name").iterator();
+
+ while (i.hasNext())
+ {
+ String queueName = (String) i.next();
+ CompositeConfiguration mungedConf = new CompositeConfiguration();
+ mungedConf.addConfiguration(_config.subset("queues.queue." + queueName));
+ mungedConf.addConfiguration(_config.subset("queues"));
+ _queues.put(queueName, new QueueConfiguration(queueName, mungedConf, this));
+ }
+
+ i = _config.getList("exchanges.exchange.name").iterator();
+ int count = 0;
+ while (i.hasNext())
+ {
+ CompositeConfiguration mungedConf = new CompositeConfiguration();
+ mungedConf.addConfiguration(config.subset("exchanges.exchange(" + count++ + ")"));
+ mungedConf.addConfiguration(_config.subset("exchanges"));
+ String exchName = (String) i.next();
+ _exchanges.put(exchName, new ExchangeConfiguration(exchName, mungedConf));
+ }
+
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public long getHousekeepingExpiredMessageCheckPeriod()
+ {
+ return _config.getLong("housekeeping.expiredMessageCheckPeriod", ApplicationRegistry.getInstance().getConfiguration().getHousekeepingCheckPeriod());
+ }
+
+ public String getAuthenticationDatabase()
+ {
+ return _config.getString("security.authentication.name");
+ }
+
+ public List getCustomExchanges()
+ {
+ return _config.getList("custom-exchanges.class-name");
+ }
+
+ public SecurityConfiguration getSecurityConfiguration()
+ {
+ return new SecurityConfiguration(_config.subset("security"));
+ }
+
+ public Configuration getStoreConfiguration()
+ {
+ return _config.subset("store");
+ }
+
+ public String getMessageStoreClass()
+ {
+ return _config.getString("store.class", MemoryMessageStore.class.getName());
+ }
+
+ public List getExchanges()
+ {
+ return _config.getList("exchanges.exchange.name");
+ }
+
+ public String[] getQueueNames()
+ {
+ return _queues.keySet().toArray(new String[_queues.size()]);
+ }
+
+ public ExchangeConfiguration getExchangeConfiguration(String exchangeName)
+ {
+ return _exchanges.get(exchangeName);
+ }
+
+ public QueueConfiguration getQueueConfiguration(String queueName)
+ {
+ // We might be asked for the config for a queue we don't know about,
+ // such as one that's been dynamically created. Those get the defaults by default.
+ if (_queues.containsKey(queueName))
+ {
+ return _queues.get(queueName);
+ }
+ else
+ {
+ return new QueueConfiguration(queueName, new PropertiesConfiguration(), this);
+ }
+ }
+
+ public long getMemoryUsageMaximum()
+ {
+ return _config.getLong("queues.maximumMemoryUsage", 0);
+ }
+
+ public long getMemoryUsageMinimum()
+ {
+ return _config.getLong("queues.minimumMemoryUsage", 0);
+ }
+
+ public int getMaximumMessageAge()
+ {
+ return _config.getInt("queues.maximumMessageAge", 0);
+ }
+
+ public Long getMaximumQueueDepth()
+ {
+ return _config.getLong("queues.maximumQueueDepth", 0);
+ }
+
+ public Long getMaximumMessageSize()
+ {
+ return _config.getLong("queues.maximumMessageSize", 0);
+ }
+
+ public Long getMaximumMessageCount()
+ {
+ return _config.getLong("queues.maximumMessageCount", 0);
+ }
+
+ public Long getMinimumAlertRepeatGap()
+ {
+ return _config.getLong("queues.minimumAlertRepeatGap", 0);
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagement.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagement.java
new file mode 100644
index 0000000000..8e4bf01c6a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagement.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.configuration.management;
+
+import javax.management.MBeanOperationInfo;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.management.MBeanOperation;
+
+public interface ConfigurationManagement
+{
+
+ String TYPE = "ConfigurationManagement";
+ int VERSION = 1;
+
+ /**
+ * Reload the
+ * @throws ConfigurationException
+ */
+ @MBeanOperation(name="reloadSecurityConfiguration",
+ description = "Force a reload of the security configuration sections",
+ impact = MBeanOperationInfo.ACTION)
+ void reloadSecurityConfiguration() throws ConfigurationException;
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagementMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagementMBean.java
new file mode 100644
index 0000000000..ead6053d70
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagementMBean.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.configuration.management;
+
+import javax.management.NotCompliantMBeanException;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.management.AMQManagedObject;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+public class ConfigurationManagementMBean extends AMQManagedObject implements ConfigurationManagement
+{
+
+ public ConfigurationManagementMBean() throws NotCompliantMBeanException
+ {
+ super(ConfigurationManagement.class, ConfigurationManagement.TYPE, ConfigurationManagement.VERSION);
+ }
+
+ @Override
+ public String getObjectInstanceName()
+ {
+ return ConfigurationManagement.TYPE;
+ }
+
+ @Override
+ public void reloadSecurityConfiguration() throws ConfigurationException
+ {
+ ApplicationRegistry.getInstance().getConfiguration().reparseConfigFile();
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java
new file mode 100644
index 0000000000..d287595e2d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java
@@ -0,0 +1,73 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.connection;
+
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.protocol.AMQMinaProtocolSession;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.qpid.protocol.AMQConstant;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.List;
+
+public class ConnectionRegistry implements IConnectionRegistry
+{
+ private List<AMQProtocolSession> _registry = new CopyOnWriteArrayList<AMQProtocolSession>();
+
+ private VirtualHost _virtualHost;
+
+ public ConnectionRegistry(VirtualHost virtualHost)
+ {
+ _virtualHost = virtualHost;
+ }
+
+ public void initialise()
+ {
+
+ }
+
+ /** Close all of the currently open connections. */
+ public void close() throws AMQException
+ {
+ while (!_registry.isEmpty())
+ {
+ AMQProtocolSession connection = _registry.get(0);
+
+ connection.closeConnection(0, new AMQConnectionException(AMQConstant.INTERNAL_ERROR, "Broker is shutting down",
+ 0, 0,
+ connection.getProtocolOutputConverter().getProtocolMajorVersion(),
+ connection.getProtocolOutputConverter().getProtocolMinorVersion(),
+ (Throwable) null), true);
+ }
+ }
+
+ public void registerConnection(AMQProtocolSession connnection)
+ {
+ _registry.add(connnection);
+ }
+
+ public void deregisterConnection(AMQProtocolSession connnection)
+ {
+ _registry.remove(connnection);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java
new file mode 100644
index 0000000000..d64fde1c20
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.connection;
+
+import org.apache.qpid.server.protocol.AMQMinaProtocolSession;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.AMQException;
+
+public interface IConnectionRegistry
+{
+
+ public void initialise();
+
+ public void close() throws AMQException;
+
+ public void registerConnection(AMQProtocolSession connnection);
+
+ public void deregisterConnection(AMQProtocolSession connnection);
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java
new file mode 100644
index 0000000000..30af09ce4b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java
@@ -0,0 +1,211 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectName;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.TabularType;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.ArrayType;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.management.AMQManagedObject;
+import org.apache.qpid.server.management.Managable;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.management.ManagedObjectRegistry;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public abstract class AbstractExchange implements Exchange, Managable
+{
+ private AMQShortString _name;
+
+
+
+ protected boolean _durable;
+ protected String _exchangeType;
+ protected int _ticket;
+
+ private VirtualHost _virtualHost;
+
+ 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
+ {
+ // open mbean data types for representing exchange bindings
+ protected String[] _bindingItemNames;
+ protected String[] _bindingItemIndexNames;
+ protected OpenType[] _bindingItemTypes;
+ protected CompositeType _bindingDataType;
+ protected TabularType _bindinglistDataType;
+ protected TabularDataSupport _bindingList;
+
+ public ExchangeMBean() throws NotCompliantMBeanException
+ {
+ super(ManagedExchange.class, ManagedExchange.TYPE, ManagedExchange.VERSION);
+ }
+
+ protected void init() throws OpenDataException
+ {
+ _bindingItemNames = new String[]{"Binding Key", "Queue Names"};
+ _bindingItemIndexNames = new String[]{_bindingItemNames[0]};
+
+ _bindingItemTypes = new OpenType[2];
+ _bindingItemTypes[0] = SimpleType.STRING;
+ _bindingItemTypes[1] = new ArrayType(1, SimpleType.STRING);
+ _bindingDataType = new CompositeType("Exchange Binding", "Binding key and Queue names",
+ _bindingItemNames, _bindingItemNames, _bindingItemTypes);
+ _bindinglistDataType = new TabularType("Exchange Bindings", "Exchange Bindings for " + getName(),
+ _bindingDataType, _bindingItemIndexNames);
+ }
+
+ public ManagedObject getParentObject()
+ {
+ return _virtualHost.getManagedObject();
+ }
+
+ public String getObjectInstanceName()
+ {
+ return _name.toString();
+ }
+
+ public String getName()
+ {
+ return _name.toString();
+ }
+
+ public String getExchangeType()
+ {
+ return _exchangeType;
+ }
+
+ public Integer getTicketNo()
+ {
+ return _ticket;
+ }
+
+ public boolean isDurable()
+ {
+ return _durable;
+ }
+
+ public boolean isAutoDelete()
+ {
+ return _autoDelete;
+ }
+
+ // Added exchangetype in the object name lets maangement apps to do any customization required
+ public ObjectName getObjectName() throws MalformedObjectNameException
+ {
+ String objNameString = super.getObjectName().toString();
+ objNameString = objNameString + ",ExchangeType=" + _exchangeType;
+ return new ObjectName(objNameString);
+ }
+
+ protected ManagedObjectRegistry getManagedObjectRegistry()
+ {
+ return ApplicationRegistry.getInstance().getManagedObjectRegistry();
+ }
+ } // End of MBean class
+
+ public AMQShortString 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(VirtualHost host, AMQShortString name, boolean durable, int ticket, boolean autoDelete) throws AMQException
+ {
+ _virtualHost = host;
+ _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().getSimpleName() + "[" + getName() +"]";
+ }
+
+ public ManagedObject getManagedObject()
+ {
+ return _exchangeMbean;
+ }
+
+ public VirtualHost getVirtualHost()
+ {
+ return _virtualHost;
+ }
+
+ public QueueRegistry getQueueRegistry()
+ {
+ return getVirtualHost().getQueueRegistry();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java
new file mode 100644
index 0000000000..c04b6c559c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java
@@ -0,0 +1,114 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQUnknownExchangeType;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class DefaultExchangeFactory implements ExchangeFactory
+{
+ private static final Logger _logger = Logger.getLogger(DefaultExchangeFactory.class);
+
+ private Map<AMQShortString, ExchangeType<? extends Exchange>> _exchangeClassMap = new HashMap<AMQShortString, ExchangeType<? extends Exchange>>();
+ private final VirtualHost _host;
+
+ public DefaultExchangeFactory(VirtualHost host)
+ {
+ _host = host;
+ registerExchangeType(DirectExchange.TYPE);
+ registerExchangeType(TopicExchange.TYPE);
+ registerExchangeType(HeadersExchange.TYPE);
+ registerExchangeType(FanoutExchange.TYPE);
+ }
+
+ public void registerExchangeType(ExchangeType<? extends Exchange> type)
+ {
+ _exchangeClassMap.put(type.getName(), type);
+ }
+
+ public Collection<ExchangeType<? extends Exchange>> getRegisteredTypes()
+ {
+ return _exchangeClassMap.values();
+ }
+
+ public Exchange createExchange(AMQShortString exchange, AMQShortString type, boolean durable, boolean autoDelete,
+ int ticket)
+ throws AMQException
+ {
+ ExchangeType<? extends Exchange> exchType = _exchangeClassMap.get(type);
+ if (exchType == null)
+ {
+
+ throw new AMQUnknownExchangeType("Unknown exchange type: " + type,null);
+ }
+ Exchange e = exchType.newInstance(_host, exchange, durable, ticket, autoDelete);
+ return e;
+ }
+
+ @Override
+ public void initialise(VirtualHostConfiguration hostConfig)
+ {
+
+ if (hostConfig == null)
+ {
+ return;
+ }
+
+ for(Object className : hostConfig.getCustomExchanges())
+ {
+ try
+ {
+ ExchangeType<?> exchangeType = ApplicationRegistry.getInstance().getPluginManager().getExchanges().get(String.valueOf(className));
+ if (exchangeType == null)
+ {
+ _logger.error("No such custom exchange class found: \""+String.valueOf(className)+"\"");
+ return;
+ }
+ Class<? extends ExchangeType> exchangeTypeClass = exchangeType.getClass();
+ ExchangeType type = exchangeTypeClass.newInstance();
+ registerExchangeType(type);
+ }
+ catch (ClassCastException classCastEx)
+ {
+ _logger.error("No custom exchange class: \""+String.valueOf(className)+"\" cannot be registered as it does not extend class \""+ExchangeType.class+"\"");
+ }
+ catch (IllegalAccessException e)
+ {
+ _logger.error("Cannot create custom exchange class: \""+String.valueOf(className)+"\"",e);
+ }
+ catch (InstantiationException e)
+ {
+ _logger.error("Cannot create custom exchange class: \""+String.valueOf(className)+"\"",e);
+ }
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java
new file mode 100644
index 0000000000..0ab8208d88
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java
@@ -0,0 +1,139 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.protocol.ExchangeInitialiser;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.IncomingMessage;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import java.util.Collection;
+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<AMQShortString, Exchange> _exchangeMap = new ConcurrentHashMap<AMQShortString, Exchange>();
+
+ private Exchange _defaultExchange;
+ private VirtualHost _host;
+
+ public DefaultExchangeRegistry(VirtualHost host)
+ {
+ //create 'standard' exchanges:
+ _host = host;
+
+ }
+
+ public void initialise() throws AMQException
+ {
+ new ExchangeInitialiser().initialise(_host.getExchangeFactory(), this);
+ }
+
+ public MessageStore getMessageStore()
+ {
+ return _host.getMessageStore();
+ }
+
+ public void registerExchange(Exchange exchange) throws AMQException
+ {
+ _exchangeMap.put(exchange.getName(), exchange);
+ if (exchange.isDurable())
+ {
+ getMessageStore().createExchange(exchange);
+ }
+ }
+
+ public void setDefaultExchange(Exchange exchange)
+ {
+ _defaultExchange = exchange;
+ }
+
+ public Exchange getDefaultExchange()
+ {
+ return _defaultExchange;
+ }
+
+ public Collection<AMQShortString> getExchangeNames()
+ {
+ return _exchangeMap.keySet();
+ }
+
+ public void unregisterExchange(AMQShortString name, boolean inUse) throws AMQException
+ {
+ // TODO: check inUse argument
+ Exchange e = _exchangeMap.remove(name);
+ if (e != null)
+ {
+ if (e.isDurable())
+ {
+ getMessageStore().removeExchange(e);
+ }
+ e.close();
+ }
+ else
+ {
+ throw new AMQException("Unknown exchange " + name);
+ }
+ }
+
+ public Exchange getExchange(AMQShortString name)
+ {
+ if ((name == null) || name.length() == 0)
+ {
+ return getDefaultExchange();
+ }
+ else
+ {
+ 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(IncomingMessage payload) throws AMQException
+ {
+ final AMQShortString exchange = payload.getExchange();
+ final Exchange exch = getExchange(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/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java
new file mode 100644
index 0000000000..e9af92bad8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java
@@ -0,0 +1,251 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.management.JMException;
+import javax.management.MBeanException;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.queue.IncomingMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class DirectExchange extends AbstractExchange
+{
+ private static final Logger _logger = Logger.getLogger(DirectExchange.class);
+
+ /**
+ * Maps from queue name to queue instances
+ */
+ private final Index _index = new Index();
+
+ public static final ExchangeType<DirectExchange> TYPE = new ExchangeType<DirectExchange>()
+ {
+
+ public AMQShortString getName()
+ {
+ return ExchangeDefaults.DIRECT_EXCHANGE_CLASS;
+ }
+
+ public Class<DirectExchange> getExchangeClass()
+ {
+ return DirectExchange.class;
+ }
+
+ public DirectExchange newInstance(VirtualHost host,
+ AMQShortString name,
+ boolean durable,
+ int ticket,
+ boolean autoDelete) throws AMQException
+ {
+ DirectExchange exch = new DirectExchange();
+ exch.initialise(host,name,durable,ticket,autoDelete);
+ return exch;
+ }
+
+ public AMQShortString getDefaultExchangeName()
+ {
+ return ExchangeDefaults.DIRECT_EXCHANGE_NAME;
+ }
+ };
+
+ /**
+ * MBean class implementing the management interfaces.
+ */
+ @MBeanDescription("Management Bean for Direct Exchange")
+ private final class DirectExchangeMBean extends ExchangeMBean
+ {
+ @MBeanConstructor("Creates an MBean for AMQ direct exchange")
+ public DirectExchangeMBean() throws JMException
+ {
+ super();
+ _exchangeType = "direct";
+ init();
+ }
+
+ public TabularData bindings() throws OpenDataException
+ {
+ Map<AMQShortString, List<AMQQueue>> bindings = _index.getBindingsMap();
+ _bindingList = new TabularDataSupport(_bindinglistDataType);
+
+ for (Map.Entry<AMQShortString, List<AMQQueue>> entry : bindings.entrySet())
+ {
+ AMQShortString key = entry.getKey();
+ List<String> queueList = new ArrayList<String>();
+
+ List<AMQQueue> queues = entry.getValue();
+ for (AMQQueue q : queues)
+ {
+ queueList.add(q.getName().toString());
+ }
+
+ Object[] bindingItemValues = {key.toString(), queueList.toArray(new String[0])};
+ CompositeData bindingData = new CompositeDataSupport(_bindingDataType, _bindingItemNames, bindingItemValues);
+ _bindingList.put(bindingData);
+ }
+
+ return _bindingList;
+ }
+
+ public void createNewBinding(String queueName, String binding) throws JMException
+ {
+ AMQQueue queue = getQueueRegistry().getQueue(new AMQShortString(queueName));
+ if (queue == null)
+ {
+ throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange.");
+ }
+
+ try
+ {
+ queue.bind(DirectExchange.this, new AMQShortString(binding), null);
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex);
+ }
+ }
+
+ }// End of MBean class
+
+
+ protected ExchangeMBean createMBean() throws AMQException
+ {
+ try
+ {
+ return new DirectExchangeMBean();
+ }
+ catch (JMException ex)
+ {
+ _logger.error("Exception occured in creating the direct exchange mbean", ex);
+ throw new AMQException("Exception occured in creating the direct exchange mbean", ex);
+ }
+ }
+
+ public AMQShortString getType()
+ {
+ return ExchangeDefaults.DIRECT_EXCHANGE_CLASS;
+ }
+
+ public void registerQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ assert queue != null;
+ assert routingKey != null;
+ if (!_index.add(routingKey, queue))
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Queue (" + queue + ") is already registered with routing key " + routingKey);
+ }
+ }
+ else
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Binding queue:" + queue + " with routing key '" + routingKey +"' to exchange:" + this);
+ }
+ }
+ }
+
+ public void deregisterQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ assert queue != null;
+ assert routingKey != null;
+
+ if (!_index.remove(routingKey, queue))
+ {
+ throw new AMQException(AMQConstant.NOT_FOUND, "Queue " + queue + " was not registered with exchange " + this.getName() +
+ " with routing key " + routingKey + ". No queue was registered with that _routing key");
+ }
+ }
+
+ public void route(IncomingMessage payload) throws AMQException
+ {
+
+ final AMQShortString routingKey = payload.getRoutingKey() == null ? AMQShortString.EMPTY_STRING : payload.getRoutingKey();
+
+ final ArrayList<AMQQueue> queues = (routingKey == null) ? null : _index.get(routingKey);
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Publishing message to queue " + queues);
+ }
+
+ payload.enqueue(queues);
+
+
+ }
+
+ public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue)
+ {
+ return isBound(routingKey,queue);
+ }
+
+ public boolean isBound(AMQShortString routingKey, AMQQueue queue)
+ {
+ final List<AMQQueue> queues = _index.get(routingKey);
+ return queues != null && queues.contains(queue);
+ }
+
+ public boolean isBound(AMQShortString routingKey)
+ {
+ final List<AMQQueue> queues = _index.get(routingKey);
+ return queues != null && !queues.isEmpty();
+ }
+
+ public boolean isBound(AMQQueue queue)
+ {
+ Map<AMQShortString, List<AMQQueue>> bindings = _index.getBindingsMap();
+ for (List<AMQQueue> queues : bindings.values())
+ {
+ if (queues.contains(queue))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasBindings()
+ {
+ return !_index.getBindingsMap().isEmpty();
+ }
+
+ public Map<AMQShortString, List<AMQQueue>> getBindings()
+ {
+ return _index.getBindingsMap();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java
new file mode 100644
index 0000000000..06209c5458
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java
@@ -0,0 +1,98 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+
+import org.apache.qpid.server.queue.IncomingMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import java.util.List;
+import java.util.Map;
+
+public interface Exchange
+{
+ AMQShortString getName();
+
+ AMQShortString getType();
+
+ void initialise(VirtualHost host, AMQShortString 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(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException;
+
+ void deregisterQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException;
+
+ void route(IncomingMessage message) throws AMQException;
+
+
+ /**
+ * Determines whether a message would be isBound to a particular queue using a specific routing key and arguments
+ * @param routingKey
+ * @param arguments
+ * @param queue
+ * @return
+ * @throws AMQException
+ */
+ boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue);
+
+ /**
+ * Determines whether a message would be isBound to a particular queue using a specific routing key
+ * @param routingKey
+ * @param queue
+ * @return
+ * @throws AMQException
+ */
+ boolean isBound(AMQShortString routingKey, AMQQueue queue);
+
+ /**
+ * Determines whether a message is routing to any queue using a specific _routing key
+ * @param routingKey
+ * @return
+ * @throws AMQException
+ */
+ boolean isBound(AMQShortString routingKey);
+
+ boolean isBound(AMQQueue queue);
+
+ /**
+ * Returns true if this exchange has at least one binding associated with it.
+ * @return
+ * @throws AMQException
+ */
+ boolean hasBindings();
+
+
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java
new file mode 100644
index 0000000000..2f76d41228
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java
@@ -0,0 +1,41 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import java.util.Collection;
+
+import org.apache.commons.configuration.Configuration;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+
+
+public interface ExchangeFactory
+{
+ Exchange createExchange(AMQShortString exchange, AMQShortString type, boolean durable, boolean autoDelete,
+ int ticket)
+ throws AMQException;
+
+ void initialise(VirtualHostConfiguration hostConfig);
+
+ Collection<ExchangeType<? extends Exchange>> getRegisteredTypes();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java
new file mode 100644
index 0000000000..c77f114428
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.qpid.AMQException;
+
+/**
+ * ExchangeInUseRegistry indicates that an exchange cannot be unregistered because it is currently being used.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represents failure to unregister exchange that is in use.
+ * </table>
+ *
+ * @todo Not an AMQP exception as no status code.
+ *
+ * @todo This exception is not used. However, it is part of the ExchangeRegistry interface, and looks like code is
+ * going to need to be added to throw/deal with this. Alternatively ExchangeResitries may be able to handle the
+ * issue internally.
+ */
+public class ExchangeInUseException extends AMQException
+{
+ public ExchangeInUseException(String exchangeName)
+ {
+ super("Exchange " + exchangeName + " is currently in use");
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java
new file mode 100644
index 0000000000..fe3b19e74e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java
@@ -0,0 +1,51 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+
+import java.util.Collection;
+
+
+public interface ExchangeRegistry extends MessageRouter
+{
+ void registerExchange(Exchange exchange) throws AMQException;
+
+ /**
+ * 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(AMQShortString name, boolean inUse) throws ExchangeInUseException, AMQException;
+
+ Exchange getExchange(AMQShortString name);
+
+ void setDefaultExchange(Exchange exchange);
+
+ Exchange getDefaultExchange();
+
+ Collection<AMQShortString> getExchangeNames();
+
+ void initialise() throws AMQException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeType.java
new file mode 100644
index 0000000000..0b55caa2f1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeType.java
@@ -0,0 +1,35 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+
+public interface ExchangeType<T extends Exchange>
+{
+ public AMQShortString getName();
+ public Class<T> getExchangeClass();
+ public T newInstance(VirtualHost host, AMQShortString name,
+ boolean durable, int ticket, boolean autoDelete) throws AMQException;
+ public AMQShortString getDefaultExchangeName();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java
new file mode 100644
index 0000000000..e9fd4d548b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java
@@ -0,0 +1,224 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.queue.IncomingMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import javax.management.JMException;
+import javax.management.MBeanException;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import java.util.List;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+public class FanoutExchange extends AbstractExchange
+{
+ private static final Logger _logger = Logger.getLogger(FanoutExchange.class);
+
+ /**
+ * Maps from queue name to queue instances
+ */
+ private final CopyOnWriteArraySet<AMQQueue> _queues = new CopyOnWriteArraySet<AMQQueue>();
+
+ /**
+ * MBean class implementing the management interfaces.
+ */
+ @MBeanDescription("Management Bean for Fanout Exchange")
+ private final class FanoutExchangeMBean extends ExchangeMBean
+ {
+ @MBeanConstructor("Creates an MBean for AMQ fanout exchange")
+ public FanoutExchangeMBean() throws JMException
+ {
+ super();
+ _exchangeType = "fanout";
+ init();
+ }
+
+ public TabularData bindings() throws OpenDataException
+ {
+
+ _bindingList = new TabularDataSupport(_bindinglistDataType);
+
+ for (AMQQueue queue : _queues)
+ {
+ String queueName = queue.getName().toString();
+
+ Object[] bindingItemValues = {queueName, new String[]{queueName}};
+ CompositeData bindingData = new CompositeDataSupport(_bindingDataType, _bindingItemNames, bindingItemValues);
+ _bindingList.put(bindingData);
+ }
+
+ return _bindingList;
+ }
+
+ public void createNewBinding(String queueName, String binding) throws JMException
+ {
+ AMQQueue queue = getQueueRegistry().getQueue(new AMQShortString(queueName));
+ if (queue == null)
+ {
+ throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange.");
+ }
+
+ try
+ {
+ queue.bind(FanoutExchange.this, new AMQShortString(binding), null);
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex);
+ }
+ }
+
+ } // End of MBean class
+
+ protected ExchangeMBean createMBean() throws AMQException
+ {
+ try
+ {
+ return new FanoutExchange.FanoutExchangeMBean();
+ }
+ catch (JMException ex)
+ {
+ _logger.error("Exception occured in creating the direct exchange mbean", ex);
+ throw new AMQException("Exception occured in creating the direct exchange mbean", ex);
+ }
+ }
+
+ public static final ExchangeType<FanoutExchange> TYPE = new ExchangeType<FanoutExchange>()
+ {
+
+ public AMQShortString getName()
+ {
+ return ExchangeDefaults.FANOUT_EXCHANGE_CLASS;
+ }
+
+ public Class<FanoutExchange> getExchangeClass()
+ {
+ return FanoutExchange.class;
+ }
+
+ public FanoutExchange newInstance(VirtualHost host,
+ AMQShortString name,
+ boolean durable,
+ int ticket,
+ boolean autoDelete) throws AMQException
+ {
+ FanoutExchange exch = new FanoutExchange();
+ exch.initialise(host, name, durable, ticket, autoDelete);
+ return exch;
+ }
+
+ public AMQShortString getDefaultExchangeName()
+ {
+ return ExchangeDefaults.FANOUT_EXCHANGE_NAME;
+ }
+ };
+
+ public Map<AMQShortString, List<AMQQueue>> getBindings()
+ {
+ return null;
+ }
+
+ public AMQShortString getType()
+ {
+ return ExchangeDefaults.FANOUT_EXCHANGE_CLASS;
+ }
+
+ public void registerQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ assert queue != null;
+
+ if (_queues.contains(queue))
+ {
+ _logger.debug("Queue " + queue + " is already registered");
+ }
+ else
+ {
+ _queues.add(queue);
+ _logger.debug("Binding queue " + queue + " with routing key " + routingKey + " to exchange " + this);
+ }
+ }
+
+ public void deregisterQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ assert queue != null;
+
+ if (!_queues.remove(queue))
+ {
+ throw new AMQException(AMQConstant.NOT_FOUND, "Queue " + queue + " was not registered with exchange " + this.getName() + ". ");
+ }
+ }
+
+ public void route(IncomingMessage payload) throws AMQException
+ {
+
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Publishing message to queue " + _queues);
+ }
+
+ payload.enqueue(new ArrayList(_queues));
+
+ }
+
+ public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue)
+ {
+ return isBound(routingKey, queue);
+ }
+
+ public boolean isBound(AMQShortString routingKey, AMQQueue queue)
+ {
+ return _queues.contains(queue);
+ }
+
+ public boolean isBound(AMQShortString routingKey)
+ {
+
+ return (_queues != null) && !_queues.isEmpty();
+ }
+
+ public boolean isBound(AMQQueue queue)
+ {
+
+ return _queues.contains(queue);
+ }
+
+ public boolean hasBindings()
+ {
+ return !_queues.isEmpty();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java
new file mode 100644
index 0000000000..2b7df4361a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java
@@ -0,0 +1,219 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.framing.AMQTypedValue;
+import org.apache.qpid.framing.FieldTable;
+
+/**
+ * Defines binding and matching based on a set of headers.
+ */
+class HeadersBinding
+{
+ private static final Logger _logger = Logger.getLogger(HeadersBinding.class);
+
+ private final FieldTable _mappings;
+ private final Set<String> required = new HashSet<String>();
+ private final Map<String,Object> matches = new HashMap<String,Object>();
+ private boolean matchAny;
+
+ private final class MatchesOrProcessor implements FieldTable.FieldTableElementProcessor
+ {
+ private Boolean _result = Boolean.FALSE;
+
+ public boolean processElement(String propertyName, AMQTypedValue value)
+ {
+ if((value != null) && (value.getValue() != null) && value.getValue().equals(matches.get(propertyName)))
+ {
+ _result = Boolean.TRUE;
+ return false;
+ }
+ return true;
+ }
+
+ public Object getResult()
+ {
+ return _result;
+ }
+ }
+
+ private final class RequiredOrProcessor implements FieldTable.FieldTableElementProcessor
+ {
+ Boolean _result = Boolean.FALSE;
+
+ public boolean processElement(String propertyName, AMQTypedValue value)
+ {
+ if(required.contains(propertyName))
+ {
+ _result = Boolean.TRUE;
+ return false;
+ }
+ return true;
+ }
+
+ public Object getResult()
+ {
+ return _result;
+ }
+ }
+
+
+
+ /**
+ * 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(FieldTable mappings)
+ {
+ _mappings = mappings;
+ initMappings();
+ }
+
+ private void initMappings()
+ {
+
+ _mappings.processOverElements(new FieldTable.FieldTableElementProcessor()
+ {
+
+ public boolean processElement(String propertyName, AMQTypedValue value)
+ {
+ if (isSpecial(propertyName))
+ {
+ processSpecial(propertyName, value.getValue());
+ }
+ else if (value.getValue() == null || value.getValue().equals(""))
+ {
+ required.add(propertyName);
+ }
+ else
+ {
+ matches.put(propertyName,value.getValue());
+ }
+
+ return true;
+ }
+
+ public Object getResult()
+ {
+ return null;
+ }
+ });
+ }
+
+ protected FieldTable 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(FieldTable headers)
+ {
+ if(headers == null)
+ {
+ return required.isEmpty() && matches.isEmpty();
+ }
+ else
+ {
+ return matchAny ? or(headers) : and(headers);
+ }
+ }
+
+ private boolean and(FieldTable headers)
+ {
+ if(headers.keys().containsAll(required))
+ {
+ for(Map.Entry<String, Object> e : matches.entrySet())
+ {
+ if(!e.getValue().equals(headers.getObject(e.getKey())))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ private boolean or(final FieldTable headers)
+ {
+ if(required.isEmpty() || !(Boolean) headers.processOverElements(new RequiredOrProcessor()))
+ {
+ return ((!matches.isEmpty()) && (Boolean) headers.processOverElements(new MatchesOrProcessor()))
+ || (required.isEmpty() && matches.isEmpty());
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ 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/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java
new file mode 100644
index 0000000000..1ee1f35de6
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java
@@ -0,0 +1,350 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.AMQTypedValue;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.queue.IncomingMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import javax.management.JMException;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * An exchange that binds queues based on a set of required headers and header values
+ * and routes messages to these queues by matching the headers of the message against
+ * those with which the queues were bound.
+ * <p/>
+ * <pre>
+ * The Headers Exchange
+ *
+ * Routes messages according to the value/presence of fields in the message header table.
+ * (Basic and JMS content has a content header field called "headers" that is a table of
+ * message header fields).
+ *
+ * class = "headers"
+ * routing key is not used
+ *
+ * Has the following binding arguments:
+ *
+ * the X-match field - if "all", does an AND match (used for GRM), if "any", does an OR match.
+ * other fields prefixed with "X-" are ignored (and generate a console warning message).
+ * a field with no value or empty value indicates a match on presence only.
+ * a field with a value indicates match on field presence and specific value.
+ *
+ * Standard instances:
+ *
+ * amq.match - pub/sub on field content/value
+ * </pre>
+ */
+public class HeadersExchange extends AbstractExchange
+{
+ private static final Logger _logger = Logger.getLogger(HeadersExchange.class);
+
+
+
+ public static final ExchangeType<HeadersExchange> TYPE = new ExchangeType<HeadersExchange>()
+ {
+
+ public AMQShortString getName()
+ {
+ return ExchangeDefaults.HEADERS_EXCHANGE_CLASS;
+ }
+
+ public Class<HeadersExchange> getExchangeClass()
+ {
+ return HeadersExchange.class;
+ }
+
+ public HeadersExchange newInstance(VirtualHost host, AMQShortString name, boolean durable, int ticket,
+ boolean autoDelete) throws AMQException
+ {
+ HeadersExchange exch = new HeadersExchange();
+ exch.initialise(host, name, durable, ticket, autoDelete);
+ return exch;
+ }
+
+ public AMQShortString getDefaultExchangeName()
+ {
+
+ return ExchangeDefaults.HEADERS_EXCHANGE_NAME;
+ }
+ };
+
+
+ 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
+ {
+ @MBeanConstructor("Creates an MBean for AMQ Headers exchange")
+ public HeadersExchangeMBean() throws JMException
+ {
+ super();
+ _exchangeType = "headers";
+ init();
+ }
+
+ /**
+ * initialises the OpenType objects.
+ */
+ protected void init() throws OpenDataException
+ {
+ _bindingItemNames = new String[]{"Binding No", "Queue Name", "Queue Bindings"};
+ _bindingItemIndexNames = new String[]{_bindingItemNames[0]};
+
+ _bindingItemTypes = new OpenType[3];
+ _bindingItemTypes[0] = SimpleType.INTEGER;
+ _bindingItemTypes[1] = SimpleType.STRING;
+ _bindingItemTypes[2] = new ArrayType(1, SimpleType.STRING);
+ _bindingDataType = new CompositeType("Exchange Binding", "Queue name and header bindings",
+ _bindingItemNames, _bindingItemNames, _bindingItemTypes);
+ _bindinglistDataType = new TabularType("Exchange Bindings", "List of exchange bindings for " + getName(),
+ _bindingDataType, _bindingItemIndexNames);
+ }
+
+ public TabularData bindings() throws OpenDataException
+ {
+ _bindingList = new TabularDataSupport(_bindinglistDataType);
+ int count = 1;
+ for (Iterator<Registration> itr = _bindings.iterator(); itr.hasNext();)
+ {
+ Registration registration = itr.next();
+ String queueName = registration.queue.getName().toString();
+
+ HeadersBinding headers = registration.binding;
+ FieldTable headerMappings = headers.getMappings();
+ final List<String> mappingList = new ArrayList<String>();
+
+ headerMappings.processOverElements(new FieldTable.FieldTableElementProcessor()
+ {
+
+ public boolean processElement(String propertyName, AMQTypedValue value)
+ {
+ mappingList.add(propertyName + "=" + value.getValue());
+ return true;
+ }
+
+ public Object getResult()
+ {
+ return mappingList;
+ }
+ });
+
+
+ Object[] bindingItemValues = {count++, queueName, mappingList.toArray(new String[0])};
+ CompositeData bindingData = new CompositeDataSupport(_bindingDataType, _bindingItemNames, bindingItemValues);
+ _bindingList.put(bindingData);
+ }
+
+ return _bindingList;
+ }
+
+ /**
+ * Creates bindings. Binding pattern is as follows-
+ * <attributename>=<value>,<attributename>=<value>,...
+ * @param queueName
+ * @param binding
+ * @throws javax.management.JMException
+ */
+ public void createNewBinding(String queueName, String binding) throws JMException
+ {
+ AMQQueue queue = getQueueRegistry().getQueue(new AMQShortString(queueName));
+
+ if (queue == null)
+ {
+ throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange.");
+ }
+
+ String[] bindings = binding.split(",");
+ FieldTable bindingMap = new FieldTable();
+ for (int i = 0; i < bindings.length; i++)
+ {
+ String[] keyAndValue = bindings[i].split("=");
+ if (keyAndValue == null || keyAndValue.length < 2)
+ {
+ throw new JMException("Format for headers binding should be \"<attribute1>=<value1>,<attribute2>=<value2>\" ");
+ }
+ bindingMap.setString(keyAndValue[0], keyAndValue[1]);
+ }
+
+ _bindings.add(new Registration(new HeadersBinding(bindingMap), queue));
+ }
+
+ } // End of MBean class
+
+ public AMQShortString getType()
+ {
+ return ExchangeDefaults.HEADERS_EXCHANGE_CLASS;
+ }
+
+ public void registerQueue(AMQShortString 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(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ _logger.debug("Exchange " + getName() + ": Unbinding " + queue.getName());
+ if(!_bindings.remove(new Registration(new HeadersBinding(args), queue)))
+ {
+ throw new AMQException(AMQConstant.NOT_FOUND, "Queue " + queue + " was not registered with exchange " + this.getName()
+ + " with headers args " + args);
+ }
+ }
+
+ public void route(IncomingMessage payload) throws AMQException
+ {
+ FieldTable headers = getHeaders(payload.getContentHeaderBody());
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Exchange " + getName() + ": routing message with headers " + headers);
+ }
+ boolean routed = false;
+ ArrayList<AMQQueue> queues = new ArrayList<AMQQueue>();
+ 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());
+ }
+ queues.add(e.queue);
+
+ routed = true;
+ }
+ }
+ payload.enqueue(queues);
+ }
+
+ public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue)
+ {
+ //fixme isBound here should take the arguements in to consideration.
+ return isBound(routingKey, queue);
+ }
+
+ public boolean isBound(AMQShortString routingKey, AMQQueue queue)
+ {
+ return isBound(queue);
+ }
+
+ public boolean isBound(AMQShortString routingKey)
+ {
+ return hasBindings();
+ }
+
+ public boolean isBound(AMQQueue queue)
+ {
+ for (Registration r : _bindings)
+ {
+ if (r.queue.equals(queue))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasBindings()
+ {
+ return !_bindings.isEmpty();
+ }
+
+ protected FieldTable 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 (JMException ex)
+ {
+ _logger.error("Exception occured in creating the HeadersExchangeMBean", ex);
+ throw new AMQException("Exception occured in creating the HeadersExchangeMBean", ex);
+ }
+ }
+
+ public Map<AMQShortString, List<AMQQueue>> getBindings()
+ {
+ return null;
+ }
+
+ 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/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java
new file mode 100644
index 0000000000..ec83161029
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java
@@ -0,0 +1,99 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.queue.AMQQueue;
+
+/**
+ * An index of queues against routing key. Allows multiple queues to be stored
+ * against the same key. Used in the DirectExchange.
+ */
+class Index
+{
+ private ConcurrentMap<AMQShortString, ArrayList<AMQQueue>> _index
+ = new ConcurrentHashMap<AMQShortString, ArrayList<AMQQueue>>();
+
+ synchronized boolean add(AMQShortString key, AMQQueue queue)
+ {
+ ArrayList<AMQQueue> queues = _index.get(key);
+ if(queues == null)
+ {
+ queues = new ArrayList<AMQQueue>();
+ }
+ else
+ {
+ queues = new ArrayList<AMQQueue>(queues);
+ }
+ //next call is atomic, so there is no race to create the list
+ _index.put(key, queues);
+
+ if(queues.contains(queue))
+ {
+ return false;
+ }
+ else
+ {
+ return queues.add(queue);
+ }
+ }
+
+ synchronized boolean remove(AMQShortString key, AMQQueue queue)
+ {
+ ArrayList<AMQQueue> queues = _index.get(key);
+ if (queues != null)
+ {
+ queues = new ArrayList<AMQQueue>(queues);
+ boolean removed = queues.remove(queue);
+ if(removed)
+ {
+ if (queues.size() == 0)
+ {
+ _index.remove(key);
+ }
+ else
+ {
+ _index.put(key, queues);
+ }
+ }
+ return removed;
+ }
+ return false;
+ }
+
+ ArrayList<AMQQueue> get(AMQShortString key)
+ {
+ return _index.get(key);
+ }
+
+ Map<AMQShortString, List<AMQQueue>> getBindingsMap()
+ {
+ return new HashMap<AMQShortString, List<AMQQueue>>(_index);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java
new file mode 100644
index 0000000000..317ff385ab
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java
@@ -0,0 +1,99 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import java.io.IOException;
+
+import javax.management.JMException;
+import javax.management.MBeanOperationInfo;
+import javax.management.openmbean.TabularData;
+
+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.server.queue.ManagedQueue;
+
+/**
+ * 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";
+ static final int VERSION = 1;
+
+ /**
+ * Returns the name of the managed exchange.
+ * @return the name of the exchange.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="Name", description=TYPE + " Name")
+ String getName() throws IOException;
+
+ @MBeanAttribute(name="ExchangeType", description="Exchange Type")
+ String getExchangeType() 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="bindings", description="view the queue bindings for this exchange")
+ TabularData bindings() throws IOException, JMException;
+
+ /**
+ * Creates new binding with the given queue and binding.
+ * @param queueName
+ * @param binding
+ * @throws JMException
+ */
+ @MBeanOperation(name="createNewBinding",
+ description="create a new binding with this exchange",
+ impact= MBeanOperationInfo.ACTION)
+ void createNewBinding(@MBeanOperationParameter(name= ManagedQueue.TYPE, description="Queue name") String queueName,
+ @MBeanOperationParameter(name="Binding", description="New binding")String binding)
+ throws JMException;
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java
new file mode 100644
index 0000000000..db9beb6da7
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java
@@ -0,0 +1,41 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.IncomingMessage;
+
+/**
+ * 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(IncomingMessage message) throws AMQException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java
new file mode 100644
index 0000000000..d18ad7ab14
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.IncomingMessage;
+
+/**
+ * NoRouteException is a {@link RequiredDeliveryException} that represents the failure case where a manadatory message
+ * cannot be delivered because there is no route for the message. The AMQP status code, 312, is always used to report
+ * this condition.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represent failure to deliver a message that must be delivered.
+ * </table>
+ */
+public class NoRouteException extends RequiredDeliveryException
+{
+ public NoRouteException(String msg, AMQMessage amqMessage)
+ {
+ super(msg, amqMessage);
+ }
+
+ public AMQConstant getReplyCode()
+ {
+ return AMQConstant.NO_ROUTE;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java
new file mode 100644
index 0000000000..bc303a219d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java
@@ -0,0 +1,670 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.common.AMQPFilterTypes;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.AMQShortStringTokenizer;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.queue.IncomingMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.exchange.topic.TopicParser;
+import org.apache.qpid.server.exchange.topic.TopicMatcherResult;
+import org.apache.qpid.server.filter.MessageFilter;
+import org.apache.qpid.server.filter.JMSSelectorFilter;
+
+import javax.management.JMException;
+import javax.management.MBeanException;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.lang.ref.WeakReference;
+
+public class TopicExchange extends AbstractExchange
+{
+
+ public static final ExchangeType<TopicExchange> TYPE = new ExchangeType<TopicExchange>()
+ {
+
+ public AMQShortString getName()
+ {
+ return ExchangeDefaults.TOPIC_EXCHANGE_CLASS;
+ }
+
+ public Class<TopicExchange> getExchangeClass()
+ {
+ return TopicExchange.class;
+ }
+
+ public TopicExchange newInstance(VirtualHost host,
+ AMQShortString name,
+ boolean durable,
+ int ticket,
+ boolean autoDelete) throws AMQException
+ {
+ TopicExchange exch = new TopicExchange();
+ exch.initialise(host, name, durable, ticket, autoDelete);
+ return exch;
+ }
+
+ public AMQShortString getDefaultExchangeName()
+ {
+ return ExchangeDefaults.TOPIC_EXCHANGE_NAME;
+ }
+ };
+
+
+ private static final Logger _logger = Logger.getLogger(TopicExchange.class);
+
+/*
+ private final ConcurrentHashMap<AMQShortString, List<AMQQueue>> _bindingKey2queues =
+ new ConcurrentHashMap<AMQShortString, List<AMQQueue>>();
+ private final ConcurrentHashMap<AMQShortString, List<AMQQueue>> _simpleBindingKey2queues =
+ new ConcurrentHashMap<AMQShortString, List<AMQQueue>>();
+ private final ConcurrentHashMap<AMQShortString, List<AMQQueue>> _wildCardBindingKey2queues =
+ new ConcurrentHashMap<AMQShortString, List<AMQQueue>>();
+*/
+ // private ConcurrentHashMap<AMQShortString, AMQQueue> _routingKey2queue = new ConcurrentHashMap<AMQShortString, AMQQueue>();
+ private static final byte TOPIC_SEPARATOR = (byte)'.';
+ private static final AMQShortString TOPIC_SEPARATOR_AS_SHORTSTRING = new AMQShortString(".");
+ private static final AMQShortString AMQP_STAR_TOKEN = new AMQShortString("*");
+ private static final AMQShortString AMQP_HASH_TOKEN = new AMQShortString("#");
+
+ private static final byte HASH_BYTE = (byte)'#';
+ private static final byte STAR_BYTE = (byte)'*';
+
+ private final TopicParser _parser = new TopicParser();
+
+ private final Map<AMQShortString, TopicExchangeResult> _topicExchangeResults =
+ new ConcurrentHashMap<AMQShortString, TopicExchangeResult>();
+
+ private final Map<Binding, FieldTable> _bindings = new HashMap<Binding, FieldTable>();
+
+ private final Map<String, WeakReference<JMSSelectorFilter<RuntimeException>>> _selectorCache = new WeakHashMap<String, WeakReference<JMSSelectorFilter<RuntimeException>>>();
+
+ public static class Binding
+ {
+ private final AMQShortString _bindingKey;
+ private final AMQQueue _queue;
+ private final FieldTable _args;
+
+ public Binding(AMQShortString bindingKey, AMQQueue queue, FieldTable args)
+ {
+ _bindingKey = bindingKey;
+ _queue = queue;
+ _args = args;
+ }
+
+ public AMQShortString getBindingKey()
+ {
+ return _bindingKey;
+ }
+
+ public AMQQueue getQueue()
+ {
+ return _queue;
+ }
+
+ public int hashCode()
+ {
+ return (_bindingKey == null ? 1 : _bindingKey.hashCode())*31 +_queue.hashCode();
+ }
+
+ public boolean equals(Object o)
+ {
+ if(this == o)
+ {
+ return true;
+ }
+ if(o instanceof Binding)
+ {
+ Binding other = (Binding) o;
+ return (_queue == other._queue)
+ && ((_bindingKey == null) ? other._bindingKey == null : _bindingKey.equals(other._bindingKey));
+ }
+ return false;
+ }
+ }
+
+
+
+ private final class TopicExchangeResult implements TopicMatcherResult
+ {
+ private final Map<AMQQueue, Integer> _unfilteredQueues = new ConcurrentHashMap<AMQQueue, Integer>();
+ private final ConcurrentHashMap<AMQQueue, Map<MessageFilter<RuntimeException>,Integer>> _filteredQueues = new ConcurrentHashMap<AMQQueue, Map<MessageFilter<RuntimeException>, Integer>>();
+
+ public void addUnfilteredQueue(AMQQueue queue)
+ {
+ Integer instances = _unfilteredQueues.get(queue);
+ if(instances == null)
+ {
+ _unfilteredQueues.put(queue, 1);
+ }
+ else
+ {
+ _unfilteredQueues.put(queue, instances + 1);
+ }
+ }
+
+ public void removeUnfilteredQueue(AMQQueue queue)
+ {
+ Integer instances = _unfilteredQueues.get(queue);
+ if(instances == 1)
+ {
+ _unfilteredQueues.remove(queue);
+ }
+ else
+ {
+ _unfilteredQueues.put(queue,instances - 1);
+ }
+
+ }
+
+
+ public void addFilteredQueue(AMQQueue queue, MessageFilter<RuntimeException> filter)
+ {
+ Map<MessageFilter<RuntimeException>,Integer> filters = _filteredQueues.get(queue);
+ if(filters == null)
+ {
+ filters = new ConcurrentHashMap<MessageFilter<RuntimeException>,Integer>();
+ _filteredQueues.put(queue, filters);
+ }
+ Integer instances = filters.get(filter);
+ if(instances == null)
+ {
+ filters.put(filter,1);
+ }
+ else
+ {
+ filters.put(filter, instances + 1);
+ }
+
+ }
+
+ public void removeFilteredQueue(AMQQueue queue, MessageFilter<RuntimeException> filter)
+ {
+ Map<MessageFilter<RuntimeException>,Integer> filters = _filteredQueues.get(queue);
+ if(filters != null)
+ {
+ Integer instances = filters.get(filter);
+ if(instances != null)
+ {
+ if(instances == 1)
+ {
+ filters.remove(filter);
+ if(filters.isEmpty())
+ {
+ _filteredQueues.remove(queue);
+ }
+ }
+ else
+ {
+ filters.put(filter, instances - 1);
+ }
+ }
+
+ }
+
+ }
+
+ public void replaceQueueFilter(AMQQueue queue,
+ MessageFilter<RuntimeException> oldFilter,
+ MessageFilter<RuntimeException> newFilter)
+ {
+ Map<MessageFilter<RuntimeException>,Integer> filters = _filteredQueues.get(queue);
+ Map<MessageFilter<RuntimeException>,Integer> newFilters = new ConcurrentHashMap<MessageFilter<RuntimeException>,Integer>(filters);
+ Integer oldFilterInstances = filters.get(oldFilter);
+ if(oldFilterInstances == 1)
+ {
+ newFilters.remove(oldFilter);
+ }
+ else
+ {
+ newFilters.put(oldFilter, oldFilterInstances-1);
+ }
+ Integer newFilterInstances = filters.get(newFilter);
+ if(newFilterInstances == null)
+ {
+ newFilters.put(newFilter, 1);
+ }
+ else
+ {
+ newFilters.put(newFilter, newFilterInstances+1);
+ }
+ _filteredQueues.put(queue,newFilters);
+ }
+
+ public Collection<AMQQueue> processMessage(IncomingMessage msg, Collection<AMQQueue> queues)
+ {
+ if(queues == null)
+ {
+ if(_filteredQueues.isEmpty())
+ {
+ return new ArrayList<AMQQueue>(_unfilteredQueues.keySet());
+ }
+ else
+ {
+ queues = new HashSet<AMQQueue>();
+ }
+ }
+ else if(!(queues instanceof Set))
+ {
+ queues = new HashSet<AMQQueue>(queues);
+ }
+
+ queues.addAll(_unfilteredQueues.keySet());
+ if(!_filteredQueues.isEmpty())
+ {
+ for(Map.Entry<AMQQueue, Map<MessageFilter<RuntimeException>, Integer>> entry : _filteredQueues.entrySet())
+ {
+ if(!queues.contains(entry.getKey()))
+ {
+ for(MessageFilter<RuntimeException> filter : entry.getValue().keySet())
+ {
+ if(filter.matches(msg))
+ {
+ queues.add(entry.getKey());
+ }
+ }
+ }
+ }
+ }
+ return queues;
+ }
+
+ }
+
+
+ /** TopicExchangeMBean class implements the management interface for the Topic exchanges. */
+ @MBeanDescription("Management Bean for Topic Exchange")
+ private final class TopicExchangeMBean extends ExchangeMBean
+ {
+ @MBeanConstructor("Creates an MBean for AMQ topic exchange")
+ public TopicExchangeMBean() throws JMException
+ {
+ super();
+ _exchangeType = "topic";
+ init();
+ }
+
+ /** returns exchange bindings in tabular form */
+ public TabularData bindings() throws OpenDataException
+ {
+ _bindingList = new TabularDataSupport(_bindinglistDataType);
+ Map<String, List<String>> bindingData = new HashMap<String, List<String>>();
+ for (Binding binding : _bindings.keySet())
+ {
+ String key = binding.getBindingKey().toString();
+ List<String> queueNames = bindingData.get(key);
+ if(queueNames == null)
+ {
+ queueNames = new ArrayList<String>();
+ bindingData.put(key, queueNames);
+ }
+ queueNames.add(binding.getQueue().getName().toString());
+
+ }
+ for(Map.Entry<String, List<String>> entry : bindingData.entrySet())
+ {
+ Object[] bindingItemValues = {entry.getKey(), entry.getValue().toArray(new String[entry.getValue().size()]) };
+ CompositeData bindingCompositeData = new CompositeDataSupport(_bindingDataType, _bindingItemNames, bindingItemValues);
+ _bindingList.put(bindingCompositeData);
+ }
+
+ return _bindingList;
+ }
+
+ public void createNewBinding(String queueName, String binding) throws JMException
+ {
+ AMQQueue queue = getQueueRegistry().getQueue(new AMQShortString(queueName));
+ if (queue == null)
+ {
+ throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange.");
+ }
+
+ try
+ {
+ queue.bind(TopicExchange.this, new AMQShortString(binding), null);
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex);
+ }
+ }
+
+ } // End of MBean class
+
+ public AMQShortString getType()
+ {
+ return ExchangeDefaults.TOPIC_EXCHANGE_CLASS;
+ }
+
+ public synchronized void registerQueue(AMQShortString rKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ assert queue != null;
+ assert rKey != null;
+
+ _logger.debug("Registering queue " + queue.getName() + " with routing key " + rKey);
+
+
+ AMQShortString routingKey;
+
+ if(rKey.contains(HASH_BYTE) || rKey.contains(STAR_BYTE))
+ {
+ routingKey = normalize(rKey);
+ }
+ else
+ {
+ routingKey = rKey;
+ }
+
+ Binding binding = new Binding(rKey, queue, args);
+
+ if(_bindings.containsKey(binding))
+ {
+ FieldTable oldArgs = _bindings.get(binding);
+ TopicExchangeResult result = _topicExchangeResults.get(routingKey);
+
+ if(argumentsContainSelector(args))
+ {
+ if(argumentsContainSelector(oldArgs))
+ {
+ result.replaceQueueFilter(queue,createSelectorFilter(oldArgs), createSelectorFilter(args));
+ }
+ else
+ {
+ result.addFilteredQueue(queue,createSelectorFilter(args));
+ result.removeUnfilteredQueue(queue);
+ }
+ }
+ else
+ {
+ if(argumentsContainSelector(oldArgs))
+ {
+ result.addUnfilteredQueue(queue);
+ result.removeFilteredQueue(queue, createSelectorFilter(oldArgs));
+ }
+ else
+ {
+ // TODO - fix control flow
+ return;
+ }
+ }
+
+ }
+ else
+ {
+
+ TopicExchangeResult result = _topicExchangeResults.get(routingKey);
+ if(result == null)
+ {
+ result = new TopicExchangeResult();
+ if(argumentsContainSelector(args))
+ {
+ result.addFilteredQueue(queue, createSelectorFilter(args));
+ }
+ else
+ {
+ result.addUnfilteredQueue(queue);
+ }
+ _parser.addBinding(routingKey, result);
+ _topicExchangeResults.put(routingKey,result);
+ }
+ else
+ {
+ if(argumentsContainSelector(args))
+ {
+ result.addFilteredQueue(queue, createSelectorFilter(args));
+ }
+ else
+ {
+ result.addUnfilteredQueue(queue);
+ }
+ }
+ _bindings.put(binding, args);
+ }
+
+
+ }
+
+ private JMSSelectorFilter<RuntimeException> createSelectorFilter(final FieldTable args)
+ throws AMQException
+ {
+
+ final String selectorString = args.getString(AMQPFilterTypes.JMS_SELECTOR.getValue());
+ WeakReference<JMSSelectorFilter<RuntimeException>> selectorRef = _selectorCache.get(selectorString);
+ JMSSelectorFilter selector = null;
+
+ if(selectorRef == null || (selector = selectorRef.get())==null)
+ {
+ selector = new JMSSelectorFilter<RuntimeException>(selectorString);
+ _selectorCache.put(selectorString, new WeakReference<JMSSelectorFilter<RuntimeException>>(selector));
+ }
+ return selector;
+ }
+
+ private static boolean argumentsContainSelector(final FieldTable args)
+ {
+ return args != null && args.containsKey(AMQPFilterTypes.JMS_SELECTOR.getValue()) && args.getString(AMQPFilterTypes.JMS_SELECTOR.getValue()).trim().length() != 0;
+ }
+
+ private AMQShortString normalize(AMQShortString routingKey)
+ {
+ if(routingKey == null)
+ {
+ routingKey = AMQShortString.EMPTY_STRING;
+ }
+
+ AMQShortStringTokenizer routingTokens = routingKey.tokenize(TOPIC_SEPARATOR);
+
+ List<AMQShortString> subscriptionList = new ArrayList<AMQShortString>();
+
+ while (routingTokens.hasMoreTokens())
+ {
+ subscriptionList.add(routingTokens.nextToken());
+ }
+
+ int size = subscriptionList.size();
+
+ for (int index = 0; index < size; index++)
+ {
+ // if there are more levels
+ if ((index + 1) < size)
+ {
+ if (subscriptionList.get(index).equals(AMQP_HASH_TOKEN))
+ {
+ if (subscriptionList.get(index + 1).equals(AMQP_HASH_TOKEN))
+ {
+ // we don't need #.# delete this one
+ subscriptionList.remove(index);
+ size--;
+ // redo this normalisation
+ index--;
+ }
+
+ if (subscriptionList.get(index + 1).equals(AMQP_STAR_TOKEN))
+ {
+ // we don't want #.* swap to *.#
+ // remove it and put it in at index + 1
+ subscriptionList.add(index + 1, subscriptionList.remove(index));
+ }
+ }
+ } // if we have more levels
+ }
+
+
+
+ AMQShortString normalizedString = AMQShortString.join(subscriptionList, TOPIC_SEPARATOR_AS_SHORTSTRING);
+
+ return normalizedString;
+ }
+
+ public void route(IncomingMessage payload) throws AMQException
+ {
+
+ final AMQShortString routingKey = payload.getRoutingKey();
+
+ // The copy here is unfortunate, but not too bad relevant to the amount of
+ // things created and copied in getMatchedQueues
+ ArrayList<AMQQueue> queues = new ArrayList<AMQQueue>();
+ queues.addAll(getMatchedQueues(payload, routingKey));
+
+ if(queues == null || queues.isEmpty())
+ {
+ _logger.info("Message routing key: " + payload.getRoutingKey() + " No routes.");
+ }
+
+ payload.enqueue(queues);
+
+ }
+
+ public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue)
+ {
+ Binding binding = new Binding(routingKey, queue, arguments);
+ if (arguments == null)
+ {
+ return _bindings.containsKey(binding);
+ }
+ else
+ {
+ FieldTable o = _bindings.get(binding);
+ if (o != null)
+ {
+ return o.equals(arguments);
+ }
+ else
+ {
+ return false;
+ }
+
+ }
+ }
+
+ public boolean isBound(AMQShortString routingKey, AMQQueue queue)
+ {
+ return isBound(routingKey, null, queue);
+ }
+
+ public boolean isBound(AMQShortString routingKey)
+ {
+ for(Binding b : _bindings.keySet())
+ {
+ if(b.getBindingKey().equals(routingKey))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean isBound(AMQQueue queue)
+ {
+ for(Binding b : _bindings.keySet())
+ {
+ if(b.getQueue().equals(queue))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean hasBindings()
+ {
+ return !_bindings.isEmpty();
+ }
+
+ public synchronized void deregisterQueue(AMQShortString rKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ assert queue != null;
+ assert rKey != null;
+
+ Binding binding = new Binding(rKey, queue, args);
+
+
+ if (!_bindings.containsKey(binding))
+ {
+ throw new AMQException(AMQConstant.NOT_FOUND, "Queue " + queue.getName() + " was not registered with exchange " + this.getName()
+ + " with routing key " + rKey + ".");
+ }
+
+ FieldTable bindingArgs = _bindings.remove(binding);
+ AMQShortString bindingKey = normalize(rKey);
+ TopicExchangeResult result = _topicExchangeResults.get(bindingKey);
+ if(argumentsContainSelector(bindingArgs))
+ {
+ result.removeFilteredQueue(queue, createSelectorFilter(bindingArgs));
+ }
+ else
+ {
+ result.removeUnfilteredQueue(queue);
+ }
+
+ }
+
+ protected ExchangeMBean createMBean() throws AMQException
+ {
+ try
+ {
+ return new TopicExchangeMBean();
+ }
+ catch (JMException ex)
+ {
+ _logger.error("Exception occured in creating the topic exchenge mbean", ex);
+ throw new AMQException("Exception occured in creating the topic exchenge mbean", ex);
+ }
+ }
+
+ private Collection<AMQQueue> getMatchedQueues(IncomingMessage message, AMQShortString routingKey)
+ {
+
+ Collection<TopicMatcherResult> results = _parser.parse(routingKey);
+ if(results.isEmpty())
+ {
+ return Collections.EMPTY_SET;
+ }
+ else
+ {
+ Collection<AMQQueue> queues = results.size() == 1 ? null : new HashSet<AMQQueue>();
+ for(TopicMatcherResult result : results)
+ {
+
+ queues = ((TopicExchangeResult)result).processMessage(message, queues);
+ }
+ return queues;
+ }
+
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKey.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKey.java
new file mode 100644
index 0000000000..8fdb91cbef
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKey.java
@@ -0,0 +1,40 @@
+package org.apache.qpid.server.exchange.headers;
+
+import org.apache.qpid.framing.AMQShortString;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class HeaderKey
+{
+ public static final HeaderKey UNKNOWN = new HeaderKey(new AMQShortString("<< UNKNOWN >>"));
+ private AMQShortString _key;
+
+ public HeaderKey(final AMQShortString key)
+ {
+ _key = key;
+ }
+
+ public String toString()
+ {
+ return _key.toString();
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKeyDictionary.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKeyDictionary.java
new file mode 100644
index 0000000000..7be99a88c9
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKeyDictionary.java
@@ -0,0 +1,50 @@
+package org.apache.qpid.server.exchange.headers;
+
+import org.apache.qpid.framing.AMQShortString;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class HeaderKeyDictionary
+{
+
+ private final Map<AMQShortString, HeaderKey> _dictionary = new HashMap<AMQShortString, HeaderKey>();
+
+
+ public HeaderKey get(final AMQShortString key)
+ {
+ HeaderKey headerKey = _dictionary.get(key);
+ return headerKey == null ? HeaderKey.UNKNOWN : headerKey;
+ }
+
+ public HeaderKey getOrCreate(final AMQShortString key)
+ {
+ HeaderKey headerKey = _dictionary.get(key);
+ if(headerKey == null)
+ {
+ headerKey = new HeaderKey(key);
+ _dictionary.put(key, headerKey);
+ }
+ return headerKey;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderMatcherResult.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderMatcherResult.java
new file mode 100644
index 0000000000..518064bb29
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderMatcherResult.java
@@ -0,0 +1,25 @@
+package org.apache.qpid.server.exchange.headers;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class HeaderMatcherResult
+{
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersMatcherDFAState.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersMatcherDFAState.java
new file mode 100644
index 0000000000..9da93d483a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersMatcherDFAState.java
@@ -0,0 +1,339 @@
+package org.apache.qpid.server.exchange.headers;
+
+import org.apache.qpid.framing.AMQTypedValue;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.exchange.topic.TopicMatcherDFAState;
+import org.apache.qpid.server.exchange.topic.TopicWord;
+import org.apache.qpid.server.exchange.topic.TopicMatcherResult;
+
+import java.util.*;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class HeadersMatcherDFAState
+{
+
+
+ private final Collection<HeaderMatcherResult> _results;
+ private final Map<HeaderKey, Map<AMQTypedValue,HeadersMatcherDFAState>> _nextStateMap;
+ private final HeaderKeyDictionary _dictionary;
+
+ public HeadersMatcherDFAState(Map<HeaderKey, Map<AMQTypedValue,HeadersMatcherDFAState>> nextStateMap,
+ Collection<HeaderMatcherResult> results,
+ HeaderKeyDictionary dictionary)
+ {
+ _nextStateMap = nextStateMap;
+ _results = results;
+ _dictionary = dictionary;
+ }
+
+
+ public Collection<HeaderMatcherResult> match(final FieldTable table)
+ {
+ return match(table.iterator());
+ }
+
+
+
+ public Collection<HeaderMatcherResult> match(Iterator<Map.Entry<AMQShortString,AMQTypedValue>> fieldTableIterator)
+ {
+
+ if(_nextStateMap.isEmpty())
+ {
+ return _results;
+ }
+
+ while(fieldTableIterator.hasNext())
+ {
+
+ Map.Entry<AMQShortString, AMQTypedValue> fieldTableEntry = fieldTableIterator.next();
+ HeaderKey key = _dictionary.get(fieldTableEntry.getKey());
+ if(key != HeaderKey.UNKNOWN)
+ {
+ Map<AMQTypedValue, HeadersMatcherDFAState> valueToStateMap = _nextStateMap.get(key);
+
+ if(valueToStateMap != null)
+ {
+ HeadersMatcherDFAState nextState = valueToStateMap.get(fieldTableEntry.getValue());
+
+ if(nextState == null)
+ {
+ nextState = valueToStateMap.get(null);
+ }
+ if(nextState != null && nextState != this)
+ {
+ return nextState.match(fieldTableIterator);
+ }
+ }
+
+ }
+ }
+
+ return _results;
+
+ }
+
+
+ HeadersMatcherDFAState mergeStateMachines(HeadersMatcherDFAState otherStateMachine)
+ {
+
+ assert(otherStateMachine._dictionary == _dictionary);
+
+ Map<Set<HeadersMatcherDFAState>, HeadersMatcherDFAState> newStateMap= new HashMap<Set<HeadersMatcherDFAState>, HeadersMatcherDFAState>();
+
+ Collection<HeaderMatcherResult> results;
+
+ if(_results.isEmpty())
+ {
+ results = otherStateMachine._results;
+ }
+ else if(otherStateMachine._results.isEmpty())
+ {
+ results = _results;
+ }
+ else
+ {
+ results = new HashSet<HeaderMatcherResult>(_results);
+ results.addAll(otherStateMachine._results);
+ }
+
+
+ final Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> newNextStateMap = new HashMap<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>>();
+
+ HeadersMatcherDFAState newState = new HeadersMatcherDFAState(newNextStateMap, results, _dictionary);
+
+
+ Set<HeadersMatcherDFAState> oldStates = new HashSet<HeadersMatcherDFAState>();
+ oldStates.add(this);
+ oldStates.add(otherStateMachine);
+
+ newStateMap.put(oldStates, newState);
+
+ mergeStateMachines(oldStates, newNextStateMap, newStateMap);
+
+ return newState;
+
+
+ }
+
+ private void mergeStateMachines(final Set<HeadersMatcherDFAState> oldStates,
+ final Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> newNextStateMap,
+ final Map<Set<HeadersMatcherDFAState>, HeadersMatcherDFAState> newStateMap)
+ {
+ Map<HeaderKey, Map<AMQTypedValue, Set<HeadersMatcherDFAState>>> nfaMap = new HashMap<HeaderKey, Map<AMQTypedValue, Set<HeadersMatcherDFAState>>>();
+
+ Set<HeaderKey> distinctKeys = new HashSet<HeaderKey>();
+
+ for(HeadersMatcherDFAState state : oldStates)
+ {
+ Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> map = state._nextStateMap;
+
+ for(Map.Entry<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> entry : map.entrySet())
+ {
+ Map<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStatesMap = nfaMap.get(entry.getKey());
+
+ if(valueToStatesMap == null)
+ {
+ valueToStatesMap = new HashMap<AMQTypedValue, Set<HeadersMatcherDFAState>>();
+ nfaMap.put(entry.getKey(), valueToStatesMap);
+ }
+
+ for(Map.Entry<AMQTypedValue, HeadersMatcherDFAState> valueToStateEntry : entry.getValue().entrySet())
+ {
+ Set<HeadersMatcherDFAState> states = valueToStatesMap.get(valueToStateEntry.getKey());
+ if(states == null)
+ {
+ states = new HashSet<HeadersMatcherDFAState>();
+ valueToStatesMap.put(valueToStateEntry.getKey(),states);
+ }
+ states.add(valueToStateEntry.getValue());
+ }
+
+ distinctKeys.add(entry.getKey());
+ }
+ }
+
+ Map<HeaderKey, Set<HeadersMatcherDFAState>> anyValueStates = new HashMap<HeaderKey, Set<HeadersMatcherDFAState>>();
+
+ for(HeaderKey distinctKey : distinctKeys)
+ {
+ Map<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStateMap = nfaMap.get(distinctKey);
+ if(valueToStateMap != null)
+ {
+ Set<HeadersMatcherDFAState> statesForKeyDefault = valueToStateMap.get(null);
+ if(statesForKeyDefault != null)
+ {
+ anyValueStates.put(distinctKey, statesForKeyDefault);
+ }
+ }
+ }
+
+ // add the defaults for "null" to all other specified values of a given header key
+
+ for( Map.Entry<HeaderKey,Map<AMQTypedValue,Set<HeadersMatcherDFAState>>> entry : nfaMap.entrySet())
+ {
+ Map<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStatesMap = entry.getValue();
+ for(Map.Entry<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStates : valueToStatesMap.entrySet())
+ {
+ if(valueToStates.getKey() != null)
+ {
+
+
+ Set<HeadersMatcherDFAState> defaults = anyValueStates.get(entry.getKey());
+ if(defaults != null)
+ {
+ valueToStates.getValue().addAll(defaults);
+ }
+ }
+ }
+ }
+
+ // if a given header key is not mentioned in the map of a machine; then that machine would stay at the same state
+ // for that key.
+ for(HeaderKey distinctKey : distinctKeys)
+ {
+ Map<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStatesMap = nfaMap.get(distinctKey);
+ for(HeadersMatcherDFAState oldState : oldStates)
+ {
+ if(!oldState._nextStateMap.containsKey(distinctKey))
+ {
+ for(Set<HeadersMatcherDFAState> endStates : valueToStatesMap.values())
+ {
+ endStates.add(oldState);
+ }
+ }
+ }
+ }
+
+
+
+
+ for(Map.Entry<HeaderKey,Map<AMQTypedValue,Set<HeadersMatcherDFAState>>> transitionClass : nfaMap.entrySet())
+ {
+ Map<AMQTypedValue, HeadersMatcherDFAState> valueToDFAState = newNextStateMap.get(transitionClass.getKey());
+ if(valueToDFAState == null)
+ {
+ valueToDFAState = new HashMap<AMQTypedValue, HeadersMatcherDFAState>();
+ newNextStateMap.put(transitionClass.getKey(), valueToDFAState);
+ }
+
+ for(Map.Entry<AMQTypedValue,Set<HeadersMatcherDFAState>> transition : transitionClass.getValue().entrySet())
+ {
+ Set<HeadersMatcherDFAState> destinations = transition.getValue();
+
+
+ HeadersMatcherDFAState nextState = newStateMap.get(destinations);
+
+ if(nextState == null)
+ {
+
+ if(destinations.size() == 1)
+ {
+ nextState = destinations.iterator().next();
+ newStateMap.put(destinations, nextState);
+ }
+ else
+ {
+ Collection<HeaderMatcherResult> results;
+
+ Set<Collection<HeaderMatcherResult>> resultSets = new HashSet<Collection<HeaderMatcherResult>>();
+ for(HeadersMatcherDFAState destination : destinations)
+ {
+ resultSets.add(destination._results);
+ }
+ resultSets.remove(Collections.EMPTY_SET);
+ if(resultSets.size() == 0)
+ {
+ results = Collections.EMPTY_SET;
+ }
+ else if(resultSets.size() == 1)
+ {
+ results = resultSets.iterator().next();
+ }
+ else
+ {
+ results = new HashSet<HeaderMatcherResult>();
+ for(Collection<HeaderMatcherResult> oldResult : resultSets)
+ {
+ results.addAll(oldResult);
+ }
+ }
+
+ final Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> nextStateMap = new HashMap<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>>();
+
+ nextState = new HeadersMatcherDFAState(nextStateMap, results, _dictionary);
+ newStateMap.put(destinations, nextState);
+
+ mergeStateMachines(
+ destinations,
+ nextStateMap,
+ newStateMap);
+
+
+ }
+
+
+ }
+ valueToDFAState.put(transition.getKey(),nextState);
+ }
+ }
+
+
+
+ final ArrayList<HeaderKey> removeKeyList = new ArrayList<HeaderKey>();
+
+ for(Map.Entry<HeaderKey,Map<AMQTypedValue,HeadersMatcherDFAState>> entry : _nextStateMap.entrySet())
+ {
+ final ArrayList<AMQTypedValue> removeValueList = new ArrayList<AMQTypedValue>();
+
+ for(Map.Entry<AMQTypedValue,HeadersMatcherDFAState> valueToDFAState : entry.getValue().entrySet())
+ {
+ if(valueToDFAState.getValue() == this)
+ {
+ HeadersMatcherDFAState defaultState = entry.getValue().get(null);
+ if(defaultState == null || defaultState == this)
+ {
+ removeValueList.add(valueToDFAState.getKey());
+ }
+ }
+ }
+
+ for(AMQTypedValue removeValue : removeValueList)
+ {
+ entry.getValue().remove(removeValue);
+ }
+
+ if(entry.getValue().isEmpty())
+ {
+ removeKeyList.add(entry.getKey());
+ }
+
+ }
+
+ for(HeaderKey removeKey : removeKeyList)
+ {
+ _nextStateMap.remove(removeKey);
+ }
+
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersParser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersParser.java
new file mode 100644
index 0000000000..85e74122c3
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersParser.java
@@ -0,0 +1,439 @@
+package org.apache.qpid.server.exchange.headers;
+
+import org.apache.qpid.framing.*;
+
+import java.util.*;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class HeadersParser
+{
+
+ private final HeaderKeyDictionary _dictionary = new HeaderKeyDictionary();
+ private static final AMQShortString MATCHING_TYPE_KEY = new AMQShortString("x-match");
+ private static final String ANY_MATCHING = "any";
+ private static final AMQShortString RESERVED_KEY_PREFIX = new AMQShortString("x-");
+
+
+ HeadersMatcherDFAState createStateMachine(FieldTable bindingArguments, HeaderMatcherResult result)
+ {
+ String matchingType = bindingArguments.getString(MATCHING_TYPE_KEY);
+ boolean matchAny = matchingType.equalsIgnoreCase(ANY_MATCHING);
+ if(matchAny)
+ {
+ return createStateMachineForAnyMatch(bindingArguments, result);
+ }
+ else
+ {
+ return createStateMachineForAllMatch(bindingArguments, result);
+ }
+
+
+ }
+
+
+ private HeadersMatcherDFAState createStateMachineForAnyMatch(final FieldTable bindingArguments,
+ final HeaderMatcherResult result)
+ {
+
+ // DFAs for "any" matches have only two states, "not-matched" and "matched"... they start in the former
+ // and upon meeting any of the criteria they move to the latter
+
+ //noinspection unchecked
+ final HeadersMatcherDFAState successState =
+ new HeadersMatcherDFAState(Collections.EMPTY_MAP,Collections.singleton(result),_dictionary);
+
+ Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> nextStateMap =
+ new HashMap<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>>();
+
+ Set<AMQShortString> seenKeys = new HashSet<AMQShortString>();
+
+ Iterator<Map.Entry<AMQShortString, AMQTypedValue>> tableIterator = bindingArguments.iterator();
+
+ while(tableIterator.hasNext())
+ {
+ final Map.Entry<AMQShortString, AMQTypedValue> entry = tableIterator.next();
+ final AMQShortString key = entry.getKey();
+ final AMQTypedValue value = entry.getValue();
+
+
+ if(seenKeys.add(key) && !key.startsWith(RESERVED_KEY_PREFIX))
+ {
+ final AMQType type = value.getType();
+
+ final HeaderKey headerKey = _dictionary.getOrCreate(key);
+ final Map<AMQTypedValue, HeadersMatcherDFAState> valueMap;
+
+ if(type == AMQType.VOID ||
+ ((type == AMQType.ASCII_STRING || type == AMQType.WIDE_STRING) && ((CharSequence)value.getValue()).length() == 0))
+ {
+ valueMap = Collections.singletonMap(null,successState);
+
+ }
+ else
+ {
+ valueMap = Collections.singletonMap(value,successState);
+ }
+ nextStateMap.put(headerKey,valueMap);
+
+ }
+
+ }
+
+ if(seenKeys.size() == 0)
+ {
+ return successState;
+ }
+ else
+ {
+ return new HeadersMatcherDFAState(nextStateMap,Collections.EMPTY_SET,_dictionary);
+ }
+
+
+ }
+
+
+ private HeadersMatcherDFAState createStateMachineForAllMatch(final FieldTable bindingArguments,
+ final HeaderMatcherResult result)
+ {
+ // DFAs for "all" matches have a "success" state, a "fail" state, and states for every subset of
+ // matches which are possible, starting with the empty subset. For example if we have a binding
+ // { x-match="all"
+ // a=1
+ // b=1
+ // c=1
+ // d=1 }
+ // Then we would have the following states
+ // (1) Seen none of a, b, c, or d
+ // (2) Seen a=1 ; none of b,c, or d
+ // (3) Seen b=1 ; none of a,c, or d
+ // (4) Seen c=1 ; none of a,b, or d
+ // (5) Seen d=1 ; none of a,b, or c
+ // (6) Seen a=1,b=1 ; none of c,d
+ // (7) Seen a=1,c=1 ; none of b,d
+ // (8) Seen a=1,d=1 ; none of b,c
+ // (9) Seen b=1,c=1 ; none of a,d
+ //(10) Seen b=1,d=1 ; none of c,d
+ //(11) Seen c=1,d=1 ; none of a,b
+ //(12) Seen a=1,b=1,c=1 ; not d
+ //(13) Seen a=1,b=1,d=1 ; not c
+ //(14) Seen a=1,c=1,d=1 ; not b
+ //(15) Seen b=1,c=1,d=1 ; not a
+ //(16) success
+ //(17) fail
+ //
+ // All states but (16) can transition to (17); additionally:
+ // (1) can transition to (2),(3),(4),(5)
+ // (2) can transition to (6),(7),(8)
+ // (3) can transition to (6),(9),(10)
+ // (4) can transition to (7),(9),(11)
+ // (5) can transition to (8),(10),(11)
+ // (6) can transition to (12),(13)
+ // (7) can transition to (12),(14)
+ // (8) can transition to (13),(14)
+ // (9) can transition to (12),(15)
+ //(10) can transition to (13),(15)
+ //(11) can transition to (14),(15)
+ //(12)-(15) can transition to (16)
+
+ Set<AMQShortString> seenKeys = new HashSet<AMQShortString>();
+ List<KeyValuePair> requiredTerms = new ArrayList<KeyValuePair>(bindingArguments.size());
+
+ Iterator<Map.Entry<AMQShortString, AMQTypedValue>> tableIterator = bindingArguments.iterator();
+
+
+
+ while(tableIterator.hasNext())
+ {
+ final Map.Entry<AMQShortString, AMQTypedValue> entry = tableIterator.next();
+ final AMQShortString key = entry.getKey();
+ final AMQTypedValue value = entry.getValue();
+
+
+ if(seenKeys.add(key) && !key.startsWith(RESERVED_KEY_PREFIX))
+ {
+ final AMQType type = value.getType();
+
+ if(type == AMQType.VOID ||
+ ((type == AMQType.ASCII_STRING || type == AMQType.WIDE_STRING) && ((CharSequence)value.getValue()).length() == 0))
+ {
+ requiredTerms.add(new KeyValuePair(_dictionary.getOrCreate(key),null));
+ }
+ else
+ {
+ requiredTerms.add(new KeyValuePair(_dictionary.getOrCreate(key),value));
+ }
+ }
+
+ }
+
+ final HeadersMatcherDFAState successState =
+ new HeadersMatcherDFAState(Collections.EMPTY_MAP,Collections.singleton(result),_dictionary);
+
+ final HeadersMatcherDFAState failState =
+ new HeadersMatcherDFAState(Collections.EMPTY_MAP,Collections.EMPTY_SET,_dictionary);
+
+ Map<Set<KeyValuePair>, HeadersMatcherDFAState> notSeenTermsToStateMap =
+ new HashMap<Set<KeyValuePair>, HeadersMatcherDFAState>();
+
+ notSeenTermsToStateMap.put(Collections.EMPTY_SET, successState);
+
+
+ final int numberOfTerms = requiredTerms.size();
+
+ for(int numMissingTerms = 1; numMissingTerms <= numberOfTerms; numMissingTerms++)
+ {
+ int[] pos = new int[numMissingTerms];
+ for(int i = 0; i < numMissingTerms; i++)
+ {
+ pos[i] = i;
+ }
+
+ final int maxTermValue = (numberOfTerms - (numMissingTerms - 1));
+
+ while(pos[0] < maxTermValue)
+ {
+
+ Set<KeyValuePair> stateSet = new HashSet<KeyValuePair>();
+ for(int posIndex = 0; posIndex < pos.length; posIndex++)
+ {
+ stateSet.add(requiredTerms.get(pos[posIndex]));
+ }
+
+ final Map<HeaderKey, Map<AMQTypedValue,HeadersMatcherDFAState>> nextStateMap =
+ new HashMap<HeaderKey, Map<AMQTypedValue,HeadersMatcherDFAState>>();
+
+
+ for(int posIndex = 0; posIndex < pos.length; posIndex++)
+ {
+ KeyValuePair nextTerm = requiredTerms.get(pos[posIndex]);
+ HashSet<KeyValuePair> nextStateSet =
+ new HashSet<KeyValuePair>(stateSet);
+ nextStateSet.remove(nextTerm);
+
+ Map<AMQTypedValue, HeadersMatcherDFAState> valueToStateMap =
+ new HashMap<AMQTypedValue, HeadersMatcherDFAState>();
+ nextStateMap.put(nextTerm._key, valueToStateMap);
+
+ valueToStateMap.put( nextTerm._value,notSeenTermsToStateMap.get(nextStateSet));
+ if(nextTerm._value != null)
+ {
+ valueToStateMap.put(null, failState);
+ }
+
+
+ }
+
+
+ HeadersMatcherDFAState newState = new HeadersMatcherDFAState(nextStateMap, Collections.EMPTY_SET, _dictionary);
+
+ notSeenTermsToStateMap.put(stateSet, newState);
+
+ int i = numMissingTerms;
+ while(i-- != 0)
+ {
+ if(++pos[i] <= numberOfTerms -(numMissingTerms-i))
+ {
+ int k = pos[i];
+ for(int j = i+1; j < numMissingTerms; j++)
+ {
+ pos[j] = ++k;
+ }
+ break;
+ }
+ }
+ }
+
+
+
+
+ }
+
+
+ return notSeenTermsToStateMap.get(new HashSet<KeyValuePair>(requiredTerms));
+
+
+
+ }
+
+ public static void main(String[] args) throws AMQFrameDecodingException
+ {
+
+ FieldTable bindingTable = new FieldTable();
+
+ bindingTable.setString(new AMQShortString("x-match"),"all");
+ bindingTable.setInteger("a",1);
+ bindingTable.setVoid(new AMQShortString("b"));
+ bindingTable.setString("c","");
+ bindingTable.setInteger("d",4);
+ bindingTable.setInteger("e",1);
+
+
+
+ FieldTable bindingTable2 = new FieldTable();
+ bindingTable2.setString(new AMQShortString("x-match"),"all");
+ bindingTable2.setInteger("a",1);
+ bindingTable2.setVoid(new AMQShortString("b"));
+ bindingTable2.setString("c","");
+ bindingTable2.setInteger("d",4);
+ bindingTable2.setInteger("e",1);
+ bindingTable2.setInteger("f",1);
+
+
+ FieldTable table = new FieldTable();
+ table.setInteger("a",1);
+ table.setInteger("b",2);
+ table.setString("c","");
+ table.setInteger("d",4);
+ table.setInteger("e",1);
+ table.setInteger("f",1);
+ table.setInteger("h",1);
+ table.setInteger("i",1);
+ table.setInteger("j",1);
+ table.setInteger("k",1);
+ table.setInteger("l",1);
+
+ org.apache.mina.common.ByteBuffer buffer = org.apache.mina.common.ByteBuffer.allocate( (int) table.getEncodedSize());
+ EncodingUtils.writeFieldTableBytes(buffer, table);
+ buffer.flip();
+
+ FieldTable table2 = EncodingUtils.readFieldTable(buffer);
+
+
+
+ FieldTable bindingTable3 = new FieldTable();
+ bindingTable3.setString(new AMQShortString("x-match"),"any");
+ bindingTable3.setInteger("a",1);
+ bindingTable3.setInteger("b",3);
+
+
+ FieldTable bindingTable4 = new FieldTable();
+ bindingTable4.setString(new AMQShortString("x-match"),"any");
+ bindingTable4.setVoid(new AMQShortString("a"));
+
+
+ FieldTable bindingTable5 = new FieldTable();
+ bindingTable5.setString(new AMQShortString("x-match"),"all");
+ bindingTable5.setString(new AMQShortString("h"),"hello");
+
+ for(int i = 0; i < 100; i++)
+ {
+ printMatches(new FieldTable[] {bindingTable5} , table2);
+ }
+
+
+
+ }
+
+
+
+ private static void printMatches(final FieldTable[] bindingKeys, final FieldTable routingKey)
+ {
+ HeadersMatcherDFAState sm = null;
+ Map<HeaderMatcherResult, String> resultMap = new HashMap<HeaderMatcherResult, String>();
+
+ HeadersParser parser = new HeadersParser();
+
+ for(int i = 0; i < bindingKeys.length; i++)
+ {
+ HeaderMatcherResult r = new HeaderMatcherResult();
+ resultMap.put(r, bindingKeys[i].toString());
+
+
+ if(i==0)
+ {
+ sm = parser.createStateMachine(bindingKeys[i], r);
+ }
+ else
+ {
+ sm = sm.mergeStateMachines(parser.createStateMachine(bindingKeys[i], r));
+ }
+ }
+
+ Collection<HeaderMatcherResult> results = null;
+ long beforeTime = System.currentTimeMillis();
+ for(int i = 0; i < 1000000; i++)
+ {
+ routingKey.size();
+
+ assert sm != null;
+ results = sm.match(routingKey);
+
+ }
+ long elapsed = System.currentTimeMillis() - beforeTime;
+ System.out.println("1000000 Iterations took: " + elapsed);
+ Collection<String> resultStrings = new ArrayList<String>();
+
+ assert results != null;
+ for(HeaderMatcherResult result : results)
+ {
+ resultStrings.add(resultMap.get(result));
+ }
+
+ final ArrayList<String> nonMatches = new ArrayList<String>();
+ for(FieldTable key : bindingKeys)
+ {
+ nonMatches.add(key.toString());
+ }
+ nonMatches.removeAll(resultStrings);
+ System.out.println("\""+routingKey+"\" matched with " + resultStrings + " DID NOT MATCH with " + nonMatches);
+
+
+ }
+
+
+ public final static class KeyValuePair
+ {
+ public final HeaderKey _key;
+ public final AMQTypedValue _value;
+ private final int _hashCode;
+
+ public KeyValuePair(final HeaderKey key, final AMQTypedValue value)
+ {
+ _key = key;
+ _value = value;
+ int hash = (1 + 31 * _key.hashCode());
+ if(_value != null)
+ {
+ hash+=_value.hashCode();
+ }
+ _hashCode = hash;
+ }
+
+ public int hashCode()
+ {
+ return _hashCode;
+ }
+
+ public boolean equals(Object o)
+ {
+ KeyValuePair other = (KeyValuePair)o;
+ return (_key == other._key) && (_value == null ? other._value == null : _value.equals(other._value));
+ }
+
+
+ public String toString()
+ {
+ return "{" + _key + " -> " + _value + "}";
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java
new file mode 100644
index 0000000000..36076cf75b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java
@@ -0,0 +1,295 @@
+package org.apache.qpid.server.exchange.topic;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.AMQShortStringTokenizer;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class TopicMatcherDFAState
+{
+ private static final AtomicInteger stateId = new AtomicInteger();
+
+ private final int _id = stateId.incrementAndGet();
+
+ private final Collection<TopicMatcherResult> _results;
+ private final Map<TopicWord, TopicMatcherDFAState> _nextStateMap;
+ private static final byte TOPIC_DELIMITTER = (byte)'.';
+
+
+ public TopicMatcherDFAState(Map<TopicWord, TopicMatcherDFAState> nextStateMap,
+ Collection<TopicMatcherResult> results )
+ {
+ _nextStateMap = nextStateMap;
+ _results = results;
+ }
+
+
+ public TopicMatcherDFAState nextState(TopicWord word)
+ {
+ final TopicMatcherDFAState nextState = _nextStateMap.get(word);
+ return nextState == null ? _nextStateMap.get(TopicWord.ANY_WORD) : nextState;
+ }
+
+ public Collection<TopicMatcherResult> terminate()
+ {
+ return _results;
+ }
+
+
+ public Collection<TopicMatcherResult> parse(TopicWordDictionary dictionary, AMQShortString routingKey)
+ {
+ return parse(dictionary, routingKey.tokenize(TOPIC_DELIMITTER));
+ }
+
+ private Collection<TopicMatcherResult> parse(final TopicWordDictionary dictionary,
+ final AMQShortStringTokenizer tokens)
+ {
+ if(!tokens.hasMoreTokens())
+ {
+ return _results;
+ }
+ TopicWord word = dictionary.getWord(tokens.nextToken());
+ TopicMatcherDFAState nextState = _nextStateMap.get(word);
+ if(nextState == null && word != TopicWord.ANY_WORD)
+ {
+ nextState = _nextStateMap.get(TopicWord.ANY_WORD);
+ }
+ if(nextState == null)
+ {
+ return Collections.EMPTY_SET;
+ }
+ // Shortcut if we are at a looping terminal state
+ if((nextState == this) && (_nextStateMap.size() == 1) && _nextStateMap.containsKey(TopicWord.ANY_WORD))
+ {
+ return _results;
+ }
+
+ return nextState.parse(dictionary, tokens);
+
+ }
+
+
+ public TopicMatcherDFAState mergeStateMachines(TopicMatcherDFAState otherStateMachine)
+ {
+ Map<Set<TopicMatcherDFAState>, TopicMatcherDFAState> newStateMap= new HashMap<Set<TopicMatcherDFAState>, TopicMatcherDFAState>();
+
+ Collection<TopicMatcherResult> results;
+
+ if(_results.isEmpty())
+ {
+ results = otherStateMachine._results;
+ }
+ else if(otherStateMachine._results.isEmpty())
+ {
+ results = _results;
+ }
+ else
+ {
+ results = new HashSet<TopicMatcherResult>(_results);
+ results.addAll(otherStateMachine._results);
+ }
+
+
+ final Map<TopicWord, TopicMatcherDFAState> newNextStateMap = new HashMap<TopicWord, TopicMatcherDFAState>();
+
+ TopicMatcherDFAState newState = new TopicMatcherDFAState(newNextStateMap, results);
+
+
+ Set<TopicMatcherDFAState> oldStates = new HashSet<TopicMatcherDFAState>();
+ oldStates.add(this);
+ oldStates.add(otherStateMachine);
+
+ newStateMap.put(oldStates, newState);
+
+ mergeStateMachines(oldStates, newNextStateMap, newStateMap);
+
+ return newState;
+
+ }
+
+ private static void mergeStateMachines(
+ final Set<TopicMatcherDFAState> oldStates,
+ final Map<TopicWord, TopicMatcherDFAState> newNextStateMap,
+ final Map<Set<TopicMatcherDFAState>, TopicMatcherDFAState> newStateMap)
+ {
+ Map<TopicWord, Set<TopicMatcherDFAState>> nfaMap = new HashMap<TopicWord, Set<TopicMatcherDFAState>>();
+
+ for(TopicMatcherDFAState state : oldStates)
+ {
+ Map<TopicWord, TopicMatcherDFAState> map = state._nextStateMap;
+ for(Map.Entry<TopicWord, TopicMatcherDFAState> entry : map.entrySet())
+ {
+ Set<TopicMatcherDFAState> states = nfaMap.get(entry.getKey());
+ if(states == null)
+ {
+ states = new HashSet<TopicMatcherDFAState>();
+ nfaMap.put(entry.getKey(), states);
+ }
+ states.add(entry.getValue());
+ }
+ }
+
+ Set<TopicMatcherDFAState> anyWordStates = nfaMap.get(TopicWord.ANY_WORD);
+
+ for(Map.Entry<TopicWord, Set<TopicMatcherDFAState>> transition : nfaMap.entrySet())
+ {
+ Set<TopicMatcherDFAState> destinations = transition.getValue();
+
+ if(anyWordStates != null)
+ {
+ destinations.addAll(anyWordStates);
+ }
+
+ TopicMatcherDFAState nextState = newStateMap.get(destinations);
+ if(nextState == null)
+ {
+
+ if(destinations.size() == 1)
+ {
+ nextState = destinations.iterator().next();
+ newStateMap.put(destinations, nextState);
+ }
+ else
+ {
+ Collection<TopicMatcherResult> results;
+
+ Set<Collection<TopicMatcherResult>> resultSets = new HashSet<Collection<TopicMatcherResult>>();
+ for(TopicMatcherDFAState destination : destinations)
+ {
+ resultSets.add(destination._results);
+ }
+ resultSets.remove(Collections.EMPTY_SET);
+ if(resultSets.size() == 0)
+ {
+ results = Collections.EMPTY_SET;
+ }
+ else if(resultSets.size() == 1)
+ {
+ results = resultSets.iterator().next();
+ }
+ else
+ {
+ results = new HashSet<TopicMatcherResult>();
+ for(Collection<TopicMatcherResult> oldResult : resultSets)
+ {
+ results.addAll(oldResult);
+ }
+ }
+
+ final Map<TopicWord, TopicMatcherDFAState> nextStateMap = new HashMap<TopicWord, TopicMatcherDFAState>();
+
+ nextState = new TopicMatcherDFAState(nextStateMap, results);
+ newStateMap.put(destinations, nextState);
+
+ mergeStateMachines(
+ destinations,
+ nextStateMap,
+ newStateMap);
+
+
+ }
+
+
+ }
+ newNextStateMap.put(transition.getKey(),nextState);
+ }
+
+ // Remove redundant transitions where defined tokenWord has same action as ANY_WORD
+ TopicMatcherDFAState anyWordState = newNextStateMap.get(TopicWord.ANY_WORD);
+ if(anyWordState != null)
+ {
+ List<TopicWord> removeList = new ArrayList<TopicWord>();
+ for(Map.Entry<TopicWord,TopicMatcherDFAState> entry : newNextStateMap.entrySet())
+ {
+ if(entry.getValue() == anyWordState && entry.getKey() != TopicWord.ANY_WORD)
+ {
+ removeList.add(entry.getKey());
+ }
+ }
+ for(TopicWord removeKey : removeList)
+ {
+ newNextStateMap.remove(removeKey);
+ }
+ }
+
+
+
+ }
+
+
+ public String toString()
+ {
+ StringBuilder transitions = new StringBuilder();
+ for(Map.Entry<TopicWord, TopicMatcherDFAState> entry : _nextStateMap.entrySet())
+ {
+ transitions.append("[ ");
+ transitions.append(entry.getKey());
+ transitions.append("\t ->\t ");
+ transitions.append(entry.getValue()._id);
+ transitions.append(" ]\n");
+ }
+
+
+ return "[ State " + _id + " ]\n" + transitions + "\n";
+
+ }
+
+ public String reachableStates()
+ {
+ StringBuilder result = new StringBuilder("Start state: " + _id + "\n");
+
+ SortedSet<TopicMatcherDFAState> reachableStates =
+ new TreeSet<TopicMatcherDFAState>(new Comparator<TopicMatcherDFAState>()
+ {
+ public int compare(final TopicMatcherDFAState o1, final TopicMatcherDFAState o2)
+ {
+ return o1._id - o2._id;
+ }
+ });
+ reachableStates.add(this);
+
+ int count;
+
+ do
+ {
+ count = reachableStates.size();
+ Collection<TopicMatcherDFAState> originalStates = new ArrayList<TopicMatcherDFAState>(reachableStates);
+ for(TopicMatcherDFAState state : originalStates)
+ {
+ reachableStates.addAll(state._nextStateMap.values());
+ }
+ }
+ while(reachableStates.size() != count);
+
+
+
+ for(TopicMatcherDFAState state : reachableStates)
+ {
+ result.append(state.toString());
+ }
+
+ return result.toString();
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java
new file mode 100644
index 0000000000..71d30adfac
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java
@@ -0,0 +1,25 @@
+package org.apache.qpid.server.exchange.topic;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public interface TopicMatcherResult
+{
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java
new file mode 100644
index 0000000000..3e9facf412
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java
@@ -0,0 +1,613 @@
+package org.apache.qpid.server.exchange.topic;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.AMQShortStringTokenizer;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+import java.io.IOException;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class TopicParser
+{
+ private static final byte TOPIC_DELIMITER = (byte)'.';
+
+ private final TopicWordDictionary _dictionary = new TopicWordDictionary();
+ private final AtomicReference<TopicMatcherDFAState> _stateMachine = new AtomicReference<TopicMatcherDFAState>();
+
+ private static class Position
+ {
+ private final TopicWord _word;
+ private final boolean _selfTransition;
+ private final int _position;
+ private final boolean _endState;
+ private boolean _followedByAnyLoop;
+
+
+ public Position(final int position, final TopicWord word, final boolean selfTransition, final boolean endState)
+ {
+ _position = position;
+ _word = word;
+ _selfTransition = selfTransition;
+ _endState = endState;
+ }
+
+
+ }
+
+ private static final Position ERROR_POSITION = new Position(Integer.MAX_VALUE,null, true, false);
+
+ private static class SimpleState
+ {
+ Set<Position> _positions;
+ Map<TopicWord, SimpleState> _nextState;
+ }
+
+
+ public void addBinding(AMQShortString bindingKey, TopicMatcherResult result)
+ {
+
+ TopicMatcherDFAState startingStateMachine;
+ TopicMatcherDFAState newStateMachine;
+
+ do
+ {
+ startingStateMachine = _stateMachine.get();
+ if(startingStateMachine == null)
+ {
+ newStateMachine = createStateMachine(bindingKey, result);
+ }
+ else
+ {
+ newStateMachine = startingStateMachine.mergeStateMachines(createStateMachine(bindingKey, result));
+ }
+
+ }
+ while(!_stateMachine.compareAndSet(startingStateMachine,newStateMachine));
+
+ }
+
+ public Collection<TopicMatcherResult> parse(AMQShortString routingKey)
+ {
+ TopicMatcherDFAState stateMachine = _stateMachine.get();
+ if(stateMachine == null)
+ {
+ return Collections.EMPTY_SET;
+ }
+ else
+ {
+ return stateMachine.parse(_dictionary,routingKey);
+ }
+ }
+
+
+ TopicMatcherDFAState createStateMachine(AMQShortString bindingKey, TopicMatcherResult result)
+ {
+ List<TopicWord> wordList = createTopicWordList(bindingKey);
+ int wildCards = 0;
+ for(TopicWord word : wordList)
+ {
+ if(word == TopicWord.WILDCARD_WORD)
+ {
+ wildCards++;
+ }
+ }
+ if(wildCards == 0)
+ {
+ TopicMatcherDFAState[] states = new TopicMatcherDFAState[wordList.size()+1];
+ states[states.length-1] = new TopicMatcherDFAState(Collections.EMPTY_MAP, Collections.singleton(result));
+ for(int i = states.length-2; i >= 0; i--)
+ {
+ states[i] = new TopicMatcherDFAState(Collections.singletonMap(wordList.get(i),states[i+1]),Collections.EMPTY_SET);
+
+ }
+ return states[0];
+ }
+ else if(wildCards == wordList.size())
+ {
+ Map<TopicWord,TopicMatcherDFAState> stateMap = new HashMap<TopicWord,TopicMatcherDFAState>();
+ TopicMatcherDFAState state = new TopicMatcherDFAState(stateMap, Collections.singleton(result));
+ stateMap.put(TopicWord.ANY_WORD, state);
+ return state;
+ }
+
+
+ int positionCount = wordList.size() - wildCards;
+
+ Position[] positions = new Position[positionCount+1];
+
+ int lastWord;
+
+ if(wordList.get(wordList.size()-1)== TopicWord.WILDCARD_WORD)
+ {
+ lastWord = wordList.size()-1;
+ positions[positionCount] = new Position(positionCount, TopicWord.ANY_WORD, true, true);
+ }
+ else
+ {
+ lastWord = wordList.size();
+ positions[positionCount] = new Position(positionCount, TopicWord.ANY_WORD, false, true);
+ }
+
+
+ int pos = 0;
+ int wordPos = 0;
+
+
+ while(wordPos < lastWord)
+ {
+ TopicWord word = wordList.get(wordPos++);
+
+ if(word == TopicWord.WILDCARD_WORD)
+ {
+ int nextWordPos = wordPos++;
+ word = wordList.get(nextWordPos);
+
+ positions[pos] = new Position(pos++,word,true,false);
+ }
+ else
+ {
+ positions[pos] = new Position(pos++,word,false,false);
+ }
+
+ }
+
+
+ for(int p = 0; p<positionCount; p++)
+ {
+ boolean followedByWildcards = true;
+
+ int n = p;
+ while(followedByWildcards && n<(positionCount+1))
+ {
+
+ if(positions[n]._selfTransition)
+ {
+ break;
+ }
+ else if(positions[n]._word!=TopicWord.ANY_WORD)
+ {
+ followedByWildcards = false;
+ }
+ n++;
+ }
+
+
+ positions[p]._followedByAnyLoop = followedByWildcards && (n!= positionCount+1);
+ }
+
+
+ // from each position you transition to a set of other positions.
+ // we approach this by examining steps of increasing length - so we
+ // look how far we can go from the start position in 1 word, 2 words, etc...
+
+ Map<Set<Position>,SimpleState> stateMap = new HashMap<Set<Position>,SimpleState>();
+
+
+ SimpleState state = new SimpleState();
+ state._positions = Collections.singleton( positions[0] );
+ stateMap.put(state._positions, state);
+
+ calculateNextStates(state, stateMap, positions);
+
+ SimpleState[] simpleStates = stateMap.values().toArray(new SimpleState[stateMap.size()]);
+ HashMap<TopicWord, TopicMatcherDFAState>[] dfaStateMaps = new HashMap[simpleStates.length];
+ Map<SimpleState, TopicMatcherDFAState> simple2DFAMap = new HashMap<SimpleState, TopicMatcherDFAState>();
+
+ for(int i = 0; i < simpleStates.length; i++)
+ {
+
+ Collection<TopicMatcherResult> results;
+ boolean endState = false;
+
+ for(Position p : simpleStates[i]._positions)
+ {
+ if(p._endState)
+ {
+ endState = true;
+ break;
+ }
+ }
+
+ if(endState)
+ {
+ results = Collections.singleton(result);
+ }
+ else
+ {
+ results = Collections.EMPTY_SET;
+ }
+
+ dfaStateMaps[i] = new HashMap<TopicWord, TopicMatcherDFAState>();
+ simple2DFAMap.put(simpleStates[i], new TopicMatcherDFAState(dfaStateMaps[i],results));
+
+ }
+ for(int i = 0; i < simpleStates.length; i++)
+ {
+ SimpleState simpleState = simpleStates[i];
+
+ Map<TopicWord, SimpleState> nextSimpleStateMap = simpleState._nextState;
+ for(Map.Entry<TopicWord, SimpleState> stateMapEntry : nextSimpleStateMap.entrySet())
+ {
+ dfaStateMaps[i].put(stateMapEntry.getKey(), simple2DFAMap.get(stateMapEntry.getValue()));
+ }
+
+ }
+
+ return simple2DFAMap.get(state);
+
+ }
+
+
+
+ private void calculateNextStates(final SimpleState state,
+ final Map<Set<Position>, SimpleState> stateMap,
+ final Position[] positions)
+ {
+ Map<TopicWord, Set<Position>> transitions = new HashMap<TopicWord,Set<Position>>();
+
+ for(Position pos : state._positions)
+ {
+ if(pos._selfTransition)
+ {
+ Set<Position> dest = transitions.get(TopicWord.ANY_WORD);
+ if(dest == null)
+ {
+ dest = new HashSet<Position>();
+ transitions.put(TopicWord.ANY_WORD,dest);
+ }
+ dest.add(pos);
+ }
+
+ final int nextPos = pos._position + 1;
+ Position nextPosition = nextPos == positions.length ? ERROR_POSITION : positions[nextPos];
+
+ Set<Position> dest = transitions.get(pos._word);
+ if(dest == null)
+ {
+ dest = new HashSet<Position>();
+ transitions.put(pos._word,dest);
+ }
+ dest.add(nextPosition);
+
+ }
+
+ Set<Position> anyWordTransitions = transitions.get(TopicWord.ANY_WORD);
+ if(anyWordTransitions != null)
+ {
+ for(Set<Position> dest : transitions.values())
+ {
+ dest.addAll(anyWordTransitions);
+ }
+ }
+
+ state._nextState = new HashMap<TopicWord, SimpleState>();
+
+ for(Map.Entry<TopicWord,Set<Position>> dest : transitions.entrySet())
+ {
+
+ if(dest.getValue().size()>1)
+ {
+ dest.getValue().remove(ERROR_POSITION);
+ }
+ Position loopingTerminal = null;
+ for(Position destPos : dest.getValue())
+ {
+ if(destPos._selfTransition && destPos._endState)
+ {
+ loopingTerminal = destPos;
+ break;
+ }
+ }
+
+ if(loopingTerminal!=null)
+ {
+ dest.setValue(Collections.singleton(loopingTerminal));
+ }
+ else
+ {
+ Position anyLoop = null;
+ for(Position destPos : dest.getValue())
+ {
+ if(destPos._followedByAnyLoop)
+ {
+ if(anyLoop == null || anyLoop._position<destPos._position)
+ {
+ anyLoop = destPos;
+ }
+ }
+ }
+ if(anyLoop != null)
+ {
+ Collection<Position> removals = new ArrayList<Position>();
+ for(Position destPos : dest.getValue())
+ {
+ if(destPos._position < anyLoop._position)
+ {
+ removals.add(destPos);
+ }
+ }
+ dest.getValue().removeAll(removals);
+ }
+ }
+
+ SimpleState stateForEntry = stateMap.get(dest.getValue());
+ if(stateForEntry == null)
+ {
+ stateForEntry = new SimpleState();
+ stateForEntry._positions = dest.getValue();
+ stateMap.put(dest.getValue(),stateForEntry);
+ calculateNextStates(stateForEntry,
+ stateMap,
+ positions);
+ }
+ state._nextState.put(dest.getKey(),stateForEntry);
+
+
+
+ }
+
+ // remove redundant transitions
+ SimpleState anyWordState = state._nextState.get(TopicWord.ANY_WORD);
+ if(anyWordState != null)
+ {
+ List<TopicWord> removeList = new ArrayList<TopicWord>();
+ for(Map.Entry<TopicWord,SimpleState> entry : state._nextState.entrySet())
+ {
+ if(entry.getValue() == anyWordState && entry.getKey() != TopicWord.ANY_WORD)
+ {
+ removeList.add(entry.getKey());
+ }
+ }
+ for(TopicWord removeKey : removeList)
+ {
+ state._nextState.remove(removeKey);
+ }
+ }
+
+
+ }
+
+ private List<TopicWord> createTopicWordList(final AMQShortString bindingKey)
+ {
+ AMQShortStringTokenizer tokens = bindingKey.tokenize(TOPIC_DELIMITER);
+ TopicWord previousWord = null;
+
+ List<TopicWord> wordList = new ArrayList<TopicWord>();
+
+ while(tokens.hasMoreTokens())
+ {
+ TopicWord nextWord = _dictionary.getOrCreateWord(tokens.nextToken());
+ if(previousWord == TopicWord.WILDCARD_WORD)
+ {
+
+ if(nextWord == TopicWord.WILDCARD_WORD)
+ {
+ // consecutive wildcards can be merged
+ // i.e. subsequent wildcards can be discarded
+ continue;
+ }
+ else if(nextWord == TopicWord.ANY_WORD)
+ {
+ // wildcard and anyword can be reordered to always put anyword first
+ wordList.set(wordList.size()-1,TopicWord.ANY_WORD);
+ nextWord = TopicWord.WILDCARD_WORD;
+ }
+ }
+ wordList.add(nextWord);
+ previousWord = nextWord;
+
+ }
+ return wordList;
+ }
+
+
+ public static void main(String[] args)
+ {
+
+ printMatches("#.b.*.*.*.*.*.h.#.j.*.*.*.*.*.*.q.#.r.*.*.*.*.*.*.*.*","a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z");
+ printMatches(new String[]{
+ "#.a.#",
+ "#.b.#",
+ "#.c.#",
+ "#.d.#",
+ "#.e.#",
+ "#.f.#",
+ "#.g.#",
+ "#.h.#",
+ "#.i.#",
+ "#.j.#",
+ "#.k.#",
+ "#.l.#",
+ "#.m.#",
+ "#.n.#",
+ "#.o.#",
+ "#.p.#",
+ "#.q.#"
+
+ }, "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z");
+/*
+ printMatches(new String[]{
+ "#.a.#",
+ "#.b.#",
+ "#.c.#",
+ "#.d.#",
+ "#.e.#",
+ "#.f.#",
+ "#.g.#",
+ "#.h.#",
+ "#.i.#",
+ "#.j.#",
+ "#.k.#",
+ "#.l.#",
+ "#.m.#",
+ "#.n.#",
+ "#.o.#",
+ "#.p.#",
+ "#.q.#",
+ "#.r.#",
+ "#.s.#",
+ "#.t.#",
+ "#.u.#",
+ "#.v.#",
+ "#.w.#",
+ "#.x.#",
+ "#.y.#",
+ "#.z.#"
+
+
+ },"a.b");
+
+ printMatches("#.b.*.*.*.*.*.h.#.j.*.*.*.*.*.p.#.r.*.*.*.*.*","a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z");
+ printMatches("#.b.*.*.*.*.*.h.#.j.*.*.*.*.*.p.#.r.*.*.*.*.*.*.*.*","a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z");
+ printMatches("a.#.b.#","a.b.b.b.b.b.b.b.c");
+
+*/
+
+ printMatches("","");
+ printMatches("a","a");
+ printMatches("a","");
+ printMatches("","a");
+ printMatches("a.b","a.b");
+ printMatches("a","a.b");
+ printMatches("a.b","a");
+ printMatches("*","a");
+ printMatches("*.b","a.b");
+ printMatches("*.*","a.b");
+ printMatches("a.*","a.b");
+ printMatches("a.*.#","a.b");
+ printMatches("a.#.b","a.b");
+
+ printMatches("#.b","a");
+ printMatches("#.b","a.b");
+ printMatches("#.a.b","a.b");
+
+
+ printMatches("#","");
+ printMatches("#","a");
+ printMatches("#","a.b");
+ printMatches("#.#","a.b");
+ printMatches("#.*","a.b");
+
+ printMatches("#.a.b","a.b");
+ printMatches("a.b.#","a.b");
+ printMatches("a.#","a.b");
+ printMatches("#.*.#","a.b");
+ printMatches("#.*.b.#","a.b");
+ printMatches("#.a.*.#","a.b");
+ printMatches("#.a.#.b.#","a.b");
+ printMatches("#.*.#.*.#","a.b");
+ printMatches("*.#.*.#","a.b");
+ printMatches("#.*.#.*","a.b");
+
+
+ printMatches(new String[]{"a.#.b.#","a.*.#.b.#"},"a.b.b.b.b.b.b.b.c");
+
+
+ printMatches(new String[]{"a.b", "a.c"},"a.b");
+ printMatches(new String[]{"a.#", "a.c", "#.b"},"a.b");
+ printMatches(new String[]{"a.#", "a.c", "#.b", "#", "*.*"},"a.b");
+
+ printMatches(new String[]{"a.b.c.d.e.#", "a.b.c.d.#", "a.b.c.d.*", "a.b.c.#", "#.e", "a.*.c.d.e","#.c.*.#.*.*"},"a.b.c.d.e");
+ printMatches(new String[]{"a.b.c.d.e.#", "a.b.c.d.#", "a.b.c.d.*", "a.b.c.#", "#.e", "a.*.c.d.e","#.c.*.#.*.*"},"a.b.c.d.f.g");
+
+
+
+
+ }
+
+ private static void printMatches(final String[] bindingKeys, final String routingKey)
+ {
+ TopicMatcherDFAState sm = null;
+ Map<TopicMatcherResult, String> resultMap = new HashMap<TopicMatcherResult, String>();
+
+ TopicParser parser = new TopicParser();
+
+ long start = System.currentTimeMillis();
+ for(int i = 0; i < bindingKeys.length; i++)
+ {
+ System.out.println((System.currentTimeMillis() - start) + ":\t" + bindingKeys[i]);
+ TopicMatcherResult r = new TopicMatcherResult(){};
+ resultMap.put(r, bindingKeys[i]);
+ AMQShortString bindingKeyShortString = new AMQShortString(bindingKeys[i]);
+
+ System.err.println("=====================================================");
+ System.err.println("Adding binding key: " + bindingKeyShortString);
+ System.err.println("-----------------------------------------------------");
+
+
+ if(i==0)
+ {
+ sm = parser.createStateMachine(bindingKeyShortString, r);
+ }
+ else
+ {
+ sm = sm.mergeStateMachines(parser.createStateMachine(bindingKeyShortString, r));
+ }
+ System.err.println(sm.reachableStates());
+ System.err.println("=====================================================");
+ try
+ {
+ System.in.read();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ }
+ }
+ AMQShortString routingKeyShortString = new AMQShortString(routingKey);
+
+ Collection<TopicMatcherResult> results = sm.parse(parser._dictionary, routingKeyShortString);
+ Collection<String> resultStrings = new ArrayList<String>();
+
+ for(TopicMatcherResult result : results)
+ {
+ resultStrings.add(resultMap.get(result));
+ }
+
+ final ArrayList<String> nonMatches = new ArrayList<String>(Arrays.asList(bindingKeys));
+ nonMatches.removeAll(resultStrings);
+ System.out.println("\""+routingKeyShortString+"\" matched with " + resultStrings + " DID NOT MATCH with " + nonMatches);
+
+
+ }
+
+ private static void printMatches(String bindingKey, String routingKey)
+ {
+ printMatches(new String[] { bindingKey }, routingKey);
+ }
+
+
+ private static boolean matches(String bindingKey, String routingKey)
+ {
+ AMQShortString bindingKeyShortString = new AMQShortString(bindingKey);
+ AMQShortString routingKeyShortString = new AMQShortString(routingKey);
+ TopicParser parser = new TopicParser();
+
+ final TopicMatcherResult result = new TopicMatcherResult(){};
+
+ TopicMatcherDFAState sm = parser.createStateMachine(bindingKeyShortString, result);
+ return !sm.parse(parser._dictionary,routingKeyShortString).isEmpty();
+
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java
new file mode 100644
index 0000000000..f14d70f8a1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java
@@ -0,0 +1,54 @@
+package org.apache.qpid.server.exchange.topic;
+
+import org.apache.qpid.framing.AMQShortString;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public final class TopicWord
+{
+ public static final TopicWord ANY_WORD = new TopicWord("*");
+ public static final TopicWord WILDCARD_WORD = new TopicWord("#");
+ private String _word;
+
+ public TopicWord()
+ {
+
+ }
+
+ public TopicWord(String s)
+ {
+ _word = s;
+ }
+
+ public TopicWord(final AMQShortString name)
+ {
+ _word = name.toString();
+ }
+
+ public String toString()
+ {
+ return _word;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java
new file mode 100644
index 0000000000..65a0cd3107
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java
@@ -0,0 +1,63 @@
+package org.apache.qpid.server.exchange.topic;
+
+import org.apache.qpid.framing.AMQShortString;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class TopicWordDictionary
+{
+ private final ConcurrentHashMap<AMQShortString,TopicWord> _dictionary =
+ new ConcurrentHashMap<AMQShortString,TopicWord>();
+
+
+
+ public TopicWordDictionary()
+ {
+ _dictionary.put(new AMQShortString("*"), TopicWord.ANY_WORD);
+ _dictionary.put(new AMQShortString("#"), TopicWord.WILDCARD_WORD);
+ }
+
+
+
+
+ public TopicWord getOrCreateWord(AMQShortString name)
+ {
+ TopicWord word = _dictionary.putIfAbsent(name, new TopicWord(name));
+ if(word == null)
+ {
+ word = _dictionary.get(name);
+ }
+ return word;
+ }
+
+
+ public TopicWord getWord(AMQShortString name)
+ {
+ TopicWord word = _dictionary.get(name);
+ if(word == null)
+ {
+ word = TopicWord.ANY_WORD;
+ }
+ return word;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ArithmeticExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ArithmeticExpression.java
new file mode 100644
index 0000000000..a964bce306
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ArithmeticExpression.java
@@ -0,0 +1,275 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.server.filter;
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.Filterable;
+
+/**
+ * An expression which performs an operation on two expression values
+ */
+public abstract class ArithmeticExpression<E extends Exception> extends BinaryExpression<E>
+{
+
+ protected static final int INTEGER = 1;
+ protected static final int LONG = 2;
+ protected static final int DOUBLE = 3;
+
+ /**
+ * @param left
+ * @param right
+ */
+ public ArithmeticExpression(Expression left, Expression right)
+ {
+ super(left, right);
+ }
+
+ public static Expression createPlus(Expression left, Expression right)
+ {
+ return new ArithmeticExpression(left, right)
+ {
+ protected Object evaluate(Object lvalue, Object rvalue)
+ {
+ if (lvalue instanceof String)
+ {
+ String text = (String) lvalue;
+ String answer = text + rvalue;
+
+ return answer;
+ }
+ else if (lvalue instanceof Number)
+ {
+ return plus((Number) lvalue, asNumber(rvalue));
+ }
+
+ throw new RuntimeException("Cannot call plus operation on: " + lvalue + " and: " + rvalue);
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "+";
+ }
+ };
+ }
+
+ public static Expression createMinus(Expression left, Expression right)
+ {
+ return new ArithmeticExpression(left, right)
+ {
+ protected Object evaluate(Object lvalue, Object rvalue)
+ {
+ if (lvalue instanceof Number)
+ {
+ return minus((Number) lvalue, asNumber(rvalue));
+ }
+
+ throw new RuntimeException("Cannot call minus operation on: " + lvalue + " and: " + rvalue);
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "-";
+ }
+ };
+ }
+
+ public static Expression createMultiply(Expression left, Expression right)
+ {
+ return new ArithmeticExpression(left, right)
+ {
+
+ protected Object evaluate(Object lvalue, Object rvalue)
+ {
+ if (lvalue instanceof Number)
+ {
+ return multiply((Number) lvalue, asNumber(rvalue));
+ }
+
+ throw new RuntimeException("Cannot call multiply operation on: " + lvalue + " and: " + rvalue);
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "*";
+ }
+ };
+ }
+
+ public static Expression createDivide(Expression left, Expression right)
+ {
+ return new ArithmeticExpression(left, right)
+ {
+
+ protected Object evaluate(Object lvalue, Object rvalue)
+ {
+ if (lvalue instanceof Number)
+ {
+ return divide((Number) lvalue, asNumber(rvalue));
+ }
+
+ throw new RuntimeException("Cannot call divide operation on: " + lvalue + " and: " + rvalue);
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "/";
+ }
+ };
+ }
+
+ public static Expression createMod(Expression left, Expression right)
+ {
+ return new ArithmeticExpression(left, right)
+ {
+
+ protected Object evaluate(Object lvalue, Object rvalue)
+ {
+ if (lvalue instanceof Number)
+ {
+ return mod((Number) lvalue, asNumber(rvalue));
+ }
+
+ throw new RuntimeException("Cannot call mod operation on: " + lvalue + " and: " + rvalue);
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "%";
+ }
+ };
+ }
+
+ protected Number plus(Number left, Number right)
+ {
+ switch (numberType(left, right))
+ {
+
+ case INTEGER:
+ return new Integer(left.intValue() + right.intValue());
+
+ case LONG:
+ return new Long(left.longValue() + right.longValue());
+
+ default:
+ return new Double(left.doubleValue() + right.doubleValue());
+ }
+ }
+
+ protected Number minus(Number left, Number right)
+ {
+ switch (numberType(left, right))
+ {
+
+ case INTEGER:
+ return new Integer(left.intValue() - right.intValue());
+
+ case LONG:
+ return new Long(left.longValue() - right.longValue());
+
+ default:
+ return new Double(left.doubleValue() - right.doubleValue());
+ }
+ }
+
+ protected Number multiply(Number left, Number right)
+ {
+ switch (numberType(left, right))
+ {
+
+ case INTEGER:
+ return new Integer(left.intValue() * right.intValue());
+
+ case LONG:
+ return new Long(left.longValue() * right.longValue());
+
+ default:
+ return new Double(left.doubleValue() * right.doubleValue());
+ }
+ }
+
+ protected Number divide(Number left, Number right)
+ {
+ return new Double(left.doubleValue() / right.doubleValue());
+ }
+
+ protected Number mod(Number left, Number right)
+ {
+ return new Double(left.doubleValue() % right.doubleValue());
+ }
+
+ private int numberType(Number left, Number right)
+ {
+ if (isDouble(left) || isDouble(right))
+ {
+ return DOUBLE;
+ }
+ else if ((left instanceof Long) || (right instanceof Long))
+ {
+ return LONG;
+ }
+ else
+ {
+ return INTEGER;
+ }
+ }
+
+ private boolean isDouble(Number n)
+ {
+ return (n instanceof Float) || (n instanceof Double);
+ }
+
+ protected Number asNumber(Object value)
+ {
+ if (value instanceof Number)
+ {
+ return (Number) value;
+ }
+ else
+ {
+ throw new RuntimeException("Cannot convert value: " + value + " into a number");
+ }
+ }
+
+ public Object evaluate(Filterable<E> message) throws E
+ {
+ Object lvalue = left.evaluate(message);
+ if (lvalue == null)
+ {
+ return null;
+ }
+
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null)
+ {
+ return null;
+ }
+
+ return evaluate(lvalue, rvalue);
+ }
+
+ /**
+ * @param lvalue
+ * @param rvalue
+ * @return
+ */
+ protected abstract Object evaluate(Object lvalue, Object rvalue);
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BinaryExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BinaryExpression.java
new file mode 100644
index 0000000000..7308de80d6
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BinaryExpression.java
@@ -0,0 +1,106 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.server.filter;
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+/**
+ * An expression which performs an operation on two expression values.
+ */
+public abstract class BinaryExpression<E extends Exception> implements Expression<E>
+{
+ protected Expression<E> left;
+ protected Expression<E> right;
+
+ public BinaryExpression(Expression<E> left, Expression<E> right)
+ {
+ this.left = left;
+ this.right = right;
+ }
+
+ public Expression<E> getLeft()
+ {
+ return left;
+ }
+
+ public Expression<E> getRight()
+ {
+ return right;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString()
+ {
+ return "(" + left.toString() + " " + getExpressionSymbol() + " " + right.toString() + ")";
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode()
+ {
+ return toString().hashCode();
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o)
+ {
+
+ if ((o == null) || !this.getClass().equals(o.getClass()))
+ {
+ return false;
+ }
+
+ return toString().equals(o.toString());
+
+ }
+
+ /**
+ * Returns the symbol that represents this binary expression. For example, addition is
+ * represented by "+"
+ *
+ * @return
+ */
+ public abstract String getExpressionSymbol();
+
+ /**
+ * @param expression
+ */
+ public void setRight(Expression<E> expression)
+ {
+ right = expression;
+ }
+
+ /**
+ * @param expression
+ */
+ public void setLeft(Expression<E> expression)
+ {
+ left = expression;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BooleanExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BooleanExpression.java
new file mode 100644
index 0000000000..9beb9798d0
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BooleanExpression.java
@@ -0,0 +1,41 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.server.filter;
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.Filterable;
+
+/**
+ * A BooleanExpression is an expression that always
+ * produces a Boolean result.
+ */
+public interface BooleanExpression<E extends Exception> extends Expression<E>
+{
+
+ /**
+ * @param message
+ * @return true if the expression evaluates to Boolean.TRUE.
+ * @throws E
+ */
+ public boolean matches(Filterable<E> message) throws E;
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ComparisonExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ComparisonExpression.java
new file mode 100644
index 0000000000..921005c462
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ComparisonExpression.java
@@ -0,0 +1,601 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.filter;
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.Filterable;
+
+/**
+ * A filter performing a comparison of two objects
+ */
+public abstract class ComparisonExpression<E extends Exception> extends BinaryExpression<E> implements BooleanExpression<E>
+{
+
+ public static<E extends Exception> BooleanExpression<E> createBetween(Expression<E> value, Expression left, Expression<E> right)
+ {
+ return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right));
+ }
+
+ public static<E extends Exception> BooleanExpression<E> createNotBetween(Expression<E> value, Expression<E> left, Expression<E> right)
+ {
+ return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right));
+ }
+
+ private static final HashSet REGEXP_CONTROL_CHARS = new HashSet();
+
+ static
+ {
+ REGEXP_CONTROL_CHARS.add(new Character('.'));
+ REGEXP_CONTROL_CHARS.add(new Character('\\'));
+ REGEXP_CONTROL_CHARS.add(new Character('['));
+ REGEXP_CONTROL_CHARS.add(new Character(']'));
+ REGEXP_CONTROL_CHARS.add(new Character('^'));
+ REGEXP_CONTROL_CHARS.add(new Character('$'));
+ REGEXP_CONTROL_CHARS.add(new Character('?'));
+ REGEXP_CONTROL_CHARS.add(new Character('*'));
+ REGEXP_CONTROL_CHARS.add(new Character('+'));
+ REGEXP_CONTROL_CHARS.add(new Character('{'));
+ REGEXP_CONTROL_CHARS.add(new Character('}'));
+ REGEXP_CONTROL_CHARS.add(new Character('|'));
+ REGEXP_CONTROL_CHARS.add(new Character('('));
+ REGEXP_CONTROL_CHARS.add(new Character(')'));
+ REGEXP_CONTROL_CHARS.add(new Character(':'));
+ REGEXP_CONTROL_CHARS.add(new Character('&'));
+ REGEXP_CONTROL_CHARS.add(new Character('<'));
+ REGEXP_CONTROL_CHARS.add(new Character('>'));
+ REGEXP_CONTROL_CHARS.add(new Character('='));
+ REGEXP_CONTROL_CHARS.add(new Character('!'));
+ }
+
+ static class LikeExpression<E extends Exception> extends UnaryExpression<E> implements BooleanExpression<E>
+ {
+
+ Pattern likePattern;
+
+ /**
+ * @param right
+ */
+ public LikeExpression(Expression<E> right, String like, int escape)
+ {
+ super(right);
+
+ StringBuffer regexp = new StringBuffer(like.length() * 2);
+ regexp.append("\\A"); // The beginning of the input
+ for (int i = 0; i < like.length(); i++)
+ {
+ char c = like.charAt(i);
+ if (escape == (0xFFFF & c))
+ {
+ i++;
+ if (i >= like.length())
+ {
+ // nothing left to escape...
+ break;
+ }
+
+ char t = like.charAt(i);
+ regexp.append("\\x");
+ regexp.append(Integer.toHexString(0xFFFF & t));
+ }
+ else if (c == '%')
+ {
+ regexp.append(".*?"); // Do a non-greedy match
+ }
+ else if (c == '_')
+ {
+ regexp.append("."); // match one
+ }
+ else if (REGEXP_CONTROL_CHARS.contains(new Character(c)))
+ {
+ regexp.append("\\x");
+ regexp.append(Integer.toHexString(0xFFFF & c));
+ }
+ else
+ {
+ regexp.append(c);
+ }
+ }
+
+ regexp.append("\\z"); // The end of the input
+
+ likePattern = Pattern.compile(regexp.toString(), Pattern.DOTALL);
+ }
+
+ /**
+ * org.apache.activemq.filter.UnaryExpression#getExpressionSymbol()
+ */
+ public String getExpressionSymbol()
+ {
+ return "LIKE";
+ }
+
+ /**
+ * org.apache.activemq.filter.Expression#evaluate(MessageEvaluationContext)
+ */
+ public Object evaluate(Filterable<E> message) throws E
+ {
+
+ Object rv = this.getRight().evaluate(message);
+
+ if (rv == null)
+ {
+ return null;
+ }
+
+ if (!(rv instanceof String))
+ {
+ return
+ Boolean.FALSE;
+ // throw new RuntimeException("LIKE can only operate on String identifiers. LIKE attemped on: '" + rv.getClass());
+ }
+
+ return likePattern.matcher((String) rv).matches() ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ public boolean matches(Filterable<E> message) throws E
+ {
+ Object object = evaluate(message);
+
+ return (object != null) && (object == Boolean.TRUE);
+ }
+ }
+
+ public static BooleanExpression createLike(Expression left, String right, String escape)
+ {
+ if ((escape != null) && (escape.length() != 1))
+ {
+ throw new RuntimeException(
+ "The ESCAPE string litteral is invalid. It can only be one character. Litteral used: " + escape);
+ }
+
+ int c = -1;
+ if (escape != null)
+ {
+ c = 0xFFFF & escape.charAt(0);
+ }
+
+ return new LikeExpression(left, right, c);
+ }
+
+ public static BooleanExpression createNotLike(Expression left, String right, String escape)
+ {
+ return UnaryExpression.createNOT(createLike(left, right, escape));
+ }
+
+ public static BooleanExpression createInFilter(Expression left, List elements)
+ {
+
+ if (!(left instanceof PropertyExpression))
+ {
+ throw new RuntimeException("Expected a property for In expression, got: " + left);
+ }
+
+ return UnaryExpression.createInExpression((PropertyExpression) left, elements, false);
+
+ }
+
+ public static BooleanExpression createNotInFilter(Expression left, List elements)
+ {
+
+ if (!(left instanceof PropertyExpression))
+ {
+ throw new RuntimeException("Expected a property for In expression, got: " + left);
+ }
+
+ return UnaryExpression.createInExpression((PropertyExpression) left, elements, true);
+
+ }
+
+ public static BooleanExpression createIsNull(Expression left)
+ {
+ return doCreateEqual(left, ConstantExpression.NULL);
+ }
+
+ public static BooleanExpression createIsNotNull(Expression left)
+ {
+ return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL));
+ }
+
+ public static BooleanExpression createNotEqual(Expression left, Expression right)
+ {
+ return UnaryExpression.createNOT(createEqual(left, right));
+ }
+
+ public static BooleanExpression createEqual(Expression left, Expression right)
+ {
+ checkEqualOperand(left);
+ checkEqualOperand(right);
+ checkEqualOperandCompatability(left, right);
+
+ return doCreateEqual(left, right);
+ }
+
+ private static<E extends Exception> BooleanExpression<E> doCreateEqual(Expression<E> left, Expression<E> right)
+ {
+ return new EqualExpression(left, right);
+ }
+
+ public static BooleanExpression createGreaterThan(final Expression left, final Expression right)
+ {
+ checkLessThanOperand(left);
+ checkLessThanOperand(right);
+
+ return new ComparisonExpression(left, right)
+ {
+ protected boolean asBoolean(int answer)
+ {
+ return answer > 0;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return ">";
+ }
+ };
+ }
+
+ public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right)
+ {
+ checkLessThanOperand(left);
+ checkLessThanOperand(right);
+
+ return new ComparisonExpression(left, right)
+ {
+ protected boolean asBoolean(int answer)
+ {
+ return answer >= 0;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return ">=";
+ }
+ };
+ }
+
+ public static BooleanExpression createLessThan(final Expression left, final Expression right)
+ {
+ checkLessThanOperand(left);
+ checkLessThanOperand(right);
+
+ return new ComparisonExpression(left, right)
+ {
+
+ protected boolean asBoolean(int answer)
+ {
+ return answer < 0;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "<";
+ }
+
+ };
+ }
+
+ public static BooleanExpression createLessThanEqual(final Expression left, final Expression right)
+ {
+ checkLessThanOperand(left);
+ checkLessThanOperand(right);
+
+ return new ComparisonExpression(left, right)
+ {
+
+ protected boolean asBoolean(int answer)
+ {
+ return answer <= 0;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "<=";
+ }
+ };
+ }
+
+ /**
+ * Only Numeric expressions can be used in >, >=, < or <= expressions.s
+ *
+ * @param expr
+ */
+ public static void checkLessThanOperand(Expression expr)
+ {
+ if (expr instanceof ConstantExpression)
+ {
+ Object value = ((ConstantExpression) expr).getValue();
+ if (value instanceof Number)
+ {
+ return;
+ }
+
+ // Else it's boolean or a String..
+ throw new RuntimeException("Value '" + expr + "' cannot be compared.");
+ }
+
+ if (expr instanceof BooleanExpression)
+ {
+ throw new RuntimeException("Value '" + expr + "' cannot be compared.");
+ }
+ }
+
+ /**
+ * Validates that the expression can be used in == or <> expression.
+ * Cannot not be NULL TRUE or FALSE litterals.
+ *
+ * @param expr
+ */
+ public static void checkEqualOperand(Expression expr)
+ {
+ if (expr instanceof ConstantExpression)
+ {
+ Object value = ((ConstantExpression) expr).getValue();
+ if (value == null)
+ {
+ throw new RuntimeException("'" + expr + "' cannot be compared.");
+ }
+ }
+ }
+
+ /**
+ *
+ * @param left
+ * @param right
+ */
+ private static void checkEqualOperandCompatability(Expression left, Expression right)
+ {
+ if ((left instanceof ConstantExpression) && (right instanceof ConstantExpression))
+ {
+ if ((left instanceof BooleanExpression) && !(right instanceof BooleanExpression))
+ {
+ throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'");
+ }
+ }
+ }
+
+ /**
+ * @param left
+ * @param right
+ */
+ public ComparisonExpression(Expression left, Expression right)
+ {
+ super(left, right);
+ }
+
+ public Object evaluate(Filterable<E> message) throws E
+ {
+ Comparable lv = (Comparable) left.evaluate(message);
+ if (lv == null)
+ {
+ return null;
+ }
+
+ Comparable rv = (Comparable) right.evaluate(message);
+ if (rv == null)
+ {
+ return null;
+ }
+
+ return compare(lv, rv);
+ }
+
+ protected Boolean compare(Comparable lv, Comparable rv)
+ {
+ Class lc = lv.getClass();
+ Class rc = rv.getClass();
+ // If the the objects are not of the same type,
+ // try to convert up to allow the comparison.
+ if (lc != rc)
+ {
+ if (lc == Byte.class)
+ {
+ if (rc == Short.class)
+ {
+ lv = new Short(((Number) lv).shortValue());
+ }
+ else if (rc == Integer.class)
+ {
+ lv = new Integer(((Number) lv).intValue());
+ }
+ else if (rc == Long.class)
+ {
+ lv = new Long(((Number) lv).longValue());
+ }
+ else if (rc == Float.class)
+ {
+ lv = new Float(((Number) lv).floatValue());
+ }
+ else if (rc == Double.class)
+ {
+ lv = new Double(((Number) lv).doubleValue());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ else if (lc == Short.class)
+ {
+ if (rc == Integer.class)
+ {
+ lv = new Integer(((Number) lv).intValue());
+ }
+ else if (rc == Long.class)
+ {
+ lv = new Long(((Number) lv).longValue());
+ }
+ else if (rc == Float.class)
+ {
+ lv = new Float(((Number) lv).floatValue());
+ }
+ else if (rc == Double.class)
+ {
+ lv = new Double(((Number) lv).doubleValue());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ else if (lc == Integer.class)
+ {
+ if (rc == Long.class)
+ {
+ lv = new Long(((Number) lv).longValue());
+ }
+ else if (rc == Float.class)
+ {
+ lv = new Float(((Number) lv).floatValue());
+ }
+ else if (rc == Double.class)
+ {
+ lv = new Double(((Number) lv).doubleValue());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ else if (lc == Long.class)
+ {
+ if (rc == Integer.class)
+ {
+ rv = new Long(((Number) rv).longValue());
+ }
+ else if (rc == Float.class)
+ {
+ lv = new Float(((Number) lv).floatValue());
+ }
+ else if (rc == Double.class)
+ {
+ lv = new Double(((Number) lv).doubleValue());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ else if (lc == Float.class)
+ {
+ if (rc == Integer.class)
+ {
+ rv = new Float(((Number) rv).floatValue());
+ }
+ else if (rc == Long.class)
+ {
+ rv = new Float(((Number) rv).floatValue());
+ }
+ else if (rc == Double.class)
+ {
+ lv = new Double(((Number) lv).doubleValue());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ else if (lc == Double.class)
+ {
+ if (rc == Integer.class)
+ {
+ rv = new Double(((Number) rv).doubleValue());
+ }
+ else if (rc == Long.class)
+ {
+ rv = new Double(((Number) rv).doubleValue());
+ }
+ else if (rc == Float.class)
+ {
+ rv = new Float(((Number) rv).doubleValue());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+
+ return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ protected abstract boolean asBoolean(int answer);
+
+ public boolean matches(Filterable<E> message) throws E
+ {
+ Object object = evaluate(message);
+
+ return (object != null) && (object == Boolean.TRUE);
+ }
+
+ private static class EqualExpression<E extends Exception> extends ComparisonExpression<E>
+ {
+ public EqualExpression(final Expression<E> left, final Expression<E> right)
+ {
+ super(left, right);
+ }
+
+ public Object evaluate(Filterable<E> message) throws E
+ {
+ Object lv = left.evaluate(message);
+ Object rv = right.evaluate(message);
+
+ // Iff one of the values is null
+ if ((lv == null) ^ (rv == null))
+ {
+ return Boolean.FALSE;
+ }
+
+ if ((lv == rv) || lv.equals(rv))
+ {
+ return Boolean.TRUE;
+ }
+
+ if ((lv instanceof Comparable) && (rv instanceof Comparable))
+ {
+ return compare((Comparable) lv, (Comparable) rv);
+ }
+
+ return Boolean.FALSE;
+ }
+
+ protected boolean asBoolean(int answer)
+ {
+ return answer == 0;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "=";
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java
new file mode 100644
index 0000000000..3ed2286f2e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java
@@ -0,0 +1,211 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.filter;
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+import java.math.BigDecimal;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.Filterable;
+
+/**
+ * Represents a constant expression
+ */
+public class ConstantExpression<E extends Exception> implements Expression<E>
+{
+
+ static class BooleanConstantExpression<E extends Exception> extends ConstantExpression<E> implements BooleanExpression<E>
+ {
+ public BooleanConstantExpression(Object value)
+ {
+ super(value);
+ }
+
+ public boolean matches(Filterable<E> message) throws E
+ {
+ Object object = evaluate(message);
+
+ return (object != null) && (object == Boolean.TRUE);
+ }
+ }
+
+ public static final BooleanConstantExpression NULL = new BooleanConstantExpression(null);
+ public static final BooleanConstantExpression TRUE = new BooleanConstantExpression(Boolean.TRUE);
+ public static final BooleanConstantExpression FALSE = new BooleanConstantExpression(Boolean.FALSE);
+
+ private Object value;
+
+ public static ConstantExpression createFromDecimal(String text)
+ {
+
+ // Strip off the 'l' or 'L' if needed.
+ if (text.endsWith("l") || text.endsWith("L"))
+ {
+ text = text.substring(0, text.length() - 1);
+ }
+
+ Number value;
+ try
+ {
+ value = new Long(text);
+ }
+ catch (NumberFormatException e)
+ {
+ // The number may be too big to fit in a long.
+ value = new BigDecimal(text);
+ }
+
+ long l = value.longValue();
+ if ((Integer.MIN_VALUE <= l) && (l <= Integer.MAX_VALUE))
+ {
+ value = new Integer(value.intValue());
+ }
+
+ return new ConstantExpression(value);
+ }
+
+ public static ConstantExpression createFromHex(String text)
+ {
+ Number value = new Long(Long.parseLong(text.substring(2), 16));
+ long l = value.longValue();
+ if ((Integer.MIN_VALUE <= l) && (l <= Integer.MAX_VALUE))
+ {
+ value = new Integer(value.intValue());
+ }
+
+ return new ConstantExpression(value);
+ }
+
+ public static ConstantExpression createFromOctal(String text)
+ {
+ Number value = new Long(Long.parseLong(text, 8));
+ long l = value.longValue();
+ if ((Integer.MIN_VALUE <= l) && (l <= Integer.MAX_VALUE))
+ {
+ value = new Integer(value.intValue());
+ }
+
+ return new ConstantExpression(value);
+ }
+
+ public static ConstantExpression createFloat(String text)
+ {
+ Number value = new Double(text);
+
+ return new ConstantExpression(value);
+ }
+
+ public ConstantExpression(Object value)
+ {
+ this.value = value;
+ }
+
+ public Object evaluate(Filterable<E> message) throws E
+ {
+ return value;
+ }
+
+ public Object getValue()
+ {
+ return value;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString()
+ {
+ if (value == null)
+ {
+ return "NULL";
+ }
+
+ if (value instanceof Boolean)
+ {
+ return ((Boolean) value).booleanValue() ? "TRUE" : "FALSE";
+ }
+
+ if (value instanceof String)
+ {
+ return encodeString((String) value);
+ }
+
+ return value.toString();
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode()
+ {
+ return toString().hashCode();
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o)
+ {
+
+ if ((o == null) || !this.getClass().equals(o.getClass()))
+ {
+ return false;
+ }
+
+ return toString().equals(o.toString());
+
+ }
+
+ /**
+ * Encodes the value of string so that it looks like it would look like
+ * when it was provided in a selector.
+ *
+ * @param s
+ * @return
+ */
+ public static String encodeString(String s)
+ {
+ StringBuffer b = new StringBuffer();
+ b.append('\'');
+ for (int i = 0; i < s.length(); i++)
+ {
+ char c = s.charAt(i);
+ if (c == '\'')
+ {
+ b.append(c);
+ }
+
+ b.append(c);
+ }
+
+ b.append('\'');
+
+ return b.toString();
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/Expression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/Expression.java
new file mode 100644
index 0000000000..f2ebe41d26
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/Expression.java
@@ -0,0 +1,38 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.server.filter;
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.Filterable;
+
+/**
+ * Represents an expression
+ */
+public interface Expression<E extends Exception>
+{
+
+ /**
+ * @return the value of this expression
+ */
+ public Object evaluate(Filterable<E> message) throws E;
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.java
new file mode 100644
index 0000000000..dd3c126ee5
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.filter;
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.Filterable;
+import org.apache.qpid.AMQException;
+
+public interface FilterManager<E extends Exception>
+{
+ void add(MessageFilter<E> filter);
+
+ void remove(MessageFilter<E> filter);
+
+ boolean allAllow(Filterable<E> msg);
+
+ boolean hasFilters();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java
new file mode 100644
index 0000000000..a7f49d0566
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.filter;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.common.AMQPFilterTypes;
+import org.apache.qpid.framing.FieldTable;
+
+
+public class FilterManagerFactory
+{
+ //private final static Logger _logger = LoggerFactory.getLogger(FilterManagerFactory.class);
+ private final static org.apache.log4j.Logger _logger = org.apache.log4j.Logger.getLogger(FilterManagerFactory.class);
+
+ //fixme move to a common class so it can be refered to from client code.
+
+ public static FilterManager createManager(FieldTable filters) throws AMQException
+ {
+ FilterManager manager = null;
+
+ if (filters != null)
+ {
+
+
+
+ if(filters.containsKey(AMQPFilterTypes.JMS_SELECTOR.getValue()))
+ {
+ String selector = filters.getString(AMQPFilterTypes.JMS_SELECTOR.getValue());
+
+ if (selector != null && !selector.equals(""))
+ {
+ manager = new SimpleFilterManager();
+ manager.add(new JMSSelectorFilter(selector));
+ }
+
+ }
+
+
+ }
+ else
+ {
+ _logger.debug("No Filters found.");
+ }
+
+
+ return manager;
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java
new file mode 100644
index 0000000000..96c9353872
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.filter;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.filter.jms.selector.SelectorParser;
+import org.apache.qpid.server.queue.Filterable;
+
+
+public class JMSSelectorFilter<E extends Exception> implements MessageFilter<E>
+{
+ private final static Logger _logger = org.apache.log4j.Logger.getLogger(JMSSelectorFilter.class);
+
+ private String _selector;
+ private BooleanExpression<E> _matcher;
+
+ public JMSSelectorFilter(String selector) throws AMQException
+ {
+ _selector = selector;
+ _matcher = new SelectorParser().parse(selector);
+ }
+
+ public boolean matches(Filterable<E> message) throws E
+ {
+ boolean match = _matcher.matches(message);
+ if(_logger.isDebugEnabled())
+ {
+ _logger.debug(message + " match(" + match + ") selector(" + System.identityHashCode(_selector) + "):" + _selector);
+ }
+ return match;
+ }
+
+ public String getSelector()
+ {
+ return _selector;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/LogicExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/LogicExpression.java
new file mode 100644
index 0000000000..094363ed9a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/LogicExpression.java
@@ -0,0 +1,122 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.server.filter;
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.Filterable;
+
+/**
+ * A filter performing a comparison of two objects
+ */
+public abstract class LogicExpression<E extends Exception> extends BinaryExpression<E> implements BooleanExpression<E>
+{
+
+ public static<E extends Exception> BooleanExpression createOR(BooleanExpression<E> lvalue, BooleanExpression<E> rvalue)
+ {
+ return new OrExpression(lvalue, rvalue);
+ }
+
+ public static<E extends Exception> BooleanExpression createAND(BooleanExpression<E> lvalue, BooleanExpression<E> rvalue)
+ {
+ return new AndExpression(lvalue, rvalue);
+ }
+
+ /**
+ * @param left
+ * @param right
+ */
+ public LogicExpression(BooleanExpression left, BooleanExpression right)
+ {
+ super(left, right);
+ }
+
+ public abstract Object evaluate(Filterable<E> message) throws E;
+
+ public boolean matches(Filterable<E> message) throws E
+ {
+ Object object = evaluate(message);
+
+ return (object != null) && (object == Boolean.TRUE);
+ }
+
+ private static class OrExpression<E extends Exception> extends LogicExpression<E>
+ {
+ public OrExpression(final BooleanExpression<E> lvalue, final BooleanExpression<E> rvalue)
+ {
+ super(lvalue, rvalue);
+ }
+
+ public Object evaluate(Filterable<E> message) throws E
+ {
+
+ Boolean lv = (Boolean) left.evaluate(message);
+ // Can we do an OR shortcut??
+ if ((lv != null) && lv.booleanValue())
+ {
+ return Boolean.TRUE;
+ }
+
+ Boolean rv = (Boolean) right.evaluate(message);
+
+ return (rv == null) ? null : rv;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "OR";
+ }
+ }
+
+ private static class AndExpression<E extends Exception> extends LogicExpression<E>
+ {
+ public AndExpression(final BooleanExpression<E> lvalue, final BooleanExpression<E> rvalue)
+ {
+ super(lvalue, rvalue);
+ }
+
+ public Object evaluate(Filterable<E> message) throws E
+ {
+
+ Boolean lv = (Boolean) left.evaluate(message);
+
+ // Can we do an AND shortcut??
+ if (lv == null)
+ {
+ return null;
+ }
+
+ if (!lv.booleanValue())
+ {
+ return Boolean.FALSE;
+ }
+
+ Boolean rv = (Boolean) right.evaluate(message);
+
+ return (rv == null) ? null : rv;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "AND";
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java
new file mode 100644
index 0000000000..58fc55f8e6
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.filter;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.Filterable;
+
+public interface MessageFilter<E extends Exception>
+{
+ boolean matches(Filterable<E> message) throws E;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java
new file mode 100644
index 0000000000..f1b3b2511d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.filter;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.Filterable;
+
+public class NoConsumerFilter implements MessageFilter
+{
+ private final static Logger _logger = org.apache.log4j.Logger.getLogger(NoConsumerFilter.class);
+
+
+ public NoConsumerFilter() throws AMQException
+ {
+ _logger.info("Created NoConsumerFilter");
+ }
+
+ public boolean matches(Filterable message)
+ {
+ return true;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/PropertyExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/PropertyExpression.java
new file mode 100644
index 0000000000..b30c70dac3
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/PropertyExpression.java
@@ -0,0 +1,268 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.filter;
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+import java.util.HashMap;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.CommonContentHeaderProperties;
+import org.apache.qpid.server.queue.Filterable;
+
+/**
+ * Represents a property expression
+ */
+public class PropertyExpression<E extends Exception> implements Expression<E>
+{
+ // Constants - defined the same as JMS
+ private static final int NON_PERSISTENT = 1;
+ private static final int PERSISTENT = 2;
+ private static final int DEFAULT_PRIORITY = 4;
+
+ private static final Logger _logger = org.apache.log4j.Logger.getLogger(PropertyExpression.class);
+
+ private static final HashMap<String, Expression<? extends Exception>> JMS_PROPERTY_EXPRESSIONS = new HashMap<String, Expression<? extends Exception>>();
+
+ {
+ JMS_PROPERTY_EXPRESSIONS.put("JMSDestination", new Expression<E>()
+ {
+ public Object evaluate(Filterable<E> message)
+ {
+ //TODO
+ return null;
+ }
+ });
+ JMS_PROPERTY_EXPRESSIONS.put("JMSReplyTo", new ReplyToExpression());
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSType", new TypeExpression());
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSDeliveryMode", new DeliveryModeExpression());
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSPriority", new PriorityExpression());
+
+ JMS_PROPERTY_EXPRESSIONS.put("AMQMessageID", new MessageIDExpression());
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSTimestamp", new TimestampExpression());
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSCorrelationID", new CorrelationIdExpression());
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSExpiration", new ExpirationExpression());
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSRedelivered", new Expression<E>()
+ {
+ public Object evaluate(Filterable message) throws E
+ {
+ return message.isRedelivered();
+ }
+ });
+ }
+
+ private final String name;
+ private final Expression<E> jmsPropertyExpression;
+
+ public boolean outerTest()
+ {
+ return false;
+ }
+
+ public PropertyExpression(String name)
+ {
+ this.name = name;
+
+
+
+ jmsPropertyExpression = (Expression<E>) JMS_PROPERTY_EXPRESSIONS.get(name);
+ }
+
+ public Object evaluate(Filterable<E> message) throws E
+ {
+
+ if (jmsPropertyExpression != null)
+ {
+ return jmsPropertyExpression.evaluate(message);
+ }
+ else
+ {
+
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties) message.getContentHeaderBody().properties;
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Looking up property:" + name);
+ _logger.debug("Properties are:" + _properties.getHeaders().keySet());
+ }
+
+ return _properties.getHeaders().getObject(name);
+ }
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString()
+ {
+ return name;
+ }
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode()
+ {
+ return name.hashCode();
+ }
+
+ /**
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o)
+ {
+
+ if ((o == null) || !this.getClass().equals(o.getClass()))
+ {
+ return false;
+ }
+
+ return name.equals(((PropertyExpression) o).name);
+
+ }
+
+ private static class ReplyToExpression<E extends Exception> implements Expression<E>
+ {
+ public Object evaluate(Filterable<E> message) throws E
+ {
+
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+ AMQShortString replyTo = _properties.getReplyTo();
+
+ return (replyTo == null) ? null : replyTo.toString();
+
+ }
+
+ }
+
+ private static class TypeExpression<E extends Exception> implements Expression<E>
+ {
+ public Object evaluate(Filterable<E> message) throws E
+ {
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+ AMQShortString type = _properties.getType();
+
+ return (type == null) ? null : type.toString();
+
+ }
+ }
+
+ private static class DeliveryModeExpression<E extends Exception> implements Expression<E>
+ {
+ public Object evaluate(Filterable<E> message) throws E
+ {
+ int mode = message.isPersistent() ? PERSISTENT : NON_PERSISTENT;
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("JMSDeliveryMode is :" + mode);
+ }
+
+ return mode;
+ }
+ }
+
+ private static class PriorityExpression<E extends Exception> implements Expression<E>
+ {
+ public Object evaluate(Filterable<E> message) throws E
+ {
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+
+ return (int) _properties.getPriority();
+ }
+ }
+
+ private static class MessageIDExpression<E extends Exception> implements Expression<E>
+ {
+ public Object evaluate(Filterable<E> message) throws E
+ {
+
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+ AMQShortString messageId = _properties.getMessageId();
+
+ return (messageId == null) ? null : messageId;
+
+ }
+ }
+
+ private static class TimestampExpression<E extends Exception> implements Expression<E>
+ {
+ public Object evaluate(Filterable<E> message) throws E
+ {
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+
+ return _properties.getTimestamp();
+ }
+ }
+
+ private static class CorrelationIdExpression<E extends Exception> implements Expression<E>
+ {
+ public Object evaluate(Filterable<E> message) throws E
+ {
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+ AMQShortString correlationId = _properties.getCorrelationId();
+
+ return (correlationId == null) ? null : correlationId.toString();
+ }
+ }
+
+ private static class ExpirationExpression<E extends Exception> implements Expression<E>
+ {
+ public Object evaluate(Filterable<E> message) throws E
+ {
+
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+
+ return _properties.getExpiration();
+
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java
new file mode 100644
index 0000000000..cb738e1489
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.filter;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.Filterable;
+
+public class SimpleFilterManager implements FilterManager<AMQException>
+{
+ private final Logger _logger = Logger.getLogger(SimpleFilterManager.class);
+
+ private final ConcurrentLinkedQueue<MessageFilter<AMQException>> _filters;
+
+ public SimpleFilterManager()
+ {
+ _logger.debug("Creating SimpleFilterManager");
+ _filters = new ConcurrentLinkedQueue<MessageFilter<AMQException>>();
+ }
+
+ public void add(MessageFilter<AMQException> filter)
+ {
+ _filters.add(filter);
+ }
+
+ public void remove(MessageFilter<AMQException> filter)
+ {
+ _filters.remove(filter);
+ }
+
+ public boolean allAllow(Filterable<AMQException> msg)
+ {
+ for (MessageFilter<AMQException> filter : _filters)
+ {
+ try
+ {
+ if (!filter.matches(msg))
+ {
+ return false;
+ }
+ }
+ catch (AMQException e)
+ {
+ //fixme
+ e.printStackTrace();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean hasFilters()
+ {
+ return !_filters.isEmpty();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/UnaryExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/UnaryExpression.java
new file mode 100644
index 0000000000..799a38af5a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/UnaryExpression.java
@@ -0,0 +1,369 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.filter;
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.Filterable;
+
+/**
+ * An expression which performs an operation on two expression values
+ */
+public abstract class UnaryExpression<E extends Exception> implements Expression<E>
+{
+
+ private static final BigDecimal BD_LONG_MIN_VALUE = BigDecimal.valueOf(Long.MIN_VALUE);
+ protected Expression<E> right;
+
+ public static<E extends Exception> Expression<E> createNegate(Expression<E> left)
+ {
+ return new NegativeExpression(left);
+ }
+
+ public static<E extends Exception> BooleanExpression createInExpression(PropertyExpression<E> right, List elements, final boolean not)
+ {
+
+ // Use a HashSet if there are many elements.
+ Collection t;
+ if (elements.size() == 0)
+ {
+ t = null;
+ }
+ else if (elements.size() < 5)
+ {
+ t = elements;
+ }
+ else
+ {
+ t = new HashSet(elements);
+ }
+
+ final Collection inList = t;
+
+ return new InExpression(right, inList, not);
+ }
+
+ abstract static class BooleanUnaryExpression<E extends Exception> extends UnaryExpression<E> implements BooleanExpression<E>
+ {
+ public BooleanUnaryExpression(Expression<E> left)
+ {
+ super(left);
+ }
+
+ public boolean matches(Filterable<E> message) throws E
+ {
+ Object object = evaluate(message);
+
+ return (object != null) && (object == Boolean.TRUE);
+ }
+ }
+ ;
+
+ public static<E extends Exception> BooleanExpression<E> createNOT(BooleanExpression<E> left)
+ {
+ return new NotExpression(left);
+ }
+
+ public static BooleanExpression createXPath(final String xpath)
+ {
+ return new XPathExpression(xpath);
+ }
+
+ public static BooleanExpression createXQuery(final String xpath)
+ {
+ return new XQueryExpression(xpath);
+ }
+
+ public static<E extends Exception> BooleanExpression createBooleanCast(Expression<E> left)
+ {
+ return new BooleanCastExpression(left);
+ }
+
+ private static Number negate(Number left)
+ {
+ Class clazz = left.getClass();
+ if (clazz == Integer.class)
+ {
+ return new Integer(-left.intValue());
+ }
+ else if (clazz == Long.class)
+ {
+ return new Long(-left.longValue());
+ }
+ else if (clazz == Float.class)
+ {
+ return new Float(-left.floatValue());
+ }
+ else if (clazz == Double.class)
+ {
+ return new Double(-left.doubleValue());
+ }
+ else if (clazz == BigDecimal.class)
+ {
+ // We ussually get a big deciamal when we have Long.MIN_VALUE constant in the
+ // Selector. Long.MIN_VALUE is too big to store in a Long as a positive so we store it
+ // as a Big decimal. But it gets Negated right away.. to here we try to covert it back
+ // to a Long.
+ BigDecimal bd = (BigDecimal) left;
+ bd = bd.negate();
+
+ if (BD_LONG_MIN_VALUE.compareTo(bd) == 0)
+ {
+ return new Long(Long.MIN_VALUE);
+ }
+
+ return bd;
+ }
+ else
+ {
+ throw new RuntimeException("Don't know how to negate: " + left);
+ }
+ }
+
+ public UnaryExpression(Expression left)
+ {
+ this.right = left;
+ }
+
+ public Expression<E> getRight()
+ {
+ return right;
+ }
+
+ public void setRight(Expression expression)
+ {
+ right = expression;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString()
+ {
+ return "(" + getExpressionSymbol() + " " + right.toString() + ")";
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode()
+ {
+ return toString().hashCode();
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o)
+ {
+
+ if ((o == null) || !this.getClass().equals(o.getClass()))
+ {
+ return false;
+ }
+
+ return toString().equals(o.toString());
+
+ }
+
+ /**
+ * Returns the symbol that represents this binary expression. For example, addition is
+ * represented by "+"
+ *
+ * @return
+ */
+ public abstract String getExpressionSymbol();
+
+ private static class NegativeExpression<E extends Exception> extends UnaryExpression<E>
+ {
+ public NegativeExpression(final Expression<E> left)
+ {
+ super(left);
+ }
+
+ public Object evaluate(Filterable<E> message) throws E
+ {
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null)
+ {
+ return null;
+ }
+
+ if (rvalue instanceof Number)
+ {
+ return negate((Number) rvalue);
+ }
+
+ return null;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "-";
+ }
+ }
+
+ private static class InExpression<E extends Exception> extends BooleanUnaryExpression<E>
+ {
+ private final Collection _inList;
+ private final boolean _not;
+
+ public InExpression(final PropertyExpression<E> right, final Collection inList, final boolean not)
+ {
+ super(right);
+ _inList = inList;
+ _not = not;
+ }
+
+ public Object evaluate(Filterable<E> message) throws E
+ {
+
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null)
+ {
+ return null;
+ }
+
+ if (rvalue.getClass() != String.class)
+ {
+ return null;
+ }
+
+ if (((_inList != null) && _inList.contains(rvalue)) ^ _not)
+ {
+ return Boolean.TRUE;
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+
+ }
+
+ public String toString()
+ {
+ StringBuffer answer = new StringBuffer();
+ answer.append(right);
+ answer.append(" ");
+ answer.append(getExpressionSymbol());
+ answer.append(" ( ");
+
+ int count = 0;
+ for (Iterator i = _inList.iterator(); i.hasNext();)
+ {
+ Object o = (Object) i.next();
+ if (count != 0)
+ {
+ answer.append(", ");
+ }
+
+ answer.append(o);
+ count++;
+ }
+
+ answer.append(" )");
+
+ return answer.toString();
+ }
+
+ public String getExpressionSymbol()
+ {
+ if (_not)
+ {
+ return "NOT IN";
+ }
+ else
+ {
+ return "IN";
+ }
+ }
+ }
+
+ private static class NotExpression<E extends Exception> extends BooleanUnaryExpression<E>
+ {
+ public NotExpression(final BooleanExpression<E> left)
+ {
+ super(left);
+ }
+
+ public Object evaluate(Filterable<E> message) throws E
+ {
+ Boolean lvalue = (Boolean) right.evaluate(message);
+ if (lvalue == null)
+ {
+ return null;
+ }
+
+ return lvalue.booleanValue() ? Boolean.FALSE : Boolean.TRUE;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "NOT";
+ }
+ }
+
+ private static class BooleanCastExpression<E extends Exception> extends BooleanUnaryExpression<E>
+ {
+ public BooleanCastExpression(final Expression<E> left)
+ {
+ super(left);
+ }
+
+ public Object evaluate(Filterable<E> message) throws E
+ {
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null)
+ {
+ return null;
+ }
+
+ if (!rvalue.getClass().equals(Boolean.class))
+ {
+ return Boolean.FALSE;
+ }
+
+ return ((Boolean) rvalue).booleanValue() ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ public String toString()
+ {
+ return right.toString();
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "";
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XPathExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XPathExpression.java
new file mode 100644
index 0000000000..1311178fb1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XPathExpression.java
@@ -0,0 +1,127 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.server.filter;
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.Filterable;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Used to evaluate an XPath Expression in a JMS selector.
+ */
+public final class XPathExpression implements BooleanExpression {
+
+ private static final Logger log = Logger.getLogger(XPathExpression.class);
+ private static final String EVALUATOR_SYSTEM_PROPERTY = "org.apache.qpid.server.filter.XPathEvaluatorClassName";
+ private static final String DEFAULT_EVALUATOR_CLASS_NAME=XalanXPathEvaluator.class.getName();
+
+ private static final Constructor EVALUATOR_CONSTRUCTOR;
+
+ static {
+ String cn = System.getProperty(EVALUATOR_SYSTEM_PROPERTY, DEFAULT_EVALUATOR_CLASS_NAME);
+ Constructor m = null;
+ try {
+ try {
+ m = getXPathEvaluatorConstructor(cn);
+ } catch (Throwable e) {
+ log.warn("Invalid "+XPathEvaluator.class.getName()+" implementation: "+cn+", reason: "+e,e);
+ cn = DEFAULT_EVALUATOR_CLASS_NAME;
+ try {
+ m = getXPathEvaluatorConstructor(cn);
+ } catch (Throwable e2) {
+ log.error("Default XPath evaluator could not be loaded",e);
+ }
+ }
+ } finally {
+ EVALUATOR_CONSTRUCTOR = m;
+ }
+ }
+
+ private static Constructor getXPathEvaluatorConstructor(String cn) throws ClassNotFoundException, SecurityException, NoSuchMethodException {
+ Class c = XPathExpression.class.getClassLoader().loadClass(cn);
+ if( !XPathEvaluator.class.isAssignableFrom(c) ) {
+ throw new ClassCastException(""+c+" is not an instance of "+XPathEvaluator.class);
+ }
+ return c.getConstructor(new Class[]{String.class});
+ }
+
+ private final String xpath;
+ private final XPathEvaluator evaluator;
+
+ static public interface XPathEvaluator {
+ public boolean evaluate(Filterable message) throws AMQException;
+ }
+
+ XPathExpression(String xpath) {
+ this.xpath = xpath;
+ this.evaluator = createEvaluator(xpath);
+ }
+
+ private XPathEvaluator createEvaluator(String xpath2) {
+ try {
+ return (XPathEvaluator)EVALUATOR_CONSTRUCTOR.newInstance(new Object[]{xpath});
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if( cause instanceof RuntimeException ) {
+ throw (RuntimeException)cause;
+ }
+ throw new RuntimeException("Invalid XPath Expression: "+xpath+" reason: "+e.getMessage(), e);
+ } catch (Throwable e) {
+ throw new RuntimeException("Invalid XPath Expression: "+xpath+" reason: "+e.getMessage(), e);
+ }
+ }
+
+ public Object evaluate(Filterable message) throws AMQException {
+// try {
+//FIXME this is flow to disk work
+// if( message.isDropped() )
+// return null;
+ return evaluator.evaluate(message) ? Boolean.TRUE : Boolean.FALSE;
+// } catch (IOException e) {
+//
+// JMSException exception = new JMSException(e.getMessage());
+// exception.initCause(e);
+// throw exception;
+//
+// }
+
+ }
+
+ public String toString() {
+ return "XPATH "+ConstantExpression.encodeString(xpath);
+ }
+
+ /**
+ * @param message
+ * @return true if the expression evaluates to Boolean.TRUE.
+ * @throws AMQException
+ */
+ public boolean matches(Filterable message) throws AMQException
+ {
+ Object object = evaluate(message);
+ return object!=null && object==Boolean.TRUE;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XQueryExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XQueryExpression.java
new file mode 100644
index 0000000000..c13f81cd08
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XQueryExpression.java
@@ -0,0 +1,58 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.server.filter;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.Filterable;
+
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+/**
+ * Used to evaluate an XQuery Expression in a JMS selector.
+ */
+public final class XQueryExpression implements BooleanExpression {
+ private final String xpath;
+
+ XQueryExpression(String xpath) {
+ super();
+ this.xpath = xpath;
+ }
+
+ public Object evaluate(Filterable message) throws AMQException {
+ return Boolean.FALSE;
+ }
+
+ public String toString() {
+ return "XQUERY "+ConstantExpression.encodeString(xpath);
+ }
+
+ /**
+ * @param message
+ * @return true if the expression evaluates to Boolean.TRUE.
+ * @throws AMQException
+ */
+ public boolean matches(Filterable message) throws AMQException
+ {
+ Object object = evaluate(message);
+ return object!=null && object==Boolean.TRUE;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XalanXPathEvaluator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XalanXPathEvaluator.java
new file mode 100644
index 0000000000..cc67776682
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XalanXPathEvaluator.java
@@ -0,0 +1,103 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.qpid.server.filter;
+//
+// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+//
+
+import java.io.ByteArrayInputStream;
+import java.io.StringReader;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.Filterable;
+import org.apache.xpath.CachedXPathAPI;
+import org.w3c.dom.Document;
+import org.w3c.dom.traversal.NodeIterator;
+import org.xml.sax.InputSource;
+
+public class XalanXPathEvaluator implements XPathExpression.XPathEvaluator {
+
+ private final String xpath;
+
+ public XalanXPathEvaluator(String xpath) {
+ this.xpath = xpath;
+ }
+
+ public boolean evaluate(Filterable m) throws AMQException
+ {
+ // TODO - we would have to check the content type and then evaluate the content
+ // here... is this really a feature we wish to implement? - RobG
+ /*
+
+ if( m instanceof TextMessage ) {
+ String text = ((TextMessage)m).getText();
+ return evaluate(text);
+ } else if ( m instanceof BytesMessage ) {
+ BytesMessage bm = (BytesMessage) m;
+ byte data[] = new byte[(int) bm.getBodyLength()];
+ bm.readBytes(data);
+ return evaluate(data);
+ }
+ */
+ return false;
+
+ }
+
+ private boolean evaluate(byte[] data) {
+ try {
+
+ InputSource inputSource = new InputSource(new ByteArrayInputStream(data));
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ DocumentBuilder dbuilder = factory.newDocumentBuilder();
+ Document doc = dbuilder.parse(inputSource);
+
+ CachedXPathAPI cachedXPathAPI = new CachedXPathAPI();
+ NodeIterator iterator = cachedXPathAPI.selectNodeIterator(doc,xpath);
+ return iterator.nextNode()!=null;
+
+ } catch (Throwable e) {
+ return false;
+ }
+ }
+
+ private boolean evaluate(String text) {
+ try {
+ InputSource inputSource = new InputSource(new StringReader(text));
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ DocumentBuilder dbuilder = factory.newDocumentBuilder();
+ Document doc = dbuilder.parse(inputSource);
+
+ // We should associated the cachedXPathAPI object with the message being evaluated
+ // since that should speedup subsequent xpath expressions.
+ CachedXPathAPI cachedXPathAPI = new CachedXPathAPI();
+ NodeIterator iterator = cachedXPathAPI.selectNodeIterator(doc,xpath);
+ return iterator.nextNode()!=null;
+ } catch (Throwable e) {
+ return false;
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java
new file mode 100644
index 0000000000..895db7b15b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java
@@ -0,0 +1,62 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.flow;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.Set;
+import java.util.HashSet;
+
+public abstract class AbstractFlowCreditManager implements FlowCreditManager
+{
+ protected final AtomicBoolean _suspended = new AtomicBoolean(false);
+ private final Set<FlowCreditManagerListener> _listeners = new HashSet<FlowCreditManagerListener>();
+
+ public final void addStateListener(FlowCreditManagerListener listener)
+ {
+ _listeners.add(listener);
+ }
+
+ public final boolean removeListener(FlowCreditManagerListener listener)
+ {
+ return _listeners.remove(listener);
+ }
+
+ private void notifyListeners(final boolean suspended)
+ {
+ for(FlowCreditManagerListener listener : _listeners)
+ {
+ listener.creditStateChanged(!suspended);
+ }
+ }
+
+ protected final void setSuspended(final boolean suspended)
+ {
+ if(_suspended.compareAndSet(!suspended, suspended))
+ {
+ notifyListeners(suspended);
+ }
+ }
+
+ protected final void notifyIncreaseBytesCredit()
+ {
+ notifyListeners(false);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/BytesOnlyCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/BytesOnlyCreditManager.java
new file mode 100644
index 0000000000..96a1071135
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/BytesOnlyCreditManager.java
@@ -0,0 +1,77 @@
+package org.apache.qpid.server.flow;
+
+import org.apache.qpid.server.queue.AMQMessage;
+
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.Set;
+import java.util.HashSet;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class BytesOnlyCreditManager extends AbstractFlowCreditManager
+{
+ private final AtomicLong _bytesCredit;
+
+ public BytesOnlyCreditManager(long initialCredit)
+ {
+ _bytesCredit = new AtomicLong(initialCredit);
+ }
+
+ public void addCredit(long messageCredit, long bytesCredit)
+ {
+ _bytesCredit.addAndGet(bytesCredit);
+ setSuspended(false);
+ }
+
+ public void removeAllCredit()
+ {
+ _bytesCredit.set(0L);
+ }
+
+ public boolean hasCredit()
+ {
+ return _bytesCredit.get() > 0L;
+ }
+
+ public boolean useCreditForMessage(AMQMessage msg)
+ {
+ final long msgSize = msg.getSize();
+ if(hasCredit())
+ {
+ if(_bytesCredit.addAndGet(-msgSize) >= 0)
+ {
+ return true;
+ }
+ else
+ {
+ _bytesCredit.addAndGet(msgSize);
+ setSuspended(true);
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java
new file mode 100644
index 0000000000..a249a6e63a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java
@@ -0,0 +1,44 @@
+package org.apache.qpid.server.flow;
+
+import org.apache.qpid.server.queue.AMQMessage;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public interface FlowCreditManager
+{
+
+ public static interface FlowCreditManagerListener
+ {
+ void creditStateChanged(boolean hasCredit);
+ }
+
+ void addStateListener(FlowCreditManagerListener listener);
+
+ boolean removeListener(FlowCreditManagerListener listener);
+
+ public void addCredit(long messageCredit, long bytesCredit);
+
+ public void removeAllCredit();
+
+ public boolean hasCredit();
+
+ public boolean useCreditForMessage(AMQMessage msg);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java
new file mode 100644
index 0000000000..d63431c3eb
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java
@@ -0,0 +1,44 @@
+package org.apache.qpid.server.flow;
+
+import org.apache.qpid.server.queue.AMQMessage;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class LimitlessCreditManager extends AbstractFlowCreditManager implements FlowCreditManager
+{
+ public void addCredit(long messageCredit, long bytesCredit)
+ {
+ }
+
+ public void removeAllCredit()
+ {
+ }
+
+ public boolean hasCredit()
+ {
+ return true;
+ }
+
+ public boolean useCreditForMessage(AMQMessage msg)
+ {
+ return true;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java
new file mode 100644
index 0000000000..9c377481de
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java
@@ -0,0 +1,79 @@
+package org.apache.qpid.server.flow;
+
+import org.apache.qpid.server.queue.AMQMessage;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class MessageAndBytesCreditManager extends AbstractFlowCreditManager implements FlowCreditManager
+{
+ private long _messageCredit;
+ private long _bytesCredit;
+
+ MessageAndBytesCreditManager(final long messageCredit, final long bytesCredit)
+ {
+ _messageCredit = messageCredit;
+ _bytesCredit = bytesCredit;
+ }
+
+ public synchronized void addCredit(long messageCredit, long bytesCredit)
+ {
+ _messageCredit += messageCredit;
+ _bytesCredit += bytesCredit;
+ setSuspended(hasCredit());
+ }
+
+ public synchronized void removeAllCredit()
+ {
+ _messageCredit = 0L;
+ _bytesCredit = 0L;
+ setSuspended(true);
+ }
+
+ public synchronized boolean hasCredit()
+ {
+ return (_messageCredit > 0L) && ( _bytesCredit > 0L );
+ }
+
+ public synchronized boolean useCreditForMessage(AMQMessage msg)
+ {
+ if(_messageCredit == 0L)
+ {
+ setSuspended(true);
+ return false;
+ }
+ else
+ {
+ final long msgSize = msg.getSize();
+ if(msgSize > _bytesCredit)
+ {
+ setSuspended(true);
+ return false;
+ }
+ _messageCredit--;
+ _bytesCredit -= msgSize;
+ setSuspended(false);
+ return true;
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java
new file mode 100644
index 0000000000..c1b3a09006
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java
@@ -0,0 +1,76 @@
+package org.apache.qpid.server.flow;
+
+import org.apache.qpid.server.queue.AMQMessage;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class MessageOnlyCreditManager extends AbstractFlowCreditManager implements FlowCreditManager
+{
+ private final AtomicLong _messageCredit;
+
+ public MessageOnlyCreditManager(final long initialCredit)
+ {
+ _messageCredit = new AtomicLong(initialCredit);
+ }
+
+ public void addCredit(long messageCredit, long bytesCredit)
+ {
+ setSuspended(false);
+ _messageCredit.addAndGet(messageCredit);
+ }
+
+ public void removeAllCredit()
+ {
+ setSuspended(true);
+ _messageCredit.set(0L);
+ }
+
+ public boolean hasCredit()
+ {
+ return _messageCredit.get() > 0L;
+ }
+
+ public boolean useCreditForMessage(AMQMessage msg)
+ {
+ if(hasCredit())
+ {
+ if(_messageCredit.addAndGet(-1L) >= 0)
+ {
+ setSuspended(false);
+ return true;
+ }
+ else
+ {
+ _messageCredit.addAndGet(1L);
+ setSuspended(true);
+ return false;
+ }
+ }
+ else
+ {
+ setSuspended(true);
+ return false;
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java
new file mode 100644
index 0000000000..be0300f2c1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java
@@ -0,0 +1,185 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.flow;
+
+import org.apache.qpid.server.queue.AMQMessage;
+
+public class Pre0_10CreditManager extends AbstractFlowCreditManager implements FlowCreditManager
+{
+
+ private volatile long _bytesCreditLimit;
+ private volatile long _messageCreditLimit;
+
+ private volatile long _bytesCredit;
+ private volatile long _messageCredit;
+
+ public Pre0_10CreditManager(long bytesCreditLimit, long messageCreditLimit)
+ {
+ _bytesCreditLimit = bytesCreditLimit;
+ _messageCreditLimit = messageCreditLimit;
+ _bytesCredit = bytesCreditLimit;
+ _messageCredit = messageCreditLimit;
+ }
+
+
+ public synchronized void setCreditLimits(final long bytesCreditLimit, final long messageCreditLimit)
+ {
+ long bytesCreditChange = bytesCreditLimit - _bytesCreditLimit;
+ long messageCreditChange = messageCreditLimit - _messageCreditLimit;
+
+
+
+ if(bytesCreditChange != 0L)
+ {
+ if(bytesCreditLimit == 0L)
+ {
+ _bytesCredit = 0;
+ }
+ else
+ {
+ _bytesCredit += bytesCreditChange;
+ }
+ }
+
+
+ if(messageCreditChange != 0L)
+ {
+ if(messageCreditLimit == 0L)
+ {
+ _messageCredit = 0;
+ }
+ else
+ {
+ _messageCredit += messageCreditChange;
+ }
+ }
+
+
+ _bytesCreditLimit = bytesCreditLimit;
+ _messageCreditLimit = messageCreditLimit;
+
+ setSuspended(!hasCredit());
+
+ }
+
+
+ public synchronized void addCredit(final long messageCredit, final long bytesCredit)
+ {
+ final long messageCreditLimit = _messageCreditLimit;
+ boolean notifyIncrease = true;
+ if(messageCreditLimit != 0L)
+ {
+ notifyIncrease = (_messageCredit != 0);
+ long newCredit = _messageCredit + messageCredit;
+ _messageCredit = newCredit > messageCreditLimit ? messageCreditLimit : newCredit;
+ }
+
+
+ final long bytesCreditLimit = _bytesCreditLimit;
+ if(bytesCreditLimit != 0L)
+ {
+ long newCredit = _bytesCredit + bytesCredit;
+ _bytesCredit = newCredit > bytesCreditLimit ? bytesCreditLimit : newCredit;
+ if(notifyIncrease && bytesCredit>0)
+ {
+ notifyIncreaseBytesCredit();
+ }
+ }
+
+
+
+ setSuspended(!hasCredit());
+
+ }
+
+ public synchronized void removeAllCredit()
+ {
+ _bytesCredit = 0L;
+ _messageCredit = 0L;
+ setSuspended(!hasCredit());
+ }
+
+ public synchronized boolean hasCredit()
+ {
+ return (_bytesCreditLimit == 0L || _bytesCredit > 0)
+ && (_messageCreditLimit == 0L || _messageCredit > 0);
+ }
+
+ public synchronized boolean useCreditForMessage(final AMQMessage msg)
+ {
+ if(_messageCreditLimit != 0L)
+ {
+ if(_messageCredit != 0L)
+ {
+ if(_bytesCreditLimit == 0L)
+ {
+ _messageCredit--;
+
+ return true;
+ }
+ else
+ {
+ if((_bytesCredit >= msg.getSize()) || (_bytesCredit == _bytesCreditLimit))
+ {
+ _messageCredit--;
+ _bytesCredit -= msg.getSize();
+
+ return true;
+ }
+ else
+ {
+ //setSuspended(true);
+ return false;
+ }
+ }
+ }
+ else
+ {
+ setSuspended(true);
+ return false;
+ }
+ }
+ else
+ {
+ if(_bytesCreditLimit == 0L)
+ {
+
+ return true;
+ }
+ else
+ {
+ if((_bytesCredit >= msg.getSize()) || (_bytesCredit == _bytesCreditLimit))
+ {
+ _bytesCredit -= msg.getSize();
+
+ return true;
+ }
+ else
+ {
+ //setSuspended(true);
+ return false;
+ }
+ }
+
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java
new file mode 100644
index 0000000000..e64eaeae76
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java
@@ -0,0 +1,61 @@
+package org.apache.qpid.server.handler;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.AMQException;
+
+/**
+ * @author Apache Software Foundation
+ *
+ *
+ */
+public class AccessRequestHandler implements StateAwareMethodListener<AccessRequestBody>
+{
+ private static final AccessRequestHandler _instance = new AccessRequestHandler();
+
+
+ public static AccessRequestHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private AccessRequestHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AccessRequestBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+
+ // We don't implement access control class, but to keep clients happy that expect it
+ // always use the "0" ticket.
+ AccessRequestOkBody response = methodRegistry.createAccessRequestOkBody(0);
+
+ session.writeFrame(response.generateFrame(channelId));
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java
new file mode 100644
index 0000000000..f90e7c3dff
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java
@@ -0,0 +1,67 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicAckBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+public class BasicAckMethodHandler implements StateAwareMethodListener<BasicAckBody>
+{
+ private static final Logger _log = Logger.getLogger(BasicAckMethodHandler.class);
+
+ private static final BasicAckMethodHandler _instance = new BasicAckMethodHandler();
+
+ public static BasicAckMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private BasicAckMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, BasicAckBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession protocolSession = stateManager.getProtocolSession();
+
+
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Ack(Tag:" + body.getDeliveryTag() + ":Mult:" + body.getMultiple() + ") received on channel " + channelId);
+ }
+
+ final AMQChannel channel = protocolSession.getChannel(channelId);
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ // this method throws an AMQException if the delivery tag is not known
+ channel.acknowledgeMessage(body.getDeliveryTag(), body.getMultiple());
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java
new file mode 100644
index 0000000000..29054f55c1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java
@@ -0,0 +1,74 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicCancelBody;
+import org.apache.qpid.framing.BasicCancelOkBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.log4j.Logger;
+
+public class BasicCancelMethodHandler implements StateAwareMethodListener<BasicCancelBody>
+{
+ private static final Logger _log = Logger.getLogger(BasicCancelMethodHandler.class);
+
+ private static final BasicCancelMethodHandler _instance = new BasicCancelMethodHandler();
+
+ public static BasicCancelMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private BasicCancelMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, BasicCancelBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ final AMQChannel channel = session.getChannel(channelId);
+
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("BasicCancel: for:" + body.getConsumerTag() +
+ " nowait:" + body.getNowait());
+ }
+
+ channel.unsubscribeConsumer(body.getConsumerTag());
+ if (!body.getNowait())
+ {
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ BasicCancelOkBody cancelOkBody = methodRegistry.createBasicCancelOkBody(body.getConsumerTag());
+ session.writeFrame(cancelOkBody.generateFrame(channelId));
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java
new file mode 100644
index 0000000000..08610f24cd
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java
@@ -0,0 +1,173 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.ConsumerTagNotUniqueException;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class BasicConsumeMethodHandler implements StateAwareMethodListener<BasicConsumeBody>
+{
+ private static final Logger _logger = Logger.getLogger(BasicConsumeMethodHandler.class);
+
+ private static final BasicConsumeMethodHandler _instance = new BasicConsumeMethodHandler();
+
+ public static BasicConsumeMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private BasicConsumeMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, BasicConsumeBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+
+
+
+ AMQChannel channel = session.getChannel(channelId);
+
+ VirtualHost vHost = session.getVirtualHost();
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+ else
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("BasicConsume: from '" + body.getQueue() +
+ "' for:" + body.getConsumerTag() +
+ " nowait:" + body.getNowait() +
+ " args:" + body.getArguments());
+ }
+
+ AMQQueue queue = body.getQueue() == null ? channel.getDefaultQueue() : vHost.getQueueRegistry().getQueue(body.getQueue().intern());
+
+ if (queue == null)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("No queue for '" + body.getQueue() + "'");
+ }
+ if (body.getQueue() != null)
+ {
+ String msg = "No such queue, '" + body.getQueue() + "'";
+ throw body.getChannelException(AMQConstant.NOT_FOUND, msg);
+ }
+ else
+ {
+ String msg = "No queue name provided, no default queue defined.";
+ throw body.getConnectionException(AMQConstant.NOT_ALLOWED, msg);
+ }
+ }
+ else
+ {
+
+ final AMQShortString consumerTagName;
+
+ // Check authz
+ if (!vHost.getAccessManager().authoriseConsume(session,
+ body.getExclusive(), body.getNoAck(),
+ body.getNoLocal(), body.getNowait(), queue))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+
+ if (body.getConsumerTag() != null)
+ {
+ consumerTagName = body.getConsumerTag().intern();
+ }
+ else
+ {
+ consumerTagName = null;
+ }
+
+ try
+ {
+ AMQShortString consumerTag = channel.subscribeToQueue(consumerTagName, queue, !body.getNoAck(),
+ body.getArguments(), body.getNoLocal(), body.getExclusive());
+ if (!body.getNowait())
+ {
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createBasicConsumeOkBody(consumerTag);
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+ }
+
+
+ }
+ catch (org.apache.qpid.AMQInvalidArgumentException ise)
+ {
+ _logger.debug("Closing connection due to invalid selector");
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createChannelCloseBody(AMQConstant.INVALID_ARGUMENT.getCode(),
+ new AMQShortString(ise.getMessage()),
+ body.getClazz(),
+ body.getMethod());
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+
+ }
+ catch (ConsumerTagNotUniqueException e)
+ {
+ AMQShortString msg = new AMQShortString("Non-unique consumer tag, '" + body.getConsumerTag() + "'");
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createConnectionCloseBody(AMQConstant.NOT_ALLOWED.getCode(), // replyCode
+ msg, // replytext
+ body.getClazz(),
+ body.getMethod());
+ session.writeFrame(responseBody.generateFrame(0));
+ }
+ catch (AMQQueue.ExistingExclusiveSubscription e)
+ {
+ throw body.getChannelException(AMQConstant.ACCESS_REFUSED,
+ "Cannot subscribe to queue "
+ + queue.getName()
+ + " as it already has an existing exclusive consumer");
+ }
+ catch (AMQQueue.ExistingSubscriptionPreventsExclusive e)
+ {
+ throw body.getChannelException(AMQConstant.ACCESS_REFUSED,
+ "Cannot subscribe to queue "
+ + queue.getName()
+ + " exclusively as it already has a consumer");
+ }
+
+ }
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicGetMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicGetMethodHandler.java
new file mode 100644
index 0000000000..001b7858ec
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicGetMethodHandler.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicGetBody;
+import org.apache.qpid.framing.BasicGetEmptyBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.flow.FlowCreditManager;
+import org.apache.qpid.server.flow.MessageOnlyCreditManager;
+import org.apache.qpid.server.subscription.SubscriptionImpl;
+import org.apache.qpid.server.subscription.ClientDeliveryMethod;
+import org.apache.qpid.server.subscription.RecordDeliveryMethod;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.subscription.SubscriptionFactoryImpl;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.queue.SimpleAMQQueue;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class BasicGetMethodHandler implements StateAwareMethodListener<BasicGetBody>
+{
+ private static final Logger _log = Logger.getLogger(BasicGetMethodHandler.class);
+
+ private static final BasicGetMethodHandler _instance = new BasicGetMethodHandler();
+
+ public static BasicGetMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private BasicGetMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, BasicGetBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+
+ VirtualHost vHost = session.getVirtualHost();
+
+ AMQChannel channel = session.getChannel(channelId);
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+ else
+ {
+ AMQQueue queue = body.getQueue() == null ? channel.getDefaultQueue() : vHost.getQueueRegistry().getQueue(body.getQueue());
+ if (queue == null)
+ {
+ _log.info("No queue for '" + body.getQueue() + "'");
+ if(body.getQueue()!=null)
+ {
+ throw body.getConnectionException(AMQConstant.NOT_FOUND,
+ "No such queue, '" + body.getQueue()+ "'");
+ }
+ else
+ {
+ throw body.getConnectionException(AMQConstant.NOT_ALLOWED,
+ "No queue name provided, no default queue defined.");
+ }
+ }
+ else
+ {
+
+ //Perform ACLs
+ if (!vHost.getAccessManager().authoriseConsume(session, body.getNoAck(), queue))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+
+ if (!performGet(queue,session, channel, !body.getNoAck()))
+ {
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ // TODO - set clusterId
+ BasicGetEmptyBody responseBody = methodRegistry.createBasicGetEmptyBody(null);
+
+
+ session.writeFrame(responseBody.generateFrame(channelId));
+ }
+ }
+ }
+ }
+
+ public static boolean performGet(final AMQQueue queue,
+ final AMQProtocolSession session,
+ final AMQChannel channel,
+ final boolean acks)
+ throws AMQException
+ {
+
+ final FlowCreditManager singleMessageCredit = new MessageOnlyCreditManager(1L);
+
+ final ClientDeliveryMethod getDeliveryMethod = new ClientDeliveryMethod()
+ {
+
+ int _msg;
+
+ public void deliverToClient(final Subscription sub, final QueueEntry entry, final long deliveryTag)
+ throws AMQException
+ {
+ singleMessageCredit.useCreditForMessage(entry.getMessage());
+ session.getProtocolOutputConverter().writeGetOk(entry.getMessage(), channel.getChannelId(),
+ deliveryTag, queue.getMessageCount());
+
+ }
+ };
+ final RecordDeliveryMethod getRecordMethod = new RecordDeliveryMethod()
+ {
+
+ public void recordMessageDelivery(final Subscription sub, final QueueEntry entry, final long deliveryTag)
+ {
+ channel.addUnacknowledgedMessage(entry, deliveryTag, null);
+ }
+ };
+
+ Subscription sub;
+ if(acks)
+ {
+ sub = SubscriptionFactoryImpl.INSTANCE.createSubscription(channel, session, null, acks, null, false, singleMessageCredit, getDeliveryMethod, getRecordMethod);
+ }
+ else
+ {
+ sub = new GetNoAckSubscription(channel,
+ session,
+ null,
+ null,
+ false,
+ singleMessageCredit,
+ getDeliveryMethod,
+ getRecordMethod);
+ }
+
+ queue.registerSubscription(sub,false);
+ queue.flushSubscription(sub);
+ queue.unregisterSubscription(sub);
+ return(!singleMessageCredit.hasCredit());
+
+
+ }
+
+ public static final class GetNoAckSubscription extends SubscriptionImpl.NoAckSubscription
+ {
+ public GetNoAckSubscription(AMQChannel channel, AMQProtocolSession protocolSession,
+ AMQShortString consumerTag, FieldTable filters,
+ boolean noLocal, FlowCreditManager creditManager,
+ ClientDeliveryMethod deliveryMethod,
+ RecordDeliveryMethod recordMethod)
+ throws AMQException
+ {
+ super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod);
+ }
+
+ public boolean wouldSuspend(QueueEntry msg)
+ {
+ return !getCreditManager().useCreditForMessage(msg.getMessage());
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java
new file mode 100644
index 0000000000..a7d3ad6217
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java
@@ -0,0 +1,106 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class BasicPublishMethodHandler implements StateAwareMethodListener<BasicPublishBody>
+{
+ private static final Logger _logger = Logger.getLogger(BasicPublishMethodHandler.class);
+
+ private static final BasicPublishMethodHandler _instance = new BasicPublishMethodHandler();
+
+
+ public static BasicPublishMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private BasicPublishMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, BasicPublishBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Publish received on channel " + channelId);
+ }
+
+ AMQShortString exchange = body.getExchange();
+ // TODO: check the delivery tag field details - is it unique across the broker or per subscriber?
+ if (exchange == null)
+ {
+ exchange = ExchangeDefaults.DEFAULT_EXCHANGE_NAME;
+
+ }
+
+ VirtualHost vHost = session.getVirtualHost();
+ Exchange e = vHost.getExchangeRegistry().getExchange(exchange);
+ // if the exchange does not exist we raise a channel exception
+ if (e == null)
+ {
+ throw body.getChannelException(AMQConstant.NOT_FOUND, "Unknown exchange name");
+ }
+ 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 = session.getChannel(channelId);
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ //Access Control
+ if (!vHost.getAccessManager().authorisePublish(session,
+ body.getImmediate(), body.getMandatory(),
+ body.getRoutingKey(), e))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+
+ MessagePublishInfo info = session.getMethodRegistry().getProtocolVersionMethodConverter().convertToInfo(body);
+ info.setExchange(exchange);
+ channel.setPublishFrame(info, e);
+ }
+ }
+
+}
+
+
+
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java
new file mode 100644
index 0000000000..dd3281c65f
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicQosBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.AMQChannel;
+
+public class BasicQosHandler implements StateAwareMethodListener<BasicQosBody>
+{
+ private static final BasicQosHandler _instance = new BasicQosHandler();
+
+ public static BasicQosHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, BasicQosBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ AMQChannel channel = session.getChannel(channelId);
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ channel.setCredit(body.getPrefetchSize(), body.getPrefetchCount());
+
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createBasicQosOkBody();
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java
new file mode 100644
index 0000000000..c7842cd643
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java
@@ -0,0 +1,73 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicRecoverBody;
+import org.apache.qpid.framing.BasicRecoverOkBody;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+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, BasicRecoverBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ _logger.debug("Recover received on protocol session " + session + " and channel " + channelId);
+ AMQChannel channel = session.getChannel(channelId);
+
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ channel.resend(body.getRequeue());
+
+ // Qpid 0-8 hacks a synchronous -ok onto recover.
+ // In Qpid 0-9 we create a separate sync-recover, sync-recover-ok pair to be "more" compliant
+ if(session.getProtocolVersion().equals(ProtocolVersion.v8_0))
+ {
+ MethodRegistry_8_0 methodRegistry = (MethodRegistry_8_0) session.getMethodRegistry();
+ AMQMethodBody recoverOk = methodRegistry.createBasicRecoverOkBody();
+ session.writeFrame(recoverOk.generateFrame(channelId));
+
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverSyncMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverSyncMethodHandler.java
new file mode 100644
index 0000000000..2c264c3d45
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverSyncMethodHandler.java
@@ -0,0 +1,75 @@
+package org.apache.qpid.server.handler;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.framing.BasicRecoverBody;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.framing.BasicRecoverSyncBody;
+import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9;
+import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.AMQException;
+
+public class BasicRecoverSyncMethodHandler implements StateAwareMethodListener<BasicRecoverSyncBody>
+{
+ private static final Logger _logger = Logger.getLogger(BasicRecoverSyncMethodHandler.class);
+
+ private static final BasicRecoverSyncMethodHandler _instance = new BasicRecoverSyncMethodHandler();
+
+ public static BasicRecoverSyncMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, BasicRecoverSyncBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ _logger.debug("Recover received on protocol session " + session + " and channel " + channelId);
+ AMQChannel channel = session.getChannel(channelId);
+
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ channel.resend(body.getRequeue());
+
+ // Qpid 0-8 hacks a synchronous -ok onto recover.
+ // In Qpid 0-9 we create a separate sync-recover, sync-recover-ok pair to be "more" compliant
+ if(session.getProtocolVersion().equals(ProtocolVersion.v0_9))
+ {
+ MethodRegistry_0_9 methodRegistry = (MethodRegistry_0_9) session.getMethodRegistry();
+ AMQMethodBody recoverOk = methodRegistry.createBasicRecoverSyncOkBody();
+ session.writeFrame(recoverOk.generateFrame(channelId));
+
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRejectMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRejectMethodHandler.java
new file mode 100644
index 0000000000..f3cab10ed7
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRejectMethodHandler.java
@@ -0,0 +1,125 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicRejectBody;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.log4j.Logger;
+
+public class BasicRejectMethodHandler implements StateAwareMethodListener<BasicRejectBody>
+{
+ private static final Logger _logger = Logger.getLogger(BasicRejectMethodHandler.class);
+
+ private static BasicRejectMethodHandler _instance = new BasicRejectMethodHandler();
+
+ public static BasicRejectMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private BasicRejectMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, BasicRejectBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ AMQChannel channel = session.getChannel(channelId);
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Rejecting:" + body.getDeliveryTag() +
+ ": Requeue:" + body.getRequeue() +
+ //": Resend:" + evt.getMethod().resend +
+ " on channel:" + channel.debugIdentity());
+ }
+
+ long deliveryTag = body.getDeliveryTag();
+
+ QueueEntry message = channel.getUnacknowledgedMessageMap().get(deliveryTag);
+
+ if (message == null)
+ {
+ _logger.warn("Dropping reject request as message is null for tag:" + deliveryTag);
+// throw evt.getMethod().getChannelException(AMQConstant.NOT_FOUND, "Delivery Tag(" + deliveryTag + ")not known");
+ }
+ else
+ {
+ if (message.isQueueDeleted())
+ {
+ _logger.warn("Message's Queue as already been purged, unable to Reject. " +
+ "Dropping message should use Dead Letter Queue");
+ message = channel.getUnacknowledgedMessageMap().remove(deliveryTag);
+ if(message != null)
+ {
+ message.discard(channel.getStoreContext());
+ }
+ //sendtoDeadLetterQueue(msg)
+ return;
+ }
+
+ if (!message.getMessage().isReferenced())
+ {
+ _logger.warn("Message as already been purged, unable to Reject.");
+ return;
+ }
+
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Rejecting: DT:" + deliveryTag + "-" + message.getMessage().debugIdentity() +
+ ": Requeue:" + body.getRequeue() +
+ //": Resend:" + evt.getMethod().resend +
+ " on channel:" + channel.debugIdentity());
+ }
+
+ // If we haven't requested message to be resent to this consumer then reject it from ever getting it.
+ //if (!evt.getMethod().resend)
+ {
+ message.reject();
+ }
+
+ if (body.getRequeue())
+ {
+ channel.requeue(deliveryTag);
+ }
+ else
+ {
+ _logger.warn("Dropping message as requeue not required and there is no dead letter queue");
+ message = channel.getUnacknowledgedMessageMap().remove(deliveryTag);
+ //sendtoDeadLetterQueue(AMQMessage message)
+// message.queue = channel.getDefaultDeadLetterQueue();
+// channel.requeue(deliveryTag);
+ }
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java
new file mode 100644
index 0000000000..9133cce6b7
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java
@@ -0,0 +1,77 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ChannelCloseBody;
+import org.apache.qpid.framing.ChannelCloseOkBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.AMQChannel;
+
+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, ChannelCloseBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Received channel close for id " + channelId + " citing class " + body.getClassId() +
+ " and method " + body.getMethodId());
+ }
+
+
+ AMQChannel channel = session.getChannel(channelId);
+
+ if (channel == null)
+ {
+ throw body.getConnectionException(AMQConstant.CHANNEL_ERROR, "Trying to close unknown channel");
+ }
+
+ session.closeChannel(channelId);
+ // Client requested closure so we don't wait for ok we send it
+ stateManager.getProtocolSession().closeChannelOk(channelId);
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ ChannelCloseOkBody responseBody = methodRegistry.createChannelCloseOkBody();
+ session.writeFrame(responseBody.generateFrame(channelId));
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java
new file mode 100644
index 0000000000..a857490e7e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ChannelCloseOkBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+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, ChannelCloseOkBody body, int channelId) throws AMQException
+ {
+
+ _logger.info("Received channel-close-ok for channel-id " + channelId);
+
+ // Let the Protocol Session know the channel is now closed.
+ stateManager.getProtocolSession().closeChannelOk(channelId);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java
new file mode 100644
index 0000000000..696ca8a63b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+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, ChannelFlowBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+
+ AMQChannel channel = session.getChannel(channelId);
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ channel.setSuspended(!body.getActive());
+ _logger.debug("Channel.Flow for channel " + channelId + ", active=" + body.getActive());
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createChannelFlowOkBody(body.getActive());
+ session.writeFrame(responseBody.generateFrame(channelId));
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java
new file mode 100644
index 0000000000..054674aed4
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java
@@ -0,0 +1,103 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import java.util.UUID;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9;
+import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+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, ChannelOpenBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ VirtualHost virtualHost = session.getVirtualHost();
+
+ final AMQChannel channel = new AMQChannel(session,channelId, virtualHost.getMessageStore()
+ );
+ session.addChannel(channel);
+
+ ChannelOpenOkBody response;
+
+ ProtocolVersion pv = session.getProtocolVersion();
+
+ if(pv.equals(ProtocolVersion.v8_0))
+ {
+ MethodRegistry_8_0 methodRegistry = (MethodRegistry_8_0) MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0);
+ response = methodRegistry.createChannelOpenOkBody();
+
+ }
+ else if(pv.equals(ProtocolVersion.v0_9))
+ {
+ MethodRegistry_0_9 methodRegistry = (MethodRegistry_0_9) MethodRegistry.getMethodRegistry(ProtocolVersion.v0_9);
+ UUID uuid = UUID.randomUUID();
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ DataOutputStream dataOut = new DataOutputStream(output);
+ try
+ {
+ dataOut.writeLong(uuid.getMostSignificantBits());
+ dataOut.writeLong(uuid.getLeastSignificantBits());
+ dataOut.flush();
+ dataOut.close();
+ }
+ catch (IOException e)
+ {
+ // This *really* shouldn't happen as we're not doing any I/O
+ throw new RuntimeException("I/O exception when writing to byte array", e);
+ }
+
+ // should really associate this channelId to the session
+ byte[] channelName = output.toByteArray();
+
+ response = methodRegistry.createChannelOpenOkBody(channelName);
+ }
+ else
+ {
+ throw new AMQException(AMQConstant.INTERNAL_ERROR, "Got channel open for protocol version not catered for: " + pv, null);
+ }
+
+
+ session.writeFrame(response.generateFrame(channelId));
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java
new file mode 100644
index 0000000000..dade5d5f54
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java
@@ -0,0 +1,72 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ConnectionCloseBody;
+import org.apache.qpid.framing.ConnectionCloseOkBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+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, ConnectionCloseBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("ConnectionClose received with reply code/reply text " + body.getReplyCode() + "/" +
+ body.getReplyText() + " for " + session);
+ }
+ try
+ {
+ session.closeSession();
+ }
+ catch (Exception e)
+ {
+ _logger.error("Error closing protocol session: " + e, e);
+ }
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ ConnectionCloseOkBody responseBody = methodRegistry.createConnectionCloseOkBody();
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java
new file mode 100644
index 0000000000..bc6e5ab403
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ConnectionCloseOkBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQState;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+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, ConnectionCloseOkBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ //todo should this not do more than just log the method?
+ _logger.info("Received Connection-close-ok");
+
+ try
+ {
+ stateManager.changeState(AMQState.CONNECTION_CLOSED);
+ session.closeSession();
+ }
+ catch (Exception e)
+ {
+ _logger.error("Error closing protocol session: " + e, e);
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java
new file mode 100644
index 0000000000..824f084f57
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java
@@ -0,0 +1,102 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.security.access.Permission;
+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.virtualhost.VirtualHost;
+import org.apache.log4j.Logger;
+
+public class ConnectionOpenMethodHandler implements StateAwareMethodListener<ConnectionOpenBody>
+{
+ private static final Logger _logger = Logger.getLogger(ConnectionOpenMethodHandler.class);
+
+ private static ConnectionOpenMethodHandler _instance = new ConnectionOpenMethodHandler();
+
+ public static ConnectionOpenMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ConnectionOpenMethodHandler()
+ {
+ }
+
+ private static AMQShortString generateClientID()
+ {
+ return new AMQShortString(Long.toString(System.currentTimeMillis()));
+ }
+
+ public void methodReceived(AMQStateManager stateManager, ConnectionOpenBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+
+ //ignore leading '/'
+ String virtualHostName;
+ if ((body.getVirtualHost() != null) && body.getVirtualHost().charAt(0) == '/')
+ {
+ virtualHostName = new StringBuilder(body.getVirtualHost().subSequence(1, body.getVirtualHost().length())).toString();
+ }
+ else
+ {
+ virtualHostName = body.getVirtualHost() == null ? null : String.valueOf(body.getVirtualHost());
+ }
+
+ VirtualHost virtualHost = stateManager.getVirtualHostRegistry().getVirtualHost(virtualHostName);
+
+ if (virtualHost == null)
+ {
+ throw body.getConnectionException(AMQConstant.NOT_FOUND, "Unknown virtual host: '" + virtualHostName + "'");
+ }
+ else
+ {
+ session.setVirtualHost(virtualHost);
+
+ //Perform ACL
+ if (!virtualHost.getAccessManager().authoriseConnect(session, virtualHost))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+
+ // See Spec (0.8.2). Section 3.1.2 Virtual Hosts
+ if (session.getContextKey() == null)
+ {
+ session.setContextKey(generateClientID());
+ }
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createConnectionOpenOkBody(body.getVirtualHost());
+
+ stateManager.changeState(AMQState.CONNECTION_OPEN);
+
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java
new file mode 100644
index 0000000000..a2a6faf21b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java
@@ -0,0 +1,126 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ConnectionCloseBody;
+import org.apache.qpid.framing.ConnectionSecureBody;
+import org.apache.qpid.framing.ConnectionSecureOkBody;
+import org.apache.qpid.framing.ConnectionTuneBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
+import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
+import org.apache.qpid.server.state.AMQState;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+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, ConnectionSecureOkBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ AuthenticationManager authMgr = ApplicationRegistry.getInstance().getAuthenticationManager();
+
+ SaslServer ss = session.getSaslServer();
+ if (ss == null)
+ {
+ throw new AMQException("No SASL context set up in session");
+ }
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AuthenticationResult authResult = authMgr.authenticate(ss, body.getResponse());
+ switch (authResult.status)
+ {
+ case ERROR:
+ Exception cause = authResult.getCause();
+
+ _logger.info("Authentication failed:" + (cause == null ? "" : cause.getMessage()));
+
+ // This should be abstracted
+ stateManager.changeState(AMQState.CONNECTION_CLOSING);
+
+ ConnectionCloseBody connectionCloseBody =
+ methodRegistry.createConnectionCloseBody(AMQConstant.NOT_ALLOWED.getCode(),
+ AMQConstant.NOT_ALLOWED.getName(),
+ body.getClazz(),
+ body.getMethod());
+
+ session.writeFrame(connectionCloseBody.generateFrame(0));
+ disposeSaslServer(session);
+ break;
+ case SUCCESS:
+ _logger.info("Connected as: " + ss.getAuthorizationID());
+ stateManager.changeState(AMQState.CONNECTION_NOT_TUNED);
+
+ ConnectionTuneBody tuneBody =
+ methodRegistry.createConnectionTuneBody(0xFFFF,
+ ConnectionStartOkMethodHandler.getConfiguredFrameSize(),
+ ApplicationRegistry.getInstance().getConfiguration().getHeartBeatDelay());
+ session.writeFrame(tuneBody.generateFrame(0));
+ session.setAuthorizedID(new UsernamePrincipal(ss.getAuthorizationID()));
+ disposeSaslServer(session);
+ break;
+ case CONTINUE:
+ stateManager.changeState(AMQState.CONNECTION_NOT_AUTH);
+
+ ConnectionSecureBody secureBody = methodRegistry.createConnectionSecureBody(authResult.challenge);
+ session.writeFrame(secureBody.generateFrame(0));
+ }
+ }
+
+ 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/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java
new file mode 100644
index 0000000000..6698ae888a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java
@@ -0,0 +1,163 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ConnectionCloseBody;
+import org.apache.qpid.framing.ConnectionSecureBody;
+import org.apache.qpid.framing.ConnectionStartOkBody;
+import org.apache.qpid.framing.ConnectionTuneBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
+import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
+import org.apache.qpid.server.state.AMQState;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+
+public class ConnectionStartOkMethodHandler implements StateAwareMethodListener<ConnectionStartOkBody>
+{
+ private static final Logger _logger = Logger.getLogger(ConnectionStartOkMethodHandler.class);
+
+ private static ConnectionStartOkMethodHandler _instance = new ConnectionStartOkMethodHandler();
+
+ public static ConnectionStartOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ConnectionStartOkMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, ConnectionStartOkBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ _logger.info("SASL Mechanism selected: " + body.getMechanism());
+ _logger.info("Locale selected: " + body.getLocale());
+
+ AuthenticationManager authMgr = ApplicationRegistry.getInstance().getAuthenticationManager();//session.getVirtualHost().getAuthenticationManager();
+
+ SaslServer ss = null;
+ try
+ {
+ ss = authMgr.createSaslServer(String.valueOf(body.getMechanism()), session.getLocalFQDN());
+
+ if (ss == null)
+ {
+ throw body.getConnectionException(AMQConstant.RESOURCE_ERROR, "Unable to create SASL Server:" + body.getMechanism()
+ );
+ }
+
+ session.setSaslServer(ss);
+
+ AuthenticationResult authResult = authMgr.authenticate(ss, body.getResponse());
+
+ //save clientProperties
+ if (session.getClientProperties() == null)
+ {
+ session.setClientProperties(body.getClientProperties());
+ }
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+
+ switch (authResult.status)
+ {
+ case ERROR:
+ Exception cause = authResult.getCause();
+
+ _logger.info("Authentication failed:" + (cause == null ? "" : cause.getMessage()));
+
+ stateManager.changeState(AMQState.CONNECTION_CLOSING);
+
+ ConnectionCloseBody closeBody =
+ methodRegistry.createConnectionCloseBody(AMQConstant.NOT_ALLOWED.getCode(), // replyCode
+ AMQConstant.NOT_ALLOWED.getName(),
+ body.getClazz(),
+ body.getMethod());
+
+ session.writeFrame(closeBody.generateFrame(0));
+ disposeSaslServer(session);
+ break;
+
+ case SUCCESS:
+ _logger.info("Connected as: " + ss.getAuthorizationID());
+ session.setAuthorizedID(new UsernamePrincipal(ss.getAuthorizationID()));
+
+ stateManager.changeState(AMQState.CONNECTION_NOT_TUNED);
+
+ ConnectionTuneBody tuneBody = methodRegistry.createConnectionTuneBody(0xFFFF,
+ getConfiguredFrameSize(),
+ ApplicationRegistry.getInstance().getConfiguration().getHeartBeatDelay());
+ session.writeFrame(tuneBody.generateFrame(0));
+ break;
+ case CONTINUE:
+ stateManager.changeState(AMQState.CONNECTION_NOT_AUTH);
+
+ ConnectionSecureBody secureBody = methodRegistry.createConnectionSecureBody(authResult.challenge);
+ session.writeFrame(secureBody.generateFrame(0));
+ }
+ }
+ catch (SaslException e)
+ {
+ disposeSaslServer(session);
+ 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 ServerConfiguration config = ApplicationRegistry.getInstance().getConfiguration();
+ final int framesize = config.getFrameSize();
+ _logger.info("Framesize set to " + framesize);
+ return framesize;
+ }
+}
+
+
+
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java
new file mode 100644
index 0000000000..0fe8c5dc92
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java
@@ -0,0 +1,54 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ConnectionTuneOkBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+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, ConnectionTuneOkBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug(body);
+ }
+ stateManager.changeState(AMQState.CONNECTION_NOT_OPENED);
+ session.initHeartbeats(body.getHeartbeat());
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeBoundHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeBoundHandler.java
new file mode 100644
index 0000000000..ccd42204d9
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeBoundHandler.java
@@ -0,0 +1,178 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.exchange.Exchange;
+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.qpid.server.virtualhost.VirtualHost;
+
+/**
+ * @author Apache Software Foundation
+ *
+ *
+ */
+public class ExchangeBoundHandler implements StateAwareMethodListener<ExchangeBoundBody>
+{
+ private static final ExchangeBoundHandler _instance = new ExchangeBoundHandler();
+
+ public static final int OK = 0;
+
+ public static final int EXCHANGE_NOT_FOUND = 1;
+
+ public static final int QUEUE_NOT_FOUND = 2;
+
+ public static final int NO_BINDINGS = 3;
+
+ public static final int QUEUE_NOT_BOUND = 4;
+
+ public static final int NO_QUEUE_BOUND_WITH_RK = 5;
+
+ public static final int SPECIFIC_QUEUE_NOT_BOUND_WITH_RK = 6;
+
+ public static ExchangeBoundHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ExchangeBoundHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, ExchangeBoundBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ VirtualHost virtualHost = session.getVirtualHost();
+ QueueRegistry queueRegistry = virtualHost.getQueueRegistry();
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+
+
+
+
+ AMQShortString exchangeName = body.getExchange();
+ AMQShortString queueName = body.getQueue();
+ AMQShortString routingKey = body.getRoutingKey();
+ if (exchangeName == null)
+ {
+ throw new AMQException("Exchange exchange must not be null");
+ }
+ Exchange exchange = virtualHost.getExchangeRegistry().getExchange(exchangeName);
+ ExchangeBoundOkBody response;
+ if (exchange == null)
+ {
+
+
+ response = methodRegistry.createExchangeBoundOkBody(EXCHANGE_NOT_FOUND,
+ new AMQShortString("Exchange " + exchangeName + " not found"));
+ }
+ else if (routingKey == null)
+ {
+ if (queueName == null)
+ {
+ if (exchange.hasBindings())
+ {
+ response = methodRegistry.createExchangeBoundOkBody(OK, null);
+ }
+ else
+ {
+
+ response = methodRegistry.createExchangeBoundOkBody(NO_BINDINGS, // replyCode
+ null); // replyText
+ }
+ }
+ else
+ {
+
+ AMQQueue queue = queueRegistry.getQueue(queueName);
+ if (queue == null)
+ {
+
+ response = methodRegistry.createExchangeBoundOkBody(QUEUE_NOT_FOUND, // replyCode
+ new AMQShortString("Queue " + queueName + " not found")); // replyText
+ }
+ else
+ {
+ if (exchange.isBound(queue))
+ {
+
+ response = methodRegistry.createExchangeBoundOkBody(OK, // replyCode
+ null); // replyText
+ }
+ else
+ {
+
+ response = methodRegistry.createExchangeBoundOkBody(QUEUE_NOT_BOUND, // replyCode
+ new AMQShortString("Queue " + queueName + " not bound to exchange " + exchangeName)); // replyText
+ }
+ }
+ }
+ }
+ else if (queueName != null)
+ {
+ AMQQueue queue = queueRegistry.getQueue(queueName);
+ if (queue == null)
+ {
+
+ response = methodRegistry.createExchangeBoundOkBody(QUEUE_NOT_FOUND, // replyCode
+ new AMQShortString("Queue " + queueName + " not found")); // replyText
+ }
+ else
+ {
+ if (exchange.isBound(body.getRoutingKey(), queue))
+ {
+
+ response = methodRegistry.createExchangeBoundOkBody(OK, // replyCode
+ null); // replyText
+ }
+ else
+ {
+
+ response = methodRegistry.createExchangeBoundOkBody(SPECIFIC_QUEUE_NOT_BOUND_WITH_RK, // replyCode
+ new AMQShortString("Queue " + queueName + " not bound with routing key " +
+ body.getRoutingKey() + " to exchange " + exchangeName)); // replyText
+ }
+ }
+ }
+ else
+ {
+ if (exchange.isBound(body.getRoutingKey()))
+ {
+
+ response = methodRegistry.createExchangeBoundOkBody(OK, // replyCode
+ null); // replyText
+ }
+ else
+ {
+
+ response = methodRegistry.createExchangeBoundOkBody(NO_QUEUE_BOUND_WITH_RK, // replyCode
+ new AMQShortString("No queue bound with routing key " + body.getRoutingKey() +
+ " to exchange " + exchangeName)); // replyText
+ }
+ }
+ session.writeFrame(response.generateFrame(channelId));
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java
new file mode 100644
index 0000000000..ba60808492
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java
@@ -0,0 +1,122 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQUnknownExchangeType;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+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 ExchangeDeclareHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, ExchangeDeclareBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ VirtualHost virtualHost = session.getVirtualHost();
+ ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry();
+ ExchangeFactory exchangeFactory = virtualHost.getExchangeFactory();
+
+ if (!body.getPassive())
+ {
+ // Perform ACL if request is not passive
+ if (!virtualHost.getAccessManager().authoriseCreateExchange(session, body.getAutoDelete(),
+ body.getDurable(), body.getExchange(), body.getInternal(), body.getNowait(), body.getPassive(),
+ body.getType()))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+
+ }
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Request to declare exchange of type " + body.getType() + " with name " + body.getExchange());
+ }
+ synchronized(exchangeRegistry)
+ {
+ Exchange exchange = exchangeRegistry.getExchange(body.getExchange());
+
+
+
+ if (exchange == null)
+ {
+ if(body.getPassive() && ((body.getType() == null) || body.getType().length() ==0))
+ {
+ throw body.getChannelException(AMQConstant.NOT_FOUND, "Unknown exchange: " + body.getExchange());
+ }
+ else
+ {
+ try
+ {
+
+ exchange = exchangeFactory.createExchange(body.getExchange() == null ? null : body.getExchange().intern(),
+ body.getType() == null ? null : body.getType().intern(),
+ body.getDurable(),
+ body.getPassive(), body.getTicket());
+ exchangeRegistry.registerExchange(exchange);
+ }
+ catch(AMQUnknownExchangeType e)
+ {
+ throw body.getConnectionException(AMQConstant.COMMAND_INVALID, "Unknown exchange: " + body.getExchange(),e);
+ }
+ }
+ }
+ else if (!exchange.getType().equals(body.getType()))
+ {
+
+ throw new AMQConnectionException(AMQConstant.NOT_ALLOWED, "Attempt to redeclare exchange: " + body.getExchange() + " of type " + exchange.getType() + " to " + body.getType() +".",body.getClazz(), body.getMethod(),body.getMajor(),body.getMinor(),null);
+ }
+
+ }
+ if(!body.getNowait())
+ {
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createExchangeDeclareOkBody();
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java
new file mode 100644
index 0000000000..bd4b610933
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java
@@ -0,0 +1,75 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ExchangeDeleteBody;
+import org.apache.qpid.framing.ExchangeDeleteOkBody;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.exchange.ExchangeInUseException;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+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, ExchangeDeleteBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ VirtualHost virtualHost = session.getVirtualHost();
+ ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry();
+
+ //Perform ACLs
+ if (!virtualHost.getAccessManager().authoriseDelete(session,
+ exchangeRegistry.getExchange(body.getExchange())))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+
+ try
+ {
+ exchangeRegistry.unregisterExchange(body.getExchange(), body.getIfUnused());
+
+ ExchangeDeleteOkBody responseBody = session.getMethodRegistry().createExchangeDeleteOkBody();
+
+ session.writeFrame(responseBody.generateFrame(channelId));
+ }
+ catch (ExchangeInUseException e)
+ {
+ // TODO: sort out consistent channel close mechanism that does all clean up etc.
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java
new file mode 100644
index 0000000000..ac516b6133
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import java.util.concurrent.Executor;
+
+/**
+ * An executor that executes the task on the current thread.
+ */
+public class OnCurrentThreadExecutor implements Executor
+{
+ public void execute(Runnable command)
+ {
+ command.run();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java
new file mode 100644
index 0000000000..84491c1d2e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java
@@ -0,0 +1,143 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQInvalidRoutingKeyException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.protocol.AMQConstant;
+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.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+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, QueueBindBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ VirtualHost virtualHost = session.getVirtualHost();
+ ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry();
+ QueueRegistry queueRegistry = virtualHost.getQueueRegistry();
+
+
+ final AMQQueue queue;
+ final AMQShortString routingKey;
+
+ if (body.getQueue() == null)
+ {
+ AMQChannel channel = session.getChannel(channelId);
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ queue = channel.getDefaultQueue();
+
+ if (queue == null)
+ {
+ throw body.getChannelException(AMQConstant.NOT_FOUND, "No default queue defined on channel and queue was null");
+ }
+
+ if (body.getRoutingKey() == null)
+ {
+ routingKey = queue.getName();
+ }
+ else
+ {
+ routingKey = body.getRoutingKey().intern();
+ }
+ }
+ else
+ {
+ queue = queueRegistry.getQueue(body.getQueue());
+ routingKey = body.getRoutingKey() == null ? AMQShortString.EMPTY_STRING : body.getRoutingKey().intern();
+ }
+
+ if (queue == null)
+ {
+ throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist.");
+ }
+ final Exchange exch = exchangeRegistry.getExchange(body.getExchange());
+ if (exch == null)
+ {
+ throw body.getChannelException(AMQConstant.NOT_FOUND, "Exchange " + body.getExchange() + " does not exist.");
+ }
+
+
+ try
+ {
+
+ //Perform ACLs
+ if (!virtualHost.getAccessManager().authoriseBind(session, exch,
+ queue, routingKey))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+
+ if (!exch.isBound(routingKey, body.getArguments(), queue))
+ {
+ queue.bind(exch, routingKey, body.getArguments());
+ }
+ }
+ catch (AMQInvalidRoutingKeyException rke)
+ {
+ throw body.getChannelException(AMQConstant.INVALID_ROUTING_KEY, routingKey.toString());
+ }
+ catch (AMQException e)
+ {
+ throw body.getChannelException(AMQConstant.CHANNEL_ERROR, e.toString());
+ }
+
+ if (_log.isInfoEnabled())
+ {
+ _log.info("Binding queue " + queue + " to exchange " + exch + " with routing key " + routingKey);
+ }
+ if (!body.getNowait())
+ {
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createQueueBindOkBody();
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java
new file mode 100644
index 0000000000..b705ea7dba
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java
@@ -0,0 +1,203 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.QueueDeclareBody;
+import org.apache.qpid.framing.QueueDeclareOkBody;
+import org.apache.qpid.protocol.AMQConstant;
+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.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.AMQQueueFactory;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class QueueDeclareHandler implements StateAwareMethodListener<QueueDeclareBody>
+{
+ private static final Logger _logger = Logger.getLogger(QueueDeclareHandler.class);
+
+ private static final QueueDeclareHandler _instance = new QueueDeclareHandler();
+
+ public static QueueDeclareHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public boolean autoRegister = ApplicationRegistry.getInstance().getConfiguration().getQueueAutoRegister();
+
+ private final AtomicInteger _counter = new AtomicInteger();
+
+ public void methodReceived(AMQStateManager stateManager, QueueDeclareBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ VirtualHost virtualHost = session.getVirtualHost();
+ ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry();
+ QueueRegistry queueRegistry = virtualHost.getQueueRegistry();
+ MessageStore store = virtualHost.getMessageStore();
+
+
+ if (!body.getPassive())
+ {
+ // Perform ACL if request is not passive
+ if (!virtualHost.getAccessManager().authoriseCreateQueue(session, body.getAutoDelete(), body.getDurable(),
+ body.getExclusive(), body.getNowait(), body.getPassive(), body.getQueue()))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+ }
+
+ final AMQShortString queueName;
+
+ // if we aren't given a queue name, we create one which we return to the client
+
+ if ((body.getQueue() == null) || (body.getQueue().length() == 0))
+ {
+ queueName = createName();
+ }
+ else
+ {
+ queueName = body.getQueue().intern();
+ }
+
+ AMQQueue queue;
+ //TODO: do we need to check that the queue already exists with exactly the same "configuration"?
+
+ synchronized (queueRegistry)
+ {
+
+
+
+ if (((queue = queueRegistry.getQueue(queueName)) == null))
+ {
+
+ if (body.getPassive())
+ {
+ String msg = "Queue: " + queueName + " not found on VirtualHost(" + virtualHost + ").";
+ throw body.getChannelException(AMQConstant.NOT_FOUND, msg);
+ }
+ else
+ {
+ queue = createQueue(queueName, body, virtualHost, session);
+ if (queue.isDurable() && !queue.isAutoDelete())
+ {
+ store.createQueue(queue, body.getArguments());
+ }
+ queueRegistry.registerQueue(queue);
+ if (autoRegister)
+ {
+ Exchange defaultExchange = exchangeRegistry.getDefaultExchange();
+
+ queue.bind(defaultExchange, queueName, null);
+ _logger.info("Queue " + queueName + " bound to default exchange(" + defaultExchange.getName() + ")");
+ }
+ }
+ }
+ else if (queue.getOwner() != null && !session.getContextKey().equals(queue.getOwner()))
+ {
+ throw body.getChannelException(AMQConstant.ALREADY_EXISTS, "Cannot declare queue('" + queueName + "'),"
+ + " as exclusive queue with same name "
+ + "declared on another client ID('"
+ + queue.getOwner() + "')");
+ }
+
+ AMQChannel channel = session.getChannel(channelId);
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ //set this as the default queue on the channel:
+ channel.setDefaultQueue(queue);
+ }
+
+ if (!body.getNowait())
+ {
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ QueueDeclareOkBody responseBody =
+ methodRegistry.createQueueDeclareOkBody(queueName,
+ queue.getMessageCount(),
+ queue.getConsumerCount());
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+ _logger.info("Queue " + queueName + " declared successfully");
+ }
+ }
+
+ protected AMQShortString createName()
+ {
+ return new AMQShortString("tmp_" + UUID.randomUUID());
+ }
+
+ protected AMQQueue createQueue(final AMQShortString queueName,
+ QueueDeclareBody body,
+ VirtualHost virtualHost,
+ final AMQProtocolSession session)
+ throws AMQException
+ {
+ final QueueRegistry registry = virtualHost.getQueueRegistry();
+ AMQShortString owner = body.getExclusive() ? session.getContextKey() : null;
+
+ final AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(queueName, body.getDurable(), owner, body.getAutoDelete(), virtualHost,
+ body.getArguments());
+
+
+ if (body.getExclusive() && !body.getDurable())
+ {
+ final AMQProtocolSession.Task deleteQueueTask =
+ new AMQProtocolSession.Task()
+ {
+ public void doTask(AMQProtocolSession session) throws AMQException
+ {
+ if (registry.getQueue(queueName) == queue)
+ {
+ queue.delete();
+ }
+ }
+ };
+
+ session.addSessionCloseTask(deleteQueueTask);
+
+ queue.addQueueDeleteTask(new AMQQueue.Task()
+ {
+ public void doTask(AMQQueue queue)
+ {
+ session.removeSessionCloseTask(deleteQueueTask);
+ }
+ });
+ }// if exclusive and not durable
+
+ return queue;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java
new file mode 100644
index 0000000000..b397db9246
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java
@@ -0,0 +1,127 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.QueueDeleteBody;
+import org.apache.qpid.framing.QueueDeleteOkBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.protocol.AMQConstant;
+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.qpid.server.store.MessageStore;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.security.access.Permission;
+
+public class QueueDeleteHandler implements StateAwareMethodListener<QueueDeleteBody>
+{
+ private static final QueueDeleteHandler _instance = new QueueDeleteHandler();
+
+ public static QueueDeleteHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private final boolean _failIfNotFound;
+
+ public QueueDeleteHandler()
+ {
+ this(true);
+ }
+
+ public QueueDeleteHandler(boolean failIfNotFound)
+ {
+ _failIfNotFound = failIfNotFound;
+
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueDeleteBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ VirtualHost virtualHost = session.getVirtualHost();
+ QueueRegistry queueRegistry = virtualHost.getQueueRegistry();
+ MessageStore store = virtualHost.getMessageStore();
+
+ AMQQueue queue;
+ if (body.getQueue() == null)
+ {
+ AMQChannel channel = session.getChannel(channelId);
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ //get the default queue on the channel:
+ queue = channel.getDefaultQueue();
+ }
+ else
+ {
+ queue = queueRegistry.getQueue(body.getQueue());
+ }
+
+ if (queue == null)
+ {
+ if (_failIfNotFound)
+ {
+ throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist.");
+ }
+ }
+ else
+ {
+ if (body.getIfEmpty() && !queue.isEmpty())
+ {
+ throw body.getChannelException(AMQConstant.IN_USE, "Queue: " + body.getQueue() + " is not empty.");
+ }
+ else if (body.getIfUnused() && !queue.isUnused())
+ {
+ // TODO - Error code
+ throw body.getChannelException(AMQConstant.IN_USE, "Queue: " + body.getQueue() + " is still used.");
+
+ }
+ else
+ {
+
+ //Perform ACLs
+ if (!virtualHost.getAccessManager().authoriseDelete(session, queue))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+
+ int purged = queue.delete();
+
+
+ if (queue.isDurable())
+ {
+ store.removeQueue(queue);
+ }
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ QueueDeleteOkBody responseBody = methodRegistry.createQueueDeleteOkBody(purged);
+ session.writeFrame(responseBody.generateFrame(channelId));
+ }
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueuePurgeHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueuePurgeHandler.java
new file mode 100644
index 0000000000..2768518f53
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueuePurgeHandler.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.QueuePurgeBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.protocol.AMQConstant;
+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.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.security.access.Permission;
+
+public class QueuePurgeHandler implements StateAwareMethodListener<QueuePurgeBody>
+{
+ private static final QueuePurgeHandler _instance = new QueuePurgeHandler();
+
+ public static QueuePurgeHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private final boolean _failIfNotFound;
+
+ public QueuePurgeHandler()
+ {
+ this(true);
+ }
+
+ public QueuePurgeHandler(boolean failIfNotFound)
+ {
+ _failIfNotFound = failIfNotFound;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueuePurgeBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ VirtualHost virtualHost = session.getVirtualHost();
+ QueueRegistry queueRegistry = virtualHost.getQueueRegistry();
+
+ AMQChannel channel = session.getChannel(channelId);
+
+
+ AMQQueue queue;
+ if(body.getQueue() == null)
+ {
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ //get the default queue on the channel:
+ queue = channel.getDefaultQueue();
+
+ if(queue == null)
+ {
+ if(_failIfNotFound)
+ {
+ throw body.getConnectionException(AMQConstant.NOT_ALLOWED,"No queue specified.");
+ }
+ }
+ }
+ else
+ {
+ queue = queueRegistry.getQueue(body.getQueue());
+ }
+
+ if(queue == null)
+ {
+ if(_failIfNotFound)
+ {
+ throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist.");
+ }
+ }
+ else
+ {
+
+ //Perform ACLs
+ if (!virtualHost.getAccessManager().authorisePurge(session, queue))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+
+ long purged = queue.clearQueue(channel.getStoreContext());
+
+
+ if(!body.getNowait())
+ {
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createQueuePurgeOkBody(purged);
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+ }
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueUnbindHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueUnbindHandler.java
new file mode 100644
index 0000000000..d4272239d1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueUnbindHandler.java
@@ -0,0 +1,137 @@
+package org.apache.qpid.server.handler;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQInvalidRoutingKeyException;
+import org.apache.qpid.protocol.AMQConstant;
+
+public class QueueUnbindHandler implements StateAwareMethodListener<QueueUnbindBody>
+{
+ private static final Logger _log = Logger.getLogger(QueueUnbindHandler.class);
+
+ private static final QueueUnbindHandler _instance = new QueueUnbindHandler();
+
+ public static QueueUnbindHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private QueueUnbindHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueueUnbindBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ VirtualHost virtualHost = session.getVirtualHost();
+ ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry();
+ QueueRegistry queueRegistry = virtualHost.getQueueRegistry();
+
+
+ final AMQQueue queue;
+ final AMQShortString routingKey;
+
+ if (body.getQueue() == null)
+ {
+ AMQChannel channel = session.getChannel(channelId);
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ queue = channel.getDefaultQueue();
+
+ if (queue == null)
+ {
+ throw body.getConnectionException(AMQConstant.NOT_FOUND, "No default queue defined on channel and queue was null");
+ }
+
+ routingKey = body.getRoutingKey() == null ? null : body.getRoutingKey().intern();
+
+ }
+ else
+ {
+ queue = queueRegistry.getQueue(body.getQueue());
+ routingKey = body.getRoutingKey() == null ? null : body.getRoutingKey().intern();
+ }
+
+ if (queue == null)
+ {
+ throw body.getConnectionException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist.");
+ }
+ final Exchange exch = exchangeRegistry.getExchange(body.getExchange());
+ if (exch == null)
+ {
+ throw body.getChannelException(AMQConstant.NOT_FOUND, "Exchange " + body.getExchange() + " does not exist.");
+ }
+
+ //Perform ACLs
+ if (!virtualHost.getAccessManager().authoriseUnbind(session, exch, routingKey, queue))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+
+ try
+ {
+ queue.unBind(exch, routingKey, body.getArguments());
+ }
+ catch (AMQInvalidRoutingKeyException rke)
+ {
+ throw body.getChannelException(AMQConstant.INVALID_ROUTING_KEY, routingKey.toString());
+ }
+ catch (AMQException e)
+ {
+ if(e.getErrorCode() == AMQConstant.NOT_FOUND)
+ {
+ throw body.getConnectionException(AMQConstant.NOT_FOUND,e.getMessage(),e);
+ }
+ throw body.getChannelException(AMQConstant.CHANNEL_ERROR, e.toString());
+ }
+
+ if (_log.isInfoEnabled())
+ {
+ _log.info("Binding queue " + queue + " to exchange " + exch + " with routing key " + routingKey);
+ }
+
+ MethodRegistry_0_9 methodRegistry = (MethodRegistry_0_9) session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createQueueUnbindOkBody();
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl.java
new file mode 100644
index 0000000000..9475b83c8f
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl.java
@@ -0,0 +1,566 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.AMQException;
+
+public class ServerMethodDispatcherImpl implements MethodDispatcher
+{
+ private final AMQStateManager _stateManager;
+
+ private static interface DispatcherFactory
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager);
+ }
+
+ private static final Map<ProtocolVersion, DispatcherFactory> _dispatcherFactories =
+ new HashMap<ProtocolVersion, DispatcherFactory>();
+
+
+ static
+ {
+ _dispatcherFactories.put(ProtocolVersion.v8_0,
+ new DispatcherFactory()
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager)
+ {
+ return new ServerMethodDispatcherImpl_8_0(stateManager);
+ }
+ });
+
+ _dispatcherFactories.put(ProtocolVersion.v0_9,
+ new DispatcherFactory()
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager)
+ {
+ return new ServerMethodDispatcherImpl_0_9(stateManager);
+ }
+ });
+
+ }
+
+
+ private static final AccessRequestHandler _accessRequestHandler = AccessRequestHandler.getInstance();
+ private static final ChannelCloseHandler _channelCloseHandler = ChannelCloseHandler.getInstance();
+ private static final ChannelOpenHandler _channelOpenHandler = ChannelOpenHandler.getInstance();
+ private static final ChannelCloseOkHandler _channelCloseOkHandler = ChannelCloseOkHandler.getInstance();
+ private static final ConnectionCloseMethodHandler _connectionCloseMethodHandler = ConnectionCloseMethodHandler.getInstance();
+ private static final ConnectionCloseOkMethodHandler _connectionCloseOkMethodHandler = ConnectionCloseOkMethodHandler.getInstance();
+ private static final ConnectionOpenMethodHandler _connectionOpenMethodHandler = ConnectionOpenMethodHandler.getInstance();
+ private static final ConnectionTuneOkMethodHandler _connectionTuneOkMethodHandler = ConnectionTuneOkMethodHandler.getInstance();
+ private static final ConnectionSecureOkMethodHandler _connectionSecureOkMethodHandler = ConnectionSecureOkMethodHandler.getInstance();
+ private static final ConnectionStartOkMethodHandler _connectionStartOkMethodHandler = ConnectionStartOkMethodHandler.getInstance();
+ private static final ExchangeDeclareHandler _exchangeDeclareHandler = ExchangeDeclareHandler.getInstance();
+ private static final ExchangeDeleteHandler _exchangeDeleteHandler = ExchangeDeleteHandler.getInstance();
+ private static final ExchangeBoundHandler _exchangeBoundHandler = ExchangeBoundHandler.getInstance();
+ private static final BasicAckMethodHandler _basicAckMethodHandler = BasicAckMethodHandler.getInstance();
+ private static final BasicRecoverMethodHandler _basicRecoverMethodHandler = BasicRecoverMethodHandler.getInstance();
+ private static final BasicConsumeMethodHandler _basicConsumeMethodHandler = BasicConsumeMethodHandler.getInstance();
+ private static final BasicGetMethodHandler _basicGetMethodHandler = BasicGetMethodHandler.getInstance();
+ private static final BasicCancelMethodHandler _basicCancelMethodHandler = BasicCancelMethodHandler.getInstance();
+ private static final BasicPublishMethodHandler _basicPublishMethodHandler = BasicPublishMethodHandler.getInstance();
+ private static final BasicQosHandler _basicQosHandler = BasicQosHandler.getInstance();
+ private static final QueueBindHandler _queueBindHandler = QueueBindHandler.getInstance();
+ private static final QueueDeclareHandler _queueDeclareHandler = QueueDeclareHandler.getInstance();
+ private static final QueueDeleteHandler _queueDeleteHandler = QueueDeleteHandler.getInstance();
+ private static final QueuePurgeHandler _queuePurgeHandler = QueuePurgeHandler.getInstance();
+ private static final ChannelFlowHandler _channelFlowHandler = ChannelFlowHandler.getInstance();
+ private static final TxSelectHandler _txSelectHandler = TxSelectHandler.getInstance();
+ private static final TxCommitHandler _txCommitHandler = TxCommitHandler.getInstance();
+ private static final TxRollbackHandler _txRollbackHandler = TxRollbackHandler.getInstance();
+ private static final BasicRejectMethodHandler _basicRejectMethodHandler = BasicRejectMethodHandler.getInstance();
+
+
+
+ public static MethodDispatcher createMethodDispatcher(AMQStateManager stateManager, ProtocolVersion protocolVersion)
+ {
+ return _dispatcherFactories.get(protocolVersion).createMethodDispatcher(stateManager);
+ }
+
+
+ public ServerMethodDispatcherImpl(AMQStateManager stateManager)
+ {
+ _stateManager = stateManager;
+ }
+
+
+ protected AMQStateManager getStateManager()
+ {
+ return _stateManager;
+ }
+
+
+
+ public boolean dispatchAccessRequest(AccessRequestBody body, int channelId) throws AMQException
+ {
+ _accessRequestHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicAck(BasicAckBody body, int channelId) throws AMQException
+ {
+ _basicAckMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicCancel(BasicCancelBody body, int channelId) throws AMQException
+ {
+ _basicCancelMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicConsume(BasicConsumeBody body, int channelId) throws AMQException
+ {
+ _basicConsumeMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicGet(BasicGetBody body, int channelId) throws AMQException
+ {
+ _basicGetMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicPublish(BasicPublishBody body, int channelId) throws AMQException
+ {
+ _basicPublishMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicQos(BasicQosBody body, int channelId) throws AMQException
+ {
+ _basicQosHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicRecover(BasicRecoverBody body, int channelId) throws AMQException
+ {
+ _basicRecoverMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicReject(BasicRejectBody body, int channelId) throws AMQException
+ {
+ _basicRejectMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelOpen(ChannelOpenBody body, int channelId) throws AMQException
+ {
+ _channelOpenHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchAccessRequestOk(AccessRequestOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicCancelOk(BasicCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicConsumeOk(BasicConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicDeliver(BasicDeliverBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicGetEmpty(BasicGetEmptyBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicGetOk(BasicGetOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicQosOk(BasicQosOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicReturn(BasicReturnBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelClose(ChannelCloseBody body, int channelId) throws AMQException
+ {
+ _channelCloseHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchChannelCloseOk(ChannelCloseOkBody body, int channelId) throws AMQException
+ {
+ _channelCloseOkHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchChannelFlow(ChannelFlowBody body, int channelId) throws AMQException
+ {
+ _channelFlowHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelFlowOk(ChannelFlowOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelOpenOk(ChannelOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+
+ public boolean dispatchConnectionOpen(ConnectionOpenBody body, int channelId) throws AMQException
+ {
+ _connectionOpenMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchConnectionClose(ConnectionCloseBody body, int channelId) throws AMQException
+ {
+ _connectionCloseMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchConnectionCloseOk(ConnectionCloseOkBody body, int channelId) throws AMQException
+ {
+ _connectionCloseOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionOpenOk(ConnectionOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionRedirect(ConnectionRedirectBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionSecure(ConnectionSecureBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionStart(ConnectionStartBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionTune(ConnectionTuneBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchDtxSelectOk(DtxSelectOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchDtxStartOk(DtxStartOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchExchangeBoundOk(ExchangeBoundOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchExchangeDeclareOk(ExchangeDeclareOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchExchangeDeleteOk(ExchangeDeleteOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileCancelOk(FileCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileConsumeOk(FileConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileDeliver(FileDeliverBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileOpen(FileOpenBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileOpenOk(FileOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileQosOk(FileQosOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileReturn(FileReturnBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileStage(FileStageBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueBindOk(QueueBindOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueDeclareOk(QueueDeclareOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueDeleteOk(QueueDeleteOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueuePurgeOk(QueuePurgeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamCancelOk(StreamCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamConsumeOk(StreamConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamDeliver(StreamDeliverBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamQosOk(StreamQosOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamReturn(StreamReturnBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTxCommitOk(TxCommitOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTxRollbackOk(TxRollbackOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTxSelectOk(TxSelectOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+
+ public boolean dispatchConnectionSecureOk(ConnectionSecureOkBody body, int channelId) throws AMQException
+ {
+ _connectionSecureOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionStartOk(ConnectionStartOkBody body, int channelId) throws AMQException
+ {
+ _connectionStartOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionTuneOk(ConnectionTuneOkBody body, int channelId) throws AMQException
+ {
+ _connectionTuneOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchDtxSelect(DtxSelectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchDtxStart(DtxStartBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchExchangeBound(ExchangeBoundBody body, int channelId) throws AMQException
+ {
+ _exchangeBoundHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchExchangeDeclare(ExchangeDeclareBody body, int channelId) throws AMQException
+ {
+ _exchangeDeclareHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchExchangeDelete(ExchangeDeleteBody body, int channelId) throws AMQException
+ {
+ _exchangeDeleteHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchFileAck(FileAckBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileCancel(FileCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileConsume(FileConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFilePublish(FilePublishBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileQos(FileQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileReject(FileRejectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchQueueBind(QueueBindBody body, int channelId) throws AMQException
+ {
+ _queueBindHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueueDeclare(QueueDeclareBody body, int channelId) throws AMQException
+ {
+ _queueDeclareHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueueDelete(QueueDeleteBody body, int channelId) throws AMQException
+ {
+ _queueDeleteHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueuePurge(QueuePurgeBody body, int channelId) throws AMQException
+ {
+ _queuePurgeHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchStreamCancel(StreamCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamConsume(StreamConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamPublish(StreamPublishBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamQos(StreamQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTunnelRequest(TunnelRequestBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTxCommit(TxCommitBody body, int channelId) throws AMQException
+ {
+ _txCommitHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchTxRollback(TxRollbackBody body, int channelId) throws AMQException
+ {
+ _txRollbackHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchTxSelect(TxSelectBody body, int channelId) throws AMQException
+ {
+ _txSelectHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_9.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_9.java
new file mode 100644
index 0000000000..8b1dca77ba
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_9.java
@@ -0,0 +1,164 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+
+import org.apache.qpid.framing.amqp_0_9.MethodDispatcher_0_9;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.AMQException;
+
+
+
+public class ServerMethodDispatcherImpl_0_9
+ extends ServerMethodDispatcherImpl
+ implements MethodDispatcher_0_9
+
+{
+
+ private static final BasicRecoverSyncMethodHandler _basicRecoverSyncMethodHandler =
+ BasicRecoverSyncMethodHandler.getInstance();
+ private static final QueueUnbindHandler _queueUnbindHandler =
+ QueueUnbindHandler.getInstance();
+
+
+ public ServerMethodDispatcherImpl_0_9(AMQStateManager stateManager)
+ {
+ super(stateManager);
+ }
+
+ public boolean dispatchBasicRecoverSync(BasicRecoverSyncBody body, int channelId) throws AMQException
+ {
+ _basicRecoverSyncMethodHandler.methodReceived(getStateManager(), body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicRecoverSyncOk(BasicRecoverSyncOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelOk(ChannelOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPing(ChannelPingBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPong(ChannelPongBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelResume(ChannelResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageAppend(MessageAppendBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCancel(MessageCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCheckpoint(MessageCheckpointBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageClose(MessageCloseBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageConsume(MessageConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageEmpty(MessageEmptyBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageGet(MessageGetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOffset(MessageOffsetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOk(MessageOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOpen(MessageOpenBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageQos(MessageQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageRecover(MessageRecoverBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageReject(MessageRejectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageResume(MessageResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageTransfer(MessageTransferBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchQueueUnbindOk(QueueUnbindOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueUnbind(QueueUnbindBody body, int channelId) throws AMQException
+ {
+ _queueUnbindHandler.methodReceived(getStateManager(),body,channelId);
+ return true;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_8_0.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_8_0.java
new file mode 100644
index 0000000000..d599ca3d4e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_8_0.java
@@ -0,0 +1,86 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.framing.amqp_8_0.MethodDispatcher_8_0;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.AMQException;
+
+public class ServerMethodDispatcherImpl_8_0
+ extends ServerMethodDispatcherImpl
+ implements MethodDispatcher_8_0
+{
+ public ServerMethodDispatcherImpl_8_0(AMQStateManager stateManager)
+ {
+ super(stateManager);
+ }
+
+ public boolean dispatchBasicRecoverOk(BasicRecoverOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelAlert(ChannelAlertBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTestContent(TestContentBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestContentOk(TestContentOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestInteger(TestIntegerBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestIntegerOk(TestIntegerOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestString(TestStringBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestStringOk(TestStringOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestTable(TestTableBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestTableOk(TestTableOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java
new file mode 100644
index 0000000000..9b23d88838
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java
@@ -0,0 +1,78 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.TxCommitBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+public class TxCommitHandler implements StateAwareMethodListener<TxCommitBody>
+{
+ private static final Logger _log = Logger.getLogger(TxCommitHandler.class);
+
+ private static TxCommitHandler _instance = new TxCommitHandler();
+
+ public static TxCommitHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private TxCommitHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, TxCommitBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ try
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Commit received on channel " + channelId);
+ }
+ AMQChannel channel = session.getChannel(channelId);
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ channel.commit();
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createTxCommitOkBody();
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+ channel.processReturns();
+ }
+ catch (AMQException e)
+ {
+ throw body.getChannelException(e.getErrorCode(), "Failed to commit: " + e.getMessage());
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java
new file mode 100644
index 0000000000..5f402f3fda
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java
@@ -0,0 +1,77 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.TxRollbackBody;
+import org.apache.qpid.framing.TxRollbackOkBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+
+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, TxRollbackBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ try
+ {
+ AMQChannel channel = session.getChannel(channelId);
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ channel.rollback();
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createTxRollbackOkBody();
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+
+ //Now resend all the unacknowledged messages back to the original subscribers.
+ //(Must be done after the TxnRollback-ok response).
+ // Why, are we not allowed to send messages back to client before the ok method?
+ channel.resend(false);
+ }
+ catch (AMQException e)
+ {
+ throw body.getChannelException(e.getErrorCode(), "Failed to rollback: " + e.getMessage());
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java
new file mode 100644
index 0000000000..308f5b73cf
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.TxSelectBody;
+import org.apache.qpid.framing.TxSelectOkBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.AMQChannel;
+
+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, TxSelectBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ AMQChannel channel = session.getChannel(channelId);
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ channel.setLocalTransactional();
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ TxSelectOkBody responseBody = methodRegistry.createTxSelectOkBody();
+ session.writeFrame(responseBody.generateFrame(channelId));
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/UnexpectedMethodException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/UnexpectedMethodException.java
new file mode 100644
index 0000000000..fb18519fe1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/UnexpectedMethodException.java
@@ -0,0 +1,33 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.AMQException;
+
+public class UnexpectedMethodException extends AMQException
+{
+ public UnexpectedMethodException(AMQMethodBody body)
+ {
+ super("Unexpected method recevied: " + body.getClass().getName());
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagement.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagement.java
new file mode 100644
index 0000000000..f723ab206c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagement.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.logging.management;
+
+import java.io.IOException;
+
+import org.apache.qpid.server.management.MBeanAttribute;
+import org.apache.qpid.server.management.MBeanOperation;
+import org.apache.qpid.server.management.MBeanOperationParameter;
+
+import javax.management.MBeanOperationInfo;
+import javax.management.openmbean.TabularData;
+
+public interface LoggingManagement
+{
+ String TYPE = "LoggingManagement";
+ int VERSION = 1;
+
+ //TabularType and contained CompositeType key/description information
+ String[] COMPOSITE_ITEM_NAMES = {"LoggerName", "Level"};
+ String[] COMPOSITE_ITEM_DESCRIPTIONS = {"Name of the logger", "Level of the logger"};
+ String[] TABULAR_UNIQUE_INDEX = {COMPOSITE_ITEM_NAMES[0]};
+
+ /**
+ * Attribute to represent the log4j xml configuration file's LogWatch interval.
+ * @return The logwatch interval in seconds.
+ */
+ @MBeanAttribute(name="Log4jLogWatchInterval",
+ description = "The log4j xml configuration file LogWatch interval (in seconds). 0 indicates not being checked.")
+ Integer getLog4jLogWatchInterval();
+
+ /**
+ * Attribute to represent the available log4j logger output levels.
+ * @return The logging level names.
+ */
+ @MBeanAttribute(name="AvailableLoggerLevels", description = "The values to which log output level can be set.")
+ String[] getAvailableLoggerLevels();
+
+
+ //****** log4j runtime operations ****** //
+
+ /**
+ * Sets the level of an active Log4J logger
+ * @param logger The name of the logger
+ * @param level The level to set the logger to
+ * @return True if successful, false if unsuccessful (eg if an invalid level is specified)
+ */
+ @MBeanOperation(name = "setRuntimeLoggerLevel", description = "Set the runtime logging level for an active log4j logger.",
+ impact = MBeanOperationInfo.ACTION)
+ boolean setRuntimeLoggerLevel(@MBeanOperationParameter(name = "logger", description = "Logger name")String logger,
+ @MBeanOperationParameter(name = "level", description = "Logger level")String level);
+
+ /**
+ * Retrieves a TabularData set of the active log4j loggers and their levels
+ * @return TabularData set of CompositeData rows with logger name and level, or null if there is a problem with the TabularData type
+ */
+ @MBeanOperation(name = "viewEffectiveRuntimeLoggerLevels", description = "View the effective runtime logging level " +
+ "for active log4j logger's.", impact = MBeanOperationInfo.INFO)
+ TabularData viewEffectiveRuntimeLoggerLevels();
+
+ /**
+ * Sets the level of the active Log4J RootLogger
+ * @param level The level to set the RootLogger to
+ * @return True if successful, false if unsuccessful (eg if an invalid level is specified)
+ */
+ @MBeanOperation(name = "setRuntimeRootLoggerLevel", description = "Set the runtime logging level for the active log4j Root Logger.",
+ impact = MBeanOperationInfo.ACTION)
+ boolean setRuntimeRootLoggerLevel(@MBeanOperationParameter(name = "level", description = "Logger level")String level);
+
+ /**
+ * Attribute to represent the level of the active Log4J RootLogger
+ * @return The level of the RootLogger.
+ */
+ @MBeanAttribute(name = "getRuntimeRootLoggerLevel", description = "Get the runtime logging level for the active log4j Root Logger.")
+ String getRuntimeRootLoggerLevel();
+
+
+ //****** log4j XML configuration file operations ****** //
+
+ /**
+ * Updates the level of an existing Log4J logger within the xml configuration file
+ * @param logger The name of the logger
+ * @param level The level to set the logger to
+ * @return True if successful, false if unsuccessful (eg if an invalid logger or level is specified)
+ * @throws IOException if there is an error parsing the configuration file.
+ */
+ @MBeanOperation(name = "setConfigFileLoggerLevel", description = "Set the logging level for an existing logger " +
+ "in the log4j xml configuration file", impact = MBeanOperationInfo.ACTION)
+ boolean setConfigFileLoggerLevel(@MBeanOperationParameter(name = "logger", description = "logger name")String logger,
+ @MBeanOperationParameter(name = "level", description = "Logger level")String level) throws IOException;
+
+ /**
+ * Retrieves a TabularData set of the existing Log4J loggers within the xml configuration file
+ * @return TabularData set of CompositeData rows with logger name and level, or null if there is a problem with the TabularData type
+ * @throws IOException if there is an error parsing the configuration file.
+ */
+ @MBeanOperation(name = "viewConfigFileLoggerLevels", description = "Get the logging level defined for the logger's " +
+ "in the log4j xml configuration file.", impact = MBeanOperationInfo.INFO)
+ TabularData viewConfigFileLoggerLevels() throws IOException;
+
+ /**
+ * Updates the level of the Log4J RootLogger within the xml configuration file if it is present
+ * @param level The level to set the logger to
+ * @return True if successful, false if not (eg an invalid level is specified, or root logger level isnt already defined)
+ * @throws IOException if there is an error parsing the configuration file.
+ */
+ @MBeanOperation(name = "setConfigFileRootLoggerLevel", description = "Set the logging level for the Root Logger " +
+ "in the log4j xml configuration file.", impact = MBeanOperationInfo.ACTION)
+ boolean setConfigFileRootLoggerLevel(@MBeanOperationParameter(name = "level", description = "Logger level")String level) throws IOException;
+
+ /**
+ * Attribute to represent the level of the Log4J RootLogger within the xml configuration file
+ * @return The level of the RootLogger, or null if it is not present
+ */
+ @MBeanAttribute(name = "getConfigFileRootLoggerLevel", description = "Get the logging level for the Root Logger " +
+ "in the log4j xml configuration file.")
+ String getConfigFileRootLoggerLevel() throws IOException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java
new file mode 100644
index 0000000000..cd3f85f8ca
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java
@@ -0,0 +1,674 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.logging.management;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.management.AMQManagedObject;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.xml.Log4jEntityResolver;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import javax.management.JMException;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+
+/** MBean class for BrokerLoggingManagerMBean. It implements all the management features exposed for managing logging. */
+@MBeanDescription("Logging Management Interface")
+public class LoggingManagementMBean extends AMQManagedObject implements LoggingManagement
+{
+
+ private static final Logger _logger = Logger.getLogger(LoggingManagementMBean.class);
+ private String _log4jConfigFileName;
+ private int _log4jLogWatchInterval;
+ private static final String[] LEVELS = new String[]{Level.ALL.toString(), Level.TRACE.toString(),
+ Level.DEBUG.toString(), Level.INFO.toString(),
+ Level.WARN.toString(), Level.ERROR.toString(),
+ Level.FATAL.toString(),Level.OFF.toString()};
+ static TabularType _loggerLevelTabularType;
+ static CompositeType _loggerLevelCompositeType;
+
+ static
+ {
+ try
+ {
+ OpenType[] loggerLevelItemTypes = new OpenType[]{SimpleType.STRING, SimpleType.STRING};
+
+ _loggerLevelCompositeType = new CompositeType("LoggerLevelList", "Logger Level Data",
+ COMPOSITE_ITEM_NAMES, COMPOSITE_ITEM_DESCRIPTIONS, loggerLevelItemTypes);
+
+ _loggerLevelTabularType = new TabularType("LoggerLevel", "List of loggers with levels",
+ _loggerLevelCompositeType, TABULAR_UNIQUE_INDEX);
+ }
+ catch (OpenDataException e)
+ {
+ _logger.error("Tabular data setup for viewing logger levels was incorrect.");
+ _loggerLevelTabularType = null;
+ }
+ }
+
+ public LoggingManagementMBean(String log4jConfigFileName, int log4jLogWatchInterval) throws JMException
+ {
+ super(LoggingManagement.class, LoggingManagement.TYPE, LoggingManagement.VERSION);
+ _log4jConfigFileName = log4jConfigFileName;
+ _log4jLogWatchInterval = log4jLogWatchInterval;
+ }
+
+ public String getObjectInstanceName()
+ {
+ return LoggingManagement.TYPE;
+ }
+
+ public Integer getLog4jLogWatchInterval()
+ {
+ return _log4jLogWatchInterval;
+ }
+
+ public String[] getAvailableLoggerLevels()
+ {
+ return LEVELS;
+ }
+ @SuppressWarnings("unchecked")
+ public synchronized boolean setRuntimeLoggerLevel(String logger, String level)
+ {
+ //check specified level is valid
+ Level newLevel;
+ try
+ {
+ newLevel = getLevel(level);
+ }
+ catch (Exception e)
+ {
+ return false;
+ }
+
+ //check specified logger exists
+ Enumeration loggers = LogManager.getCurrentLoggers();
+ Boolean loggerExists = false;
+
+ while(loggers.hasMoreElements())
+ {
+ Logger log = (Logger) loggers.nextElement();
+ if (log.getName().equals(logger))
+ {
+ loggerExists = true;
+ break;
+ }
+ }
+
+ if(!loggerExists)
+ {
+ return false;
+ }
+
+ //set the logger to the new level
+ _logger.info("Setting level to " + level + " for logger: " + logger);
+
+ Logger log = Logger.getLogger(logger);
+ log.setLevel(newLevel);
+
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized TabularData viewEffectiveRuntimeLoggerLevels()
+ {
+ if (_loggerLevelTabularType == null)
+ {
+ _logger.warn("TabluarData type not set up correctly");
+ return null;
+ }
+
+ _logger.info("Getting levels for currently active log4j loggers");
+
+ Enumeration loggers = LogManager.getCurrentLoggers();
+
+ TabularData loggerLevelList = new TabularDataSupport(_loggerLevelTabularType);
+
+ Logger logger;
+ String loggerName;
+ String level;
+
+ try
+ {
+ while(loggers.hasMoreElements()){
+ logger = (Logger) loggers.nextElement();
+
+ loggerName = logger.getName();
+ level = logger.getEffectiveLevel().toString();
+
+ Object[] itemData = {loggerName, level};
+ CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, COMPOSITE_ITEM_NAMES, itemData);
+ loggerLevelList.put(loggerData);
+ }
+ }
+ catch (OpenDataException e)
+ {
+ _logger.warn("Unable to create logger level list due to :" + e);
+ return null;
+ }
+
+ return loggerLevelList;
+
+ }
+
+ public synchronized String getRuntimeRootLoggerLevel()
+ {
+ Logger rootLogger = Logger.getRootLogger();
+
+ return rootLogger.getLevel().toString();
+ }
+
+ public synchronized boolean setRuntimeRootLoggerLevel(String level)
+ {
+ Level newLevel;
+ try
+ {
+ newLevel = getLevel(level);
+ }
+ catch (Exception e)
+ {
+ return false;
+ }
+
+ _logger.info("Setting RootLogger level to " + level);
+
+ Logger log = Logger.getRootLogger();
+ log.setLevel(newLevel);
+
+ return true;
+ }
+
+ //method to convert from a string to a log4j Level, throws exception if the given value is invalid
+ private Level getLevel(String level) throws Exception
+ {
+ Level newLevel = Level.toLevel(level);
+
+ //above Level.toLevel call returns a DEBUG Level if the request fails. Check the result.
+ if (newLevel.equals(Level.DEBUG) && !(level.equalsIgnoreCase("debug")))
+ {
+ //received DEBUG but we did not ask for it, the Level request failed.
+ throw new Exception("Invalid level name");
+ }
+
+ return newLevel;
+ }
+
+ //handler to catch errors signalled by the JAXP parser and throw an appropriate exception
+ private class SaxErrorHandler implements ErrorHandler
+ {
+
+ public void error(SAXParseException e) throws SAXException
+ {
+ throw new SAXException("Error parsing XML file: " + e.getMessage());
+ }
+
+ public void fatalError(SAXParseException e) throws SAXException
+ {
+ throw new SAXException("Fatal error parsing XML file: " + e.getMessage());
+ }
+
+ public void warning(SAXParseException e) throws SAXException
+ {
+ throw new SAXException("Warning parsing XML file: " + e.getMessage());
+ }
+ }
+
+ //method to parse the XML configuration file, validating it in the process, and returning a DOM Document of the content.
+ private synchronized Document parseConfigFile(String fileName) throws IOException
+ {
+ //check file was specified, exists, and is readable
+ if(fileName == null)
+ {
+ _logger.warn("No log4j XML configuration file has been set");
+ throw new IOException("No log4j XML configuration file has been set");
+ }
+
+ File configFile = new File(fileName);
+
+ if (!configFile.exists())
+ {
+ _logger.warn("Specified log4j XML configuration file does not exist: " + fileName);
+ throw new IOException("Specified log4j XML configuration file does not exist");
+ }
+ else if (!configFile.canRead())
+ {
+ _logger.warn("Specified log4j XML configuration file is not readable: " + fileName);
+ throw new IOException("Specified log4j XML configuration file is not readable");
+ }
+
+ //parse it
+ DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder docBuilder;
+ Document doc;
+
+ ErrorHandler errHandler = new SaxErrorHandler();
+ try
+ {
+ docFactory.setValidating(true);
+ docBuilder = docFactory.newDocumentBuilder();
+ docBuilder.setErrorHandler(errHandler);
+ docBuilder.setEntityResolver(new Log4jEntityResolver());
+ doc = docBuilder.parse(fileName);
+ }
+ catch (ParserConfigurationException e)
+ {
+ _logger.warn("Unable to parse the log4j XML file due to possible configuration error: " + e);
+ //recommended that MBeans should use java.* and javax.* exceptions only
+ throw new IOException("Unable to parse the log4j XML file due to possible configuration error: " + e.getMessage());
+ }
+ catch (SAXException e)
+ {
+ _logger.warn("The specified log4j XML file is invalid: " + e);
+ //recommended that MBeans should use standard java.* and javax.* exceptions only
+ throw new IOException("The specified log4j XML file is invalid: " + e.getMessage());
+ }
+ catch (IOException e)
+ {
+ _logger.warn("Unable to parse the specified log4j XML file" + e);
+ throw new IOException("Unable to parse the specified log4j XML file", e);
+ }
+
+ return doc;
+ }
+
+
+ private synchronized boolean writeUpdatedConfigFile(String log4jConfigFileName, Document doc) throws IOException
+ {
+ File log4jConfigFile = new File(log4jConfigFileName);
+
+ if (!log4jConfigFile.canWrite())
+ {
+ _logger.warn("Specified log4j XML configuration file is not writable: " + log4jConfigFile);
+ throw new IOException("Specified log4j XML configuration file is not writable");
+ }
+
+ Transformer transformer = null;
+ try
+ {
+ transformer = TransformerFactory.newInstance().newTransformer();
+ }
+ catch (Exception e)
+ {
+ _logger.warn("Could not create an XML transformer: " +e);
+ return false;
+ }
+
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "log4j.dtd");
+ DOMSource source = new DOMSource(doc);
+
+ File tmp;
+ try
+ {
+ tmp = File.createTempFile("LogManMBeanTemp", ".tmp");
+ tmp.deleteOnExit();
+ StreamResult result = new StreamResult(tmp);
+ transformer.transform(source, result);
+ }
+ catch (TransformerException e)
+ {
+ _logger.warn("Could not transform the XML into new file: " +e);
+ return false;
+ }
+ catch (IOException e)
+ {
+ _logger.warn("Could not create the new file: " +e);
+ return false;
+ }
+
+ // Swap temp file in to replace existing configuration file.
+ File old = new File(log4jConfigFile.getAbsoluteFile() + ".old");
+ if (old.exists())
+ {
+ old.delete();
+ }
+ log4jConfigFile.renameTo(old);
+ return tmp.renameTo(log4jConfigFile);
+ }
+
+
+ /* The log4j XML configuration file DTD defines three possible element
+ * combinations for specifying optional logger+level settings.
+ * Must account for the following:
+ *
+ * <category name="x"> <priority value="y"/> </category> OR
+ * <category name="x"> <level value="y"/> </category> OR
+ * <logger name="x"> <level value="y"/> </logger>
+ *
+ * Noting also that the level/priority child element is optional too,
+ * and not the only possible child element.
+ */
+
+
+ public synchronized TabularData viewConfigFileLoggerLevels() throws IOException
+ {
+ if (_loggerLevelTabularType == null)
+ {
+ _logger.warn("TabluarData type not set up correctly");
+ return null;
+ }
+
+ _logger.info("Getting logger levels from log4j configuration file");
+
+ Document doc = parseConfigFile(_log4jConfigFileName);
+
+ TabularData loggerLevelList = new TabularDataSupport(_loggerLevelTabularType);
+
+ //retrieve the 'category' element nodes
+ NodeList categoryElements = doc.getElementsByTagName("category");
+
+ String categoryName;
+ String priority = null;
+
+ for (int i = 0; i < categoryElements.getLength(); i++)
+ {
+ Element categoryElement = (Element) categoryElements.item(i);
+ categoryName = categoryElement.getAttribute("name");
+
+ //retrieve the category's mandatory 'priority' or 'level' element's value.
+ //It may not be the only child node, so request by tag name.
+ NodeList priorityElements = categoryElement.getElementsByTagName("priority");
+ NodeList levelElements = categoryElement.getElementsByTagName("level");
+
+ if (priorityElements.getLength() != 0)
+ {
+ Element priorityElement = (Element) priorityElements.item(0);
+ priority = priorityElement.getAttribute("value").toUpperCase();
+ }
+ else if (levelElements.getLength() != 0)
+ {
+ Element levelElement = (Element) levelElements.item(0);
+ priority = levelElement.getAttribute("value").toUpperCase();
+ }
+ else
+ {
+ //there is no exiting priority or level to view, move onto next category/logger
+ continue;
+ }
+
+ try
+ {
+ Object[] itemData = {categoryName, priority};
+ CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, COMPOSITE_ITEM_NAMES, itemData);
+ loggerLevelList.put(loggerData);
+ }
+ catch (OpenDataException e)
+ {
+ _logger.warn("Unable to create logger level list due to :" + e);
+ return null;
+ }
+ }
+
+ //retrieve the 'logger' element nodes
+ NodeList loggerElements = doc.getElementsByTagName("logger");
+
+ String loggerName;
+ String level;
+
+ for (int i = 0; i < loggerElements.getLength(); i++)
+ {
+ Element loggerElement = (Element) loggerElements.item(i);
+ loggerName = loggerElement.getAttribute("name");
+
+ //retrieve the logger's mandatory 'level' element's value
+ //It may not be the only child node, so request by tag name.
+ NodeList levelElements = loggerElement.getElementsByTagName("level");
+
+ Element levelElement = (Element) levelElements.item(0);
+ level = levelElement.getAttribute("value").toUpperCase();
+
+ try
+ {
+ Object[] itemData = {loggerName, level};
+ CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, COMPOSITE_ITEM_NAMES, itemData);
+ loggerLevelList.put(loggerData);
+ }
+ catch (OpenDataException e)
+ {
+ _logger.warn("Unable to create logger level list due to :" + e);
+ return null;
+ }
+ }
+
+ return loggerLevelList;
+ }
+
+ public synchronized boolean setConfigFileLoggerLevel(String logger, String level) throws IOException
+ {
+ //check that the specified level is a valid log4j Level
+ try
+ {
+ getLevel(level);
+ }
+ catch (Exception e)
+ {
+ //it isnt a valid level
+ return false;
+ }
+
+ _logger.info("Setting level to " + level + " for logger '" + logger
+ + "' in log4j xml configuration file: " + _log4jConfigFileName);
+
+ Document doc = parseConfigFile(_log4jConfigFileName);
+
+ //retrieve the 'category' and 'logger' element nodes
+ NodeList categoryElements = doc.getElementsByTagName("category");
+ NodeList loggerElements = doc.getElementsByTagName("logger");
+
+ //collect them into a single elements list
+ List<Element> logElements = new ArrayList<Element>();
+
+ for (int i = 0; i < categoryElements.getLength(); i++)
+ {
+ logElements.add((Element) categoryElements.item(i));
+ }
+ for (int i = 0; i < loggerElements.getLength(); i++)
+ {
+ logElements.add((Element) loggerElements.item(i));
+ }
+
+ //try to locate the specified logger/category in the elements retrieved
+ Element logElement = null;
+ for (Element e : logElements)
+ {
+ if (e.getAttribute("name").equals(logger))
+ {
+ logElement = e;
+ break;
+ }
+ }
+
+ if (logElement == null)
+ {
+ //no loggers/categories with given name found, does not exist to update
+ _logger.warn("Specified logger does not exist in the configuration file: " +logger);
+ return false;
+ }
+
+ //retrieve the optional 'priority' or 'level' sub-element value.
+ //It may not be the only child node, so request by tag name.
+ NodeList priorityElements = logElement.getElementsByTagName("priority");
+ NodeList levelElements = logElement.getElementsByTagName("level");
+
+ Element levelElement = null;
+ if (priorityElements.getLength() != 0)
+ {
+ levelElement = (Element) priorityElements.item(0);
+ }
+ else if (levelElements.getLength() != 0)
+ {
+ levelElement = (Element) levelElements.item(0);
+ }
+ else
+ {
+ //there is no exiting priority or level element to update
+ return false;
+ }
+
+ //update the element with the new level/priority
+ levelElement.setAttribute("value", level);
+
+ //output the new file
+ return writeUpdatedConfigFile(_log4jConfigFileName, doc);
+ }
+
+
+ /* The log4j XML configuration file DTD defines 2 possible element
+ * combinations for specifying the optional root logger level settings
+ * Must account for the following:
+ *
+ * <root> <priority value="y"/> </root> OR
+ * <root> <level value="y"/> </root>
+ *
+ * Noting also that the level/priority child element is optional too,
+ * and not the only possible child element.
+ */
+
+ public synchronized String getConfigFileRootLoggerLevel() throws IOException
+ {
+ _logger.info("Getting root logger level from log4j configuration file");
+
+ Document doc = parseConfigFile(_log4jConfigFileName);
+
+ //retrieve the optional 'root' element node
+ NodeList rootElements = doc.getElementsByTagName("root");
+
+ if (rootElements.getLength() == 0)
+ {
+ //there is not root logger definition
+ return null;
+ }
+
+ Element rootElement = (Element) rootElements.item(0);
+
+ //retrieve the optional 'priority' or 'level' element value.
+ //It may not be the only child node, so request by tag name.
+ NodeList priorityElements = rootElement.getElementsByTagName("priority");
+ NodeList levelElements = rootElement.getElementsByTagName("level");
+ String priority = null;
+
+ if (priorityElements.getLength() != 0)
+ {
+ Element priorityElement = (Element) priorityElements.item(0);
+ priority = priorityElement.getAttribute("value");
+ }
+ else if(levelElements.getLength() != 0)
+ {
+ Element levelElement = (Element) levelElements.item(0);
+ priority = levelElement.getAttribute("value");
+ }
+
+ if(priority != null)
+ {
+ return priority.toUpperCase();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public synchronized boolean setConfigFileRootLoggerLevel(String level) throws IOException
+ {
+ //check that the specified level is a valid log4j Level
+ try
+ {
+ getLevel(level);
+ }
+ catch (Exception e)
+ {
+ //it isnt a valid level
+ return false;
+ }
+
+ _logger.info("Setting level to " + level + " for the Root logger in " +
+ "log4j xml configuration file: " + _log4jConfigFileName);
+
+ Document doc = parseConfigFile(_log4jConfigFileName);
+
+ //retrieve the optional 'root' element node
+ NodeList rootElements = doc.getElementsByTagName("root");
+
+ if (rootElements.getLength() == 0)
+ {
+ return false;
+ }
+
+ Element rootElement = (Element) rootElements.item(0);
+
+ //retrieve the optional 'priority' or 'level' sub-element value.
+ //It may not be the only child node, so request by tag name.
+ NodeList priorityElements = rootElement.getElementsByTagName("priority");
+ NodeList levelElements = rootElement.getElementsByTagName("level");
+
+ Element levelElement = null;
+ if (priorityElements.getLength() != 0)
+ {
+ levelElement = (Element) priorityElements.item(0);
+ }
+ else if (levelElements.getLength() != 0)
+ {
+ levelElement = (Element) levelElements.item(0);
+ }
+ else
+ {
+ //there is no exiting priority/level to update
+ return false;
+ }
+
+ //update the element with the new level/priority
+ levelElement.setAttribute("value", level);
+
+ //output the new file
+ return writeUpdatedConfigFile(_log4jConfigFileName, doc);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java
new file mode 100644
index 0000000000..c6e07f6f48
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java
@@ -0,0 +1,97 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.management;
+
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.NotCompliantMBeanException;
+import javax.management.NotificationBroadcaster;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+
+/**
+ * This class provides additinal feature of Notification Broadcaster to the
+ * DefaultManagedObject.
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+public abstract class AMQManagedObject extends DefaultManagedObject
+ implements NotificationBroadcaster
+{
+ /**
+ * broadcaster support class
+ */
+ protected NotificationBroadcasterSupport _broadcaster = new NotificationBroadcasterSupport();
+
+ /**
+ * sequence number for notifications
+ */
+ protected long _notificationSequenceNumber = 0;
+
+ protected MBeanInfo _mbeanInfo;
+
+ protected AMQManagedObject(Class<?> managementInterface, String typeName, int version)
+ throws NotCompliantMBeanException
+ {
+ super(managementInterface, typeName, version);
+ 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/qpid/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java
new file mode 100644
index 0000000000..67aee90ba4
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java
@@ -0,0 +1,200 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.management;
+
+import javax.management.JMException;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectName;
+import javax.management.StandardMBean;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+/**
+ * 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;
+ private int _version;
+
+ protected DefaultManagedObject(Class<?> managementInterface, String typeName, int version)
+ throws NotCompliantMBeanException
+ {
+ super(managementInterface);
+ _managementInterface = managementInterface;
+ _typeName = typeName;
+ _version = version;
+ }
+
+ public String getType()
+ {
+ return _typeName;
+ }
+
+ public Class<?> getManagementInterface()
+ {
+ return _managementInterface;
+ }
+
+ public ManagedObject getParentObject()
+ {
+ return null;
+ }
+
+ public void register() throws AMQException
+ {
+ try
+ {
+ getManagedObjectRegistry().registerObject(this);
+ }
+ catch (JMException e)
+ {
+ throw new AMQException("Error registering managed object " + this + ": " + e, e);
+ }
+ }
+
+ protected ManagedObjectRegistry getManagedObjectRegistry()
+ {
+ return ApplicationRegistry.getInstance().getManagedObjectRegistry();
+ }
+
+ public void unregister() throws AMQException
+ {
+ try
+ {
+ 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);
+
+ objectName.append(",");
+ objectName.append("version=").append(_version);
+
+
+ return new ObjectName(objectName.toString());
+ }
+
+ protected ObjectName getObjectNameForSingleInstanceMBean() throws MalformedObjectNameException
+ {
+ StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN);
+
+ objectName.append(":type=");
+ objectName.append(getHierarchicalType(this));
+
+ String hierarchyName = getHierarchicalName(this);
+ if (hierarchyName != null)
+ {
+ objectName.append(",");
+ objectName.append(hierarchyName.substring(0, hierarchyName.lastIndexOf(",")));
+ }
+
+ objectName.append(",");
+ objectName.append("version=").append(_version);
+
+ return new ObjectName(objectName.toString());
+ }
+
+ protected String getHierarchicalType(ManagedObject obj)
+ {
+ if (obj.getParentObject() != null)
+ {
+ String parentType = getHierarchicalType(obj.getParentObject()).toString();
+ return parentType + "." + obj.getType();
+ }
+ else
+ return obj.getType();
+ }
+
+ protected String getHierarchicalName(ManagedObject obj)
+ {
+ if (obj.getParentObject() != null)
+ {
+ String 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;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java
new file mode 100644
index 0000000000..5a113de5be
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java
@@ -0,0 +1,371 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.management;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+import org.apache.qpid.server.security.auth.rmi.RMIPasswordAuthenticator;
+
+import javax.management.JMException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.MBeanServerForwarder;
+import javax.management.remote.rmi.RMIConnectorServer;
+import javax.management.remote.rmi.RMIJRMPServerImpl;
+import javax.management.remote.rmi.RMIServerImpl;
+import javax.rmi.ssl.SslRMIClientSocketFactory;
+import javax.rmi.ssl.SslRMIServerSocketFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.rmi.AlreadyBoundException;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class starts up an MBeanserver. If out of the box agent has been enabled then there are no
+ * security features implemented like user authentication and authorisation.
+ */
+public class JMXManagedObjectRegistry implements ManagedObjectRegistry
+{
+ private static final Logger _log = Logger.getLogger(JMXManagedObjectRegistry.class);
+ private static final Logger _startupLog = Logger.getLogger("Qpid.Broker");
+
+ public static final String MANAGEMENT_PORT_CONFIG_PATH = "management.jmxport";
+ public static final int MANAGEMENT_PORT_DEFAULT = 8999;
+ public static final int PORT_EXPORT_OFFSET = 100;
+
+ private final MBeanServer _mbeanServer;
+ private Registry _rmiRegistry;
+
+
+ public JMXManagedObjectRegistry() throws AMQException
+ {
+ _log.info("Initialising managed object registry using platform MBean server");
+ IApplicationRegistry appRegistry = ApplicationRegistry.getInstance();
+
+ // Retrieve the config parameters
+ boolean platformServer = appRegistry.getConfiguration().getPlatformMbeanserver();
+
+ _mbeanServer =
+ platformServer ? ManagementFactory.getPlatformMBeanServer()
+ : MBeanServerFactory.createMBeanServer(ManagedObject.DOMAIN);
+ }
+
+
+ public void start() throws IOException, ConfigurationException
+ {
+ //check if system properties are set to use the JVM's out-of-the-box JMXAgent
+ if (areOutOfTheBoxJMXOptionsSet())
+ {
+ _log.warn("JMX: Using the out of the box JMX Agent");
+ _startupLog.warn("JMX: Using the out of the box JMX Agent");
+ return;
+ }
+
+ IApplicationRegistry appRegistry = ApplicationRegistry.getInstance();
+ int port = appRegistry.getConfiguration().getJMXManagementPort();
+
+ //retrieve the Principal Database assigned to JMX authentication duties
+ String jmxDatabaseName = appRegistry.getConfiguration().getJMXPrincipalDatabase();
+ Map<String, PrincipalDatabase> map = appRegistry.getDatabaseManager().getDatabases();
+ PrincipalDatabase db = map.get(jmxDatabaseName);
+
+ final JMXConnectorServer cs;
+ HashMap<String,Object> env = new HashMap<String,Object>();
+
+ //Socket factories for the RMIConnectorServer, either default or SLL depending on configuration
+ RMIClientSocketFactory csf;
+ RMIServerSocketFactory ssf;
+
+ //check ssl enabled option in config, default to true if option is not set
+ boolean sslEnabled = appRegistry.getConfiguration().getManagementSSLEnabled();
+
+ if (sslEnabled)
+ {
+ //set the SSL related system properties used by the SSL RMI socket factories to the values
+ //given in the configuration file, unless command line settings have already been specified
+ String keyStorePath;
+
+ if(System.getProperty("javax.net.ssl.keyStore") != null)
+ {
+ keyStorePath = System.getProperty("javax.net.ssl.keyStore");
+ }
+ else
+ {
+ keyStorePath = appRegistry.getConfiguration().getManagementKeyStorePath();
+ }
+
+ //check the keystore path value is valid
+ if (keyStorePath == null)
+ {
+ throw new ConfigurationException("JMX management SSL keystore path not defined, " +
+ "unable to start SSL protected JMX ConnectorServer");
+ }
+ else
+ {
+ //ensure the system property is set
+ System.setProperty("javax.net.ssl.keyStore", keyStorePath);
+
+ //check the file is usable
+ File ksf = new File(keyStorePath);
+
+ if (!ksf.exists())
+ {
+ throw new FileNotFoundException("Cannot find JMX management SSL keystore file " + ksf + "\n"
+ + "Check broker configuration, or see create-example-ssl-stores script"
+ + "in the bin/ directory if you need to generate an example store.");
+ }
+ if (!ksf.canRead())
+ {
+ throw new FileNotFoundException("Cannot read JMX management SSL keystore file: "
+ + ksf + ". Check permissions.");
+ }
+
+ _log.info("JMX ConnectorServer using SSL keystore file " + ksf.getAbsolutePath());
+ _startupLog.info("JMX ConnectorServer using SSL keystore file " + ksf.getAbsolutePath());
+ }
+
+ //check the key store password is set
+ if (System.getProperty("javax.net.ssl.keyStorePassword") == null)
+ {
+
+ if (appRegistry.getConfiguration().getManagementKeyStorePassword() == null)
+ {
+ throw new ConfigurationException("JMX management SSL keystore password not defined, " +
+ "unable to start requested SSL protected JMX server");
+ }
+ else
+ {
+ System.setProperty("javax.net.ssl.keyStorePassword",
+ appRegistry.getConfiguration().getManagementKeyStorePassword());
+ }
+ }
+
+ //create the SSL RMI socket factories
+ csf = new SslRMIClientSocketFactory();
+ ssf = new SslRMIServerSocketFactory();
+
+ _log.warn("Starting JMX ConnectorServer on port '"+ port + "' (+" +
+ (port +PORT_EXPORT_OFFSET) + ") with SSL");
+ _startupLog.warn("Starting JMX ConnectorServer on port '"+ port + "' (+" +
+ (port +PORT_EXPORT_OFFSET) + ") with SSL");
+ }
+ else
+ {
+ //Do not specify any specific RMI socket factories, resulting in use of the defaults.
+ csf = null;
+ ssf = null;
+
+ _log.warn("Starting JMX ConnectorServer on port '" + port + "' (+" + (port +PORT_EXPORT_OFFSET) + ")");
+ _startupLog.warn("Starting JMX ConnectorServer on port '" + port + "' (+" + (port +PORT_EXPORT_OFFSET) + ")");
+ }
+
+ //add a JMXAuthenticator implementation the env map to authenticate the RMI based JMX connector server
+ RMIPasswordAuthenticator rmipa = new RMIPasswordAuthenticator();
+ rmipa.setPrincipalDatabase(db);
+ env.put(JMXConnectorServer.AUTHENTICATOR, rmipa);
+
+ /*
+ * Start a RMI registry on the management port, to hold the JMX RMI ConnectorServer stub.
+ * Using custom socket factory to prevent anyone (including us unfortunately) binding to the registry using RMI.
+ * As a result, only binds made using the object reference will succeed, thus securing it from external change.
+ */
+ System.setProperty("java.rmi.server.randomIDs", "true");
+ _rmiRegistry = LocateRegistry.createRegistry(port, null, new CustomRMIServerSocketFactory());
+
+ /*
+ * We must now create the RMI ConnectorServer manually, as the JMX Factory methods use RMI calls
+ * to bind the ConnectorServer to the registry, which will now fail as for security we have
+ * locked it from any RMI based modifications, including our own. Instead, we will manually bind
+ * the RMIConnectorServer stub to the registry using its object reference, which will still succeed.
+ *
+ * The registry is exported on the defined management port 'port'. We will export the RMIConnectorServer
+ * on 'port +1'. Use of these two well-defined ports will ease any navigation through firewall's.
+ */
+ final RMIServerImpl rmiConnectorServerStub = new RMIJRMPServerImpl(port+PORT_EXPORT_OFFSET, csf, ssf, env);
+ final String hostname = InetAddress.getLocalHost().getHostName();
+ final JMXServiceURL externalUrl = new JMXServiceURL(
+ "service:jmx:rmi://"+hostname+":"+(port+PORT_EXPORT_OFFSET)+"/jndi/rmi://"+hostname+":"+port+"/jmxrmi");
+
+ final JMXServiceURL internalUrl = new JMXServiceURL("rmi", hostname, port+PORT_EXPORT_OFFSET);
+ cs = new RMIConnectorServer(internalUrl, env, rmiConnectorServerStub, _mbeanServer)
+ {
+ @Override
+ public synchronized void start() throws IOException
+ {
+ try
+ {
+ //manually bind the connector server to the registry at key 'jmxrmi', like the out-of-the-box agent
+ _rmiRegistry.bind("jmxrmi", rmiConnectorServerStub);
+ }
+ catch (AlreadyBoundException abe)
+ {
+ //key was already in use. shouldnt happen here as its a new registry, unbindable by normal means.
+
+ //IOExceptions are the only checked type throwable by the method, wrap and rethrow
+ IOException ioe = new IOException(abe.getMessage());
+ ioe.initCause(abe);
+ throw ioe;
+ }
+
+ //now do the normal tasks
+ super.start();
+ }
+
+ @Override
+ public JMXServiceURL getAddress()
+ {
+ //must return our pre-crafted url that includes the full details, inc JNDI details
+ return externalUrl;
+ }
+
+ };
+
+
+ //Add the custom invoker as an MBeanServerForwarder, and start the RMIConnectorServer.
+ MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance();
+ cs.setMBeanServerForwarder(mbsf);
+ cs.start();
+ }
+
+ /*
+ * Custom RMIServerSocketFactory class, used to prevent updates to the RMI registry.
+ * Supplied to the registry at creation, this will prevent RMI-based operations on the
+ * registry such as attempting to bind a new object, thereby securing it from tampering.
+ * This is accomplished by always returning null when attempting to determine the address
+ * of the caller, thus ensuring the registry will refuse the attempt. Calls to bind etc
+ * made using the object reference will not be affected and continue to operate normally.
+ */
+
+ private class CustomRMIServerSocketFactory implements RMIServerSocketFactory
+ {
+
+ public ServerSocket createServerSocket(int port) throws IOException
+ {
+ return new NoLocalAddressServerSocket(port);
+ }
+
+ private class NoLocalAddressServerSocket extends ServerSocket
+ {
+ NoLocalAddressServerSocket(int port) throws IOException
+ {
+ super(port);
+ }
+
+ @Override
+ public Socket accept() throws IOException
+ {
+ Socket s = new NoLocalAddressSocket();
+ super.implAccept(s);
+ return s;
+ }
+ }
+
+ private class NoLocalAddressSocket extends Socket
+ {
+ @Override
+ public InetAddress getInetAddress()
+ {
+ return null;
+ }
+ }
+ }
+
+
+ public void registerObject(ManagedObject managedObject) throws JMException
+ {
+ _mbeanServer.registerMBean(managedObject, managedObject.getObjectName());
+ }
+
+ public void unregisterObject(ManagedObject managedObject) throws JMException
+ {
+ _mbeanServer.unregisterMBean(managedObject.getObjectName());
+ }
+
+ // checks if the system properties are set which enable the JVM's out-of-the-box JMXAgent.
+ private boolean areOutOfTheBoxJMXOptionsSet()
+ {
+ if (System.getProperty("com.sun.management.jmxremote") != null)
+ {
+ return true;
+ }
+
+ if (System.getProperty("com.sun.management.jmxremote.port") != null)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ // stops the RMIRegistry and unregisters the MBeans from the MBeanServer
+ public void close() throws RemoteException
+ {
+ if (_rmiRegistry != null)
+ {
+ // Stopping the RMI registry
+ UnicastRemoteObject.unexportObject(_rmiRegistry, true);
+ }
+
+ //ObjectName query to gather all Qpid related MBeans
+ ObjectName mbeanNameQuery = null;
+ try
+ {
+ mbeanNameQuery = new ObjectName(ManagedObject.DOMAIN + ":*");
+ }
+ catch (Exception e1)
+ {
+ _log.warn("Unable to generate MBean ObjectName query for close operation");
+ }
+
+ for (ObjectName name : _mbeanServer.queryNames(mbeanNameQuery, null))
+ {
+ try
+ {
+ _mbeanServer.unregisterMBean(name);
+ }
+ catch (JMException e)
+ {
+ _log.error("Exception unregistering MBean '"+ name +"': " + e.getMessage());
+ }
+ }
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java
new file mode 100644
index 0000000000..7d42297699
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+
+package org.apache.qpid.server.management;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for MBean attributes. This should be used with getter or setter
+ * methods of attributes.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Inherited
+public @interface MBeanAttribute
+{
+ String name();
+ String description();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java
new file mode 100644
index 0000000000..9138e03085
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+
+package org.apache.qpid.server.management;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for MBean constructors.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.CONSTRUCTOR)
+@Inherited
+public @interface MBeanConstructor
+{
+ String value();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java
new file mode 100644
index 0000000000..448fed3280
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+
+package org.apache.qpid.server.management;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for MBean class.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface MBeanDescription {
+ String value();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java
new file mode 100644
index 0000000000..0c2ec2aebd
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java
@@ -0,0 +1,388 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.management;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.NotCompliantMBeanException;
+
+/**
+ * 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())
+ {
+ 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;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java
new file mode 100644
index 0000000000..e9b4d85e66
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java
@@ -0,0 +1,257 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.management;
+
+import org.apache.qpid.server.configuration.management.ConfigurationManagement;
+import org.apache.qpid.server.logging.management.LoggingManagement;
+import org.apache.qpid.server.security.access.management.UserManagement;
+import org.apache.log4j.Logger;
+
+import javax.management.remote.MBeanServerForwarder;
+import javax.management.remote.JMXPrincipal;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.MBeanInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.JMException;
+import javax.security.auth.Subject;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.Principal;
+import java.security.AccessControlContext;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Properties;
+
+/**
+ * This class can be used by the JMXConnectorServer as an InvocationHandler for the mbean operations. This implements
+ * the logic for allowing the users to invoke MBean operations and implements the restrictions for readOnly, readWrite
+ * and admin users.
+ */
+public class MBeanInvocationHandlerImpl implements InvocationHandler
+{
+ private static final Logger _logger = Logger.getLogger(MBeanInvocationHandlerImpl.class);
+
+ public final static String ADMIN = "admin";
+ public final static String READWRITE = "readwrite";
+ public final static String READONLY = "readonly";
+ private final static String DELEGATE = "JMImplementation:type=MBeanServerDelegate";
+ private MBeanServer _mbs;
+ private static Properties _userRoles = new Properties();
+
+ private static HashSet<String> _adminOnlyMethods = new HashSet<String>();
+ {
+ _adminOnlyMethods.add(UserManagement.TYPE);
+ _adminOnlyMethods.add(LoggingManagement.TYPE);
+ _adminOnlyMethods.add(ConfigurationManagement.TYPE);
+ }
+
+ public static MBeanServerForwarder newProxyInstance()
+ {
+ final InvocationHandler handler = new MBeanInvocationHandlerImpl();
+ final Class[] interfaces = new Class[]{MBeanServerForwarder.class};
+
+ Object proxy = Proxy.newProxyInstance(MBeanServerForwarder.class.getClassLoader(), interfaces, handler);
+ return MBeanServerForwarder.class.cast(proxy);
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ final String methodName = method.getName();
+
+ if (methodName.equals("getMBeanServer"))
+ {
+ return _mbs;
+ }
+
+ if (methodName.equals("setMBeanServer"))
+ {
+ if (args[0] == null)
+ {
+ throw new IllegalArgumentException("Null MBeanServer");
+ }
+ if (_mbs != null)
+ {
+ throw new IllegalArgumentException("MBeanServer object already initialized");
+ }
+ _mbs = (MBeanServer) args[0];
+ return null;
+ }
+
+ // Retrieve Subject from current AccessControlContext
+ AccessControlContext acc = AccessController.getContext();
+ Subject subject = Subject.getSubject(acc);
+
+ // Allow operations performed locally on behalf of the connector server itself
+ if (subject == null)
+ {
+ return method.invoke(_mbs, args);
+ }
+
+ if (args == null || DELEGATE.equals(args[0]))
+ {
+ return method.invoke(_mbs, args);
+ }
+
+ // Restrict access to "createMBean" and "unregisterMBean" to any user
+ if (methodName.equals("createMBean") || methodName.equals("unregisterMBean"))
+ {
+ _logger.debug("User trying to create or unregister an MBean");
+ throw new SecurityException("Access denied");
+ }
+
+ // Retrieve JMXPrincipal from Subject
+ Set<JMXPrincipal> principals = subject.getPrincipals(JMXPrincipal.class);
+ if (principals == null || principals.isEmpty())
+ {
+ throw new SecurityException("Access denied");
+ }
+
+ Principal principal = principals.iterator().next();
+ String identity = principal.getName();
+
+ if (isAdminMethod(args))
+ {
+ if (isAdmin(identity))
+ {
+ return method.invoke(_mbs, args);
+ }
+ else
+ {
+ throw new SecurityException("Access denied");
+ }
+ }
+
+ // Following users can perform any operation other than "createMBean" and "unregisterMBean"
+ if (isAllowedToModify(identity))
+ {
+ return method.invoke(_mbs, args);
+ }
+
+ // These users can only call "getAttribute" on the MBeanServerDelegate MBean
+ // Here we can add other fine grained permissions like specific method for a particular mbean
+ if (isReadOnlyUser(identity) && isReadOnlyMethod(method, args))
+ {
+ return method.invoke(_mbs, args);
+ }
+
+ throw new SecurityException("Access denied");
+ }
+
+ private boolean isAdminMethod(Object[] args)
+ {
+ if (args[0] instanceof ObjectName)
+ {
+ ObjectName object = (ObjectName) args[0];
+
+ return _adminOnlyMethods.contains(object.getKeyProperty("type"));
+ }
+ return false;
+ }
+
+ // Initialises the user roles
+ public static void setAccessRights(Properties accessRights)
+ {
+ _userRoles = accessRights;
+ }
+
+ private boolean isAdmin(String userName)
+ {
+ if (ADMIN.equals(_userRoles.getProperty(userName)))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isAllowedToModify(String userName)
+ {
+ if (ADMIN.equals(_userRoles.getProperty(userName))
+ || READWRITE.equals(_userRoles.getProperty(userName)))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isReadOnlyUser(String userName)
+ {
+ if (READONLY.equals(_userRoles.getProperty(userName)))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isReadOnlyMethod(Method method, Object[] args)
+ {
+ String methodName = method.getName();
+
+ //handle standard get/set/query and select 'is' methods from MBeanServer
+ if (methodName.startsWith("query") || methodName.startsWith("get")
+ ||methodName.startsWith("isInstanceOf") || methodName.startsWith("isRegistered"))
+ {
+ return true;
+ }
+ else if (methodName.startsWith("set"))
+ {
+ return false;
+ }
+
+ //handle invocation of other methods on mbeans
+ if ((args[0] instanceof ObjectName) && (methodName.equals("invoke")))
+ {
+
+ //get invoked method name
+ String mbeanMethod = (args.length > 1) ? (String) args[1] : null;
+ if (mbeanMethod == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ //check if the given method is tagged with an INFO impact attribute
+ MBeanInfo mbeanInfo = _mbs.getMBeanInfo((ObjectName) args[0]);
+ if (mbeanInfo != null)
+ {
+ MBeanOperationInfo[] opInfos = mbeanInfo.getOperations();
+ for (MBeanOperationInfo opInfo : opInfos)
+ {
+ if (opInfo.getName().equals(mbeanMethod) && (opInfo.getImpact() == MBeanOperationInfo.INFO))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ catch (JMException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java
new file mode 100644
index 0000000000..a2dca3e51d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+
+package org.apache.qpid.server.management;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.management.MBeanOperationInfo;
+
+/**
+ * Annotation for MBean operations.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Inherited
+public @interface MBeanOperation
+{
+ String name();
+ String description();
+ int impact() default MBeanOperationInfo.INFO;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java
new file mode 100644
index 0000000000..aba5ec70d8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+
+package org.apache.qpid.server.management;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for MBean operation parameters.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface MBeanOperationParameter {
+ String name();
+ String description();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java
new file mode 100644
index 0000000000..166a2a376d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.management;
+
+/**
+ * Any object that can return a related MBean should implement this interface.
+ *
+ * This enables other classes to get the managed object, which in turn is useful when
+ * constructing relationships between managed objects without having to maintain
+ * separate data structures containing MBeans.
+ *
+ */
+public interface Managable
+{
+ ManagedObject getManagedObject();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java
new file mode 100644
index 0000000000..c18417fc43
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java
@@ -0,0 +1,98 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.management;
+
+import java.io.IOException;
+
+import javax.management.JMException;
+import javax.management.MBeanOperationInfo;
+
+import org.apache.qpid.server.exchange.ManagedExchange;
+import org.apache.qpid.server.queue.ManagedQueue;
+
+/**
+ * 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 = "VirtualHostManager";
+
+ static final int VERSION = 1 ;
+
+ /**
+ * Creates a new Exchange.
+ * @param name
+ * @param type
+ * @param durable
+ * @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="ExchangeType", description="Type of the exchange")String type,
+ @MBeanOperationParameter(name="durable", description="true if the Exchang should be durable")boolean durable)
+ 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= ManagedExchange.TYPE, description="Exchange Name")String exchange)
+ throws IOException, JMException;
+
+ /**
+ * Create a new Queue on the Broker server
+ * @param queueName
+ * @param durable
+ * @param owner
+ * @throws IOException
+ * @throws JMException
+ */
+ @MBeanOperation(name="createNewQueue", description="Create a new Queue on the Broker server", impact= MBeanOperationInfo.ACTION)
+ void createNewQueue(@MBeanOperationParameter(name="queue name", description="Name of the new queue")String queueName,
+ @MBeanOperationParameter(name="owner", description="Owner name")String owner,
+ @MBeanOperationParameter(name="durable", description="true if the queue should be durable")boolean durable)
+ 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= ManagedQueue.TYPE, description="Queue Name")String queueName)
+ throws IOException, JMException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java
new file mode 100644
index 0000000000..42ea8921a4
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.management;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.apache.qpid.AMQException;
+
+/**
+ * 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/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java
new file mode 100644
index 0000000000..b58b17ba86
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java
@@ -0,0 +1,51 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.management;
+
+import javax.management.JMException;
+
+import org.apache.commons.configuration.ConfigurationException;
+
+import java.rmi.RemoteException;
+import java.io.IOException;
+
+/**
+ * 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 start() throws IOException, ConfigurationException;
+
+ void registerObject(ManagedObject managedObject) throws JMException;
+
+ void unregisterObject(ManagedObject managedObject) throws JMException;
+
+ void close() throws RemoteException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java
new file mode 100644
index 0000000000..b4fbed6948
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java
@@ -0,0 +1,60 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.management;
+
+import javax.management.JMException;
+
+import org.apache.log4j.Logger;
+
+import java.rmi.RemoteException;
+
+/**
+ * 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 start()
+ {
+ //no-op
+ }
+
+ public void registerObject(ManagedObject managedObject) throws JMException
+ {
+ }
+
+ public void unregisterObject(ManagedObject managedObject) throws JMException
+ {
+ }
+
+ public void close() throws RemoteException
+ {
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverter.java
new file mode 100644
index 0000000000..e01c5aabbf
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverter.java
@@ -0,0 +1,57 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/*
+ * This file is auto-generated by Qpid Gentools v.0.1 - do not modify.
+ * Supported AMQP versions:
+ * 8-0
+ */
+package org.apache.qpid.server.output;
+
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.AMQDataBlock;
+import org.apache.qpid.AMQException;
+
+public interface ProtocolOutputConverter
+{
+ void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag);
+
+ interface Factory
+ {
+ ProtocolOutputConverter newInstance(AMQProtocolSession session);
+ }
+
+ void writeDeliver(AMQMessage message, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException;
+
+ void writeGetOk(AMQMessage message, int channelId, long deliveryTag, int queueSize) throws AMQException;
+
+ byte getProtocolMinorVersion();
+
+ byte getProtocolMajorVersion();
+
+ void writeReturn(AMQMessage message, int channelId, int replyCode, AMQShortString replyText)
+ throws AMQException;
+
+ void writeFrame(AMQDataBlock block);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverterRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverterRegistry.java
new file mode 100644
index 0000000000..36e7e88fd6
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverterRegistry.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/*
+ * This file is auto-generated by Qpid Gentools v.0.1 - do not modify.
+ * Supported AMQP versions:
+ * 8-0
+ */
+package org.apache.qpid.server.output;
+
+import org.apache.qpid.server.output.ProtocolOutputConverter.Factory;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.framing.ProtocolVersion;
+
+import java.util.Map;
+import java.util.HashMap;
+
+public class ProtocolOutputConverterRegistry
+{
+
+ private static final Map<ProtocolVersion, Factory> _registry =
+ new HashMap<ProtocolVersion, Factory>();
+
+
+ static
+ {
+ register(ProtocolVersion.v8_0, org.apache.qpid.server.output.amqp0_8.ProtocolOutputConverterImpl.getInstanceFactory());
+ register(ProtocolVersion.v0_9, org.apache.qpid.server.output.amqp0_9.ProtocolOutputConverterImpl.getInstanceFactory());
+
+ }
+
+ private static void register(ProtocolVersion version, Factory converter)
+ {
+
+ _registry.put(version,converter);
+ }
+
+
+ public static ProtocolOutputConverter getConverter(AMQProtocolSession session)
+ {
+ return _registry.get(session.getProtocolVersion()).newInstance(session);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_8/ProtocolOutputConverterImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_8/ProtocolOutputConverterImpl.java
new file mode 100644
index 0000000000..2b55d294b5
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_8/ProtocolOutputConverterImpl.java
@@ -0,0 +1,284 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/*
+ * This file is auto-generated by Qpid Gentools v.0.1 - do not modify.
+ * Supported AMQP versions:
+ * 8-0
+ */
+package org.apache.qpid.server.output.amqp0_8;
+
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQMessageHandle;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.AMQException;
+
+import org.apache.mina.common.ByteBuffer;
+
+import java.util.Iterator;
+
+public class ProtocolOutputConverterImpl implements ProtocolOutputConverter
+{
+
+
+ public static Factory getInstanceFactory()
+ {
+ return new Factory()
+ {
+
+ public ProtocolOutputConverter newInstance(AMQProtocolSession session)
+ {
+ return new ProtocolOutputConverterImpl(session);
+ }
+ };
+ }
+
+ private final AMQProtocolSession _protocolSession;
+
+ private ProtocolOutputConverterImpl(AMQProtocolSession session)
+ {
+ _protocolSession = session;
+ }
+
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _protocolSession;
+ }
+
+ public void writeDeliver(AMQMessage message, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException
+ {
+ AMQDataBlock deliver = createEncodedDeliverFrame(message, channelId, deliveryTag, consumerTag);
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ message.getContentHeaderBody());
+
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+ final StoreContext storeContext = message.getStoreContext();
+
+
+ final int bodyCount = messageHandle.getBodyCount(storeContext);
+
+ if(bodyCount == 0)
+ {
+ SmallCompositeAMQDataBlock compositeBlock = new SmallCompositeAMQDataBlock(deliver,
+ contentHeader);
+
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+
+
+ //
+ // 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.
+ //
+ ContentChunk cb = messageHandle.getContentChunk(storeContext, 0);
+
+ AMQDataBlock firstContentBody = new AMQFrame(channelId, getProtocolSession().getMethodRegistry().getProtocolVersionMethodConverter().convertToBody(cb));
+ AMQDataBlock[] blocks = new AMQDataBlock[]{deliver, contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(blocks);
+ writeFrame(compositeBlock);
+
+ //
+ // Now start writing out the other content bodies
+ //
+ for(int i = 1; i < bodyCount; i++)
+ {
+ cb = messageHandle.getContentChunk(storeContext, i);
+ writeFrame(new AMQFrame(channelId, getProtocolSession().getMethodRegistry().getProtocolVersionMethodConverter().convertToBody(cb)));
+ }
+
+
+ }
+
+
+ }
+
+
+ public void writeGetOk(AMQMessage message, int channelId, long deliveryTag, int queueSize) throws AMQException
+ {
+
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+ final StoreContext storeContext = message.getStoreContext();
+
+ AMQDataBlock deliver = createEncodedGetOkFrame(message, channelId, deliveryTag, queueSize);
+
+
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ message.getContentHeaderBody());
+
+ final int bodyCount = messageHandle.getBodyCount(storeContext);
+ if(bodyCount == 0)
+ {
+ SmallCompositeAMQDataBlock compositeBlock = new SmallCompositeAMQDataBlock(deliver,
+ contentHeader);
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+
+
+ //
+ // 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.
+ //
+ ContentChunk cb = messageHandle.getContentChunk(storeContext, 0);
+
+ AMQDataBlock firstContentBody = new AMQFrame(channelId, getProtocolSession().getMethodRegistry().getProtocolVersionMethodConverter().convertToBody(cb));
+ AMQDataBlock[] blocks = new AMQDataBlock[]{deliver, contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(blocks);
+ writeFrame(compositeBlock);
+
+ //
+ // Now start writing out the other content bodies
+ //
+ for(int i = 1; i < bodyCount; i++)
+ {
+ cb = messageHandle.getContentChunk(storeContext, i);
+ writeFrame(new AMQFrame(channelId, getProtocolSession().getMethodRegistry().getProtocolVersionMethodConverter().convertToBody(cb)));
+ }
+
+
+ }
+
+
+ }
+
+
+ private AMQDataBlock createEncodedDeliverFrame(AMQMessage message, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException
+ {
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+
+ MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0);
+ BasicDeliverBody deliverBody =
+ methodRegistry.createBasicDeliverBody(consumerTag,
+ deliveryTag,
+ messageHandle.isRedelivered(),
+ pb.getExchange(),
+ pb.getRoutingKey());
+ AMQFrame deliverFrame = deliverBody.generateFrame(channelId);
+
+
+ return deliverFrame;
+ }
+
+ private AMQDataBlock createEncodedGetOkFrame(AMQMessage message, int channelId, long deliveryTag, int queueSize)
+ throws AMQException
+ {
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+
+ MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0);
+ BasicGetOkBody getOkBody =
+ methodRegistry.createBasicGetOkBody(deliveryTag,
+ messageHandle.isRedelivered(),
+ pb.getExchange(),
+ pb.getRoutingKey(),
+ queueSize);
+ AMQFrame getOkFrame = getOkBody.generateFrame(channelId);
+
+ return getOkFrame;
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return getProtocolSession().getProtocolMinorVersion();
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return getProtocolSession().getProtocolMajorVersion();
+ }
+
+ private AMQDataBlock createEncodedReturnFrame(AMQMessage message, int channelId, int replyCode, AMQShortString replyText) throws AMQException
+ {
+ MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0);
+ BasicReturnBody basicReturnBody =
+ methodRegistry.createBasicReturnBody(replyCode,
+ replyText,
+ message.getMessagePublishInfo().getExchange(),
+ message.getMessagePublishInfo().getRoutingKey());
+ AMQFrame returnFrame = basicReturnBody.generateFrame(channelId);
+
+ return returnFrame;
+ }
+
+ public void writeReturn(AMQMessage message, int channelId, int replyCode, AMQShortString replyText)
+ throws AMQException
+ {
+ AMQDataBlock returnFrame = createEncodedReturnFrame(message, channelId, replyCode, replyText);
+
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ message.getContentHeaderBody());
+
+ Iterator<AMQDataBlock> bodyFrameIterator = message.getBodyFrameIterator(getProtocolSession(), 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[] blocks = new AMQDataBlock[]{returnFrame, contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(blocks);
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(new AMQDataBlock[]{returnFrame, contentHeader});
+
+ 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())
+ {
+ writeFrame(bodyFrameIterator.next());
+ }
+ }
+
+
+ public void writeFrame(AMQDataBlock block)
+ {
+ getProtocolSession().writeFrame(block);
+ }
+
+
+ public void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag)
+ {
+ MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0);
+ BasicCancelOkBody basicCancelOkBody = methodRegistry.createBasicCancelOkBody(consumerTag);
+ writeFrame(basicCancelOkBody.generateFrame(channelId));
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9/ProtocolOutputConverterImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9/ProtocolOutputConverterImpl.java
new file mode 100644
index 0000000000..65184fe744
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9/ProtocolOutputConverterImpl.java
@@ -0,0 +1,391 @@
+package org.apache.qpid.server.output.amqp0_9;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+import org.apache.mina.common.ByteBuffer;
+
+import java.util.Iterator;
+
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQMessageHandle;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQVersionAwareProtocolSession;
+
+public class ProtocolOutputConverterImpl implements ProtocolOutputConverter
+{
+ private static final MethodRegistry METHOD_REGISTRY = MethodRegistry.getMethodRegistry(ProtocolVersion.v0_9);
+ private static final ProtocolVersionMethodConverter PROTOCOL_METHOD_CONVERTER = METHOD_REGISTRY.getProtocolVersionMethodConverter();
+
+
+ public static Factory getInstanceFactory()
+ {
+ return new Factory()
+ {
+
+ public ProtocolOutputConverter newInstance(AMQProtocolSession session)
+ {
+ return new ProtocolOutputConverterImpl(session);
+ }
+ };
+ }
+
+ private final AMQProtocolSession _protocolSession;
+
+ private ProtocolOutputConverterImpl(AMQProtocolSession session)
+ {
+ _protocolSession = session;
+ }
+
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _protocolSession;
+ }
+
+ public void writeDeliver(AMQMessage message, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException
+ {
+ AMQBody deliverBody = createEncodedDeliverFrame(message, channelId, deliveryTag, consumerTag);
+ final ContentHeaderBody contentHeaderBody = message.getContentHeaderBody();
+
+
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+ final StoreContext storeContext = message.getStoreContext();
+
+
+ final int bodyCount = messageHandle.getBodyCount(storeContext);
+
+ if(bodyCount == 0)
+ {
+ SmallCompositeAMQBodyBlock compositeBlock = new SmallCompositeAMQBodyBlock(channelId, deliverBody,
+ contentHeaderBody);
+
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+
+
+ //
+ // 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.
+ //
+ ContentChunk cb = messageHandle.getContentChunk(storeContext, 0);
+
+ AMQBody firstContentBody = PROTOCOL_METHOD_CONVERTER.convertToBody(cb);
+
+ CompositeAMQBodyBlock compositeBlock = new CompositeAMQBodyBlock(channelId, deliverBody, contentHeaderBody, firstContentBody);
+ writeFrame(compositeBlock);
+
+ //
+ // Now start writing out the other content bodies
+ //
+ for(int i = 1; i < bodyCount; i++)
+ {
+ cb = messageHandle.getContentChunk(storeContext, i);
+ writeFrame(new AMQFrame(channelId, PROTOCOL_METHOD_CONVERTER.convertToBody(cb)));
+ }
+
+
+ }
+
+ }
+
+ private AMQDataBlock createContentHeaderBlock(final int channelId, final ContentHeaderBody contentHeaderBody)
+ {
+
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ contentHeaderBody);
+ return contentHeader;
+ }
+
+
+ public void writeGetOk(AMQMessage message, int channelId, long deliveryTag, int queueSize) throws AMQException
+ {
+
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+ final StoreContext storeContext = message.getStoreContext();
+
+ AMQFrame deliver = createEncodedGetOkFrame(message, channelId, deliveryTag, queueSize);
+
+
+ AMQDataBlock contentHeader = createContentHeaderBlock(channelId, message.getContentHeaderBody());
+
+ final int bodyCount = messageHandle.getBodyCount(storeContext);
+ if(bodyCount == 0)
+ {
+ SmallCompositeAMQDataBlock compositeBlock = new SmallCompositeAMQDataBlock(deliver,
+ contentHeader);
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+
+
+ //
+ // 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.
+ //
+ ContentChunk cb = messageHandle.getContentChunk(storeContext, 0);
+
+ AMQDataBlock firstContentBody = new AMQFrame(channelId, PROTOCOL_METHOD_CONVERTER.convertToBody(cb));
+ AMQDataBlock[] blocks = new AMQDataBlock[]{deliver, contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(blocks);
+ writeFrame(compositeBlock);
+
+ //
+ // Now start writing out the other content bodies
+ //
+ for(int i = 1; i < bodyCount; i++)
+ {
+ cb = messageHandle.getContentChunk(storeContext, i);
+ writeFrame(new AMQFrame(channelId, PROTOCOL_METHOD_CONVERTER.convertToBody(cb)));
+ }
+
+
+ }
+
+
+ }
+
+
+ private AMQBody createEncodedDeliverFrame(AMQMessage message, final int channelId, final long deliveryTag, final AMQShortString consumerTag)
+ throws AMQException
+ {
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+
+
+ final boolean isRedelivered = messageHandle.isRedelivered();
+ final AMQShortString exchangeName = pb.getExchange();
+ final AMQShortString routingKey = pb.getRoutingKey();
+
+ final AMQBody returnBlock = new AMQBody()
+ {
+
+ public AMQBody _underlyingBody;
+
+ public AMQBody createAMQBody()
+ {
+ return METHOD_REGISTRY.createBasicDeliverBody(consumerTag,
+ deliveryTag,
+ isRedelivered,
+ exchangeName,
+ routingKey);
+
+
+
+
+
+ }
+
+ public byte getFrameType()
+ {
+ return AMQMethodBody.TYPE;
+ }
+
+ public int getSize()
+ {
+ if(_underlyingBody == null)
+ {
+ _underlyingBody = createAMQBody();
+ }
+ return _underlyingBody.getSize();
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ if(_underlyingBody == null)
+ {
+ _underlyingBody = createAMQBody();
+ }
+ _underlyingBody.writePayload(buffer);
+ }
+
+ public void handle(final int channelId, final AMQVersionAwareProtocolSession amqMinaProtocolSession)
+ throws AMQException
+ {
+ throw new AMQException("This block should never be dispatched!");
+ }
+ };
+ return returnBlock;
+ }
+
+ private AMQFrame createEncodedGetOkFrame(AMQMessage message, int channelId, long deliveryTag, int queueSize)
+ throws AMQException
+ {
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+
+
+ BasicGetOkBody getOkBody =
+ METHOD_REGISTRY.createBasicGetOkBody(deliveryTag,
+ messageHandle.isRedelivered(),
+ pb.getExchange(),
+ pb.getRoutingKey(),
+ queueSize);
+ AMQFrame getOkFrame = getOkBody.generateFrame(channelId);
+
+ return getOkFrame;
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return getProtocolSession().getProtocolMinorVersion();
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return getProtocolSession().getProtocolMajorVersion();
+ }
+
+ private AMQDataBlock createEncodedReturnFrame(AMQMessage message, int channelId, int replyCode, AMQShortString replyText) throws AMQException
+ {
+
+ BasicReturnBody basicReturnBody =
+ METHOD_REGISTRY.createBasicReturnBody(replyCode,
+ replyText,
+ message.getMessagePublishInfo().getExchange(),
+ message.getMessagePublishInfo().getRoutingKey());
+ AMQFrame returnFrame = basicReturnBody.generateFrame(channelId);
+
+ return returnFrame;
+ }
+
+ public void writeReturn(AMQMessage message, int channelId, int replyCode, AMQShortString replyText)
+ throws AMQException
+ {
+ AMQDataBlock returnFrame = createEncodedReturnFrame(message, channelId, replyCode, replyText);
+
+ AMQDataBlock contentHeader = createContentHeaderBlock(channelId, message.getContentHeaderBody());
+
+ Iterator<AMQDataBlock> bodyFrameIterator = message.getBodyFrameIterator(getProtocolSession(), 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[] blocks = new AMQDataBlock[]{returnFrame, contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(blocks);
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(new AMQDataBlock[]{returnFrame, contentHeader});
+
+ 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())
+ {
+ writeFrame(bodyFrameIterator.next());
+ }
+ }
+
+
+ public void writeFrame(AMQDataBlock block)
+ {
+ getProtocolSession().writeFrame(block);
+ }
+
+
+ public void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag)
+ {
+
+ BasicCancelOkBody basicCancelOkBody = METHOD_REGISTRY.createBasicCancelOkBody(consumerTag);
+ writeFrame(basicCancelOkBody.generateFrame(channelId));
+
+ }
+
+
+ public static final class CompositeAMQBodyBlock extends AMQDataBlock
+ {
+ public static final int OVERHEAD = 3 * AMQFrame.getFrameOverhead();
+
+ private final AMQBody _methodBody;
+ private final AMQBody _headerBody;
+ private final AMQBody _contentBody;
+ private final int _channel;
+
+
+ public CompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody, AMQBody contentBody)
+ {
+ _channel = channel;
+ _methodBody = methodBody;
+ _headerBody = headerBody;
+ _contentBody = contentBody;
+
+ }
+
+ public long getSize()
+ {
+ return OVERHEAD + _methodBody.getSize() + _headerBody.getSize() + _contentBody.getSize();
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ AMQFrame.writeFrames(buffer, _channel, _methodBody, _headerBody, _contentBody);
+ }
+ }
+
+ public static final class SmallCompositeAMQBodyBlock extends AMQDataBlock
+ {
+ public static final int OVERHEAD = 2 * AMQFrame.getFrameOverhead();
+
+ private final AMQBody _methodBody;
+ private final AMQBody _headerBody;
+ private final int _channel;
+
+
+ public SmallCompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody)
+ {
+ _channel = channel;
+ _methodBody = methodBody;
+ _headerBody = headerBody;
+
+ }
+
+ public long getSize()
+ {
+ return OVERHEAD + _methodBody.getSize() + _headerBody.getSize() ;
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ AMQFrame.writeFrames(buffer, _channel, _methodBody, _headerBody);
+ }
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Activator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Activator.java
new file mode 100644
index 0000000000..b0ebf197f9
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Activator.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.qpid.server.plugins;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator
+{
+
+ BundleContext _context = null;
+
+ public void start(BundleContext ctx) throws Exception
+ {
+ _context = ctx;
+ }
+
+ public void stop(BundleContext ctx) throws Exception
+ {
+ start(null);
+ }
+
+ public BundleContext getContext()
+ {
+ return _context;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java
new file mode 100644
index 0000000000..dbfcefb6ab
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.qpid.server.plugins;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.framework.Felix;
+import org.apache.felix.framework.cache.BundleCache;
+import org.apache.felix.framework.util.FelixConstants;
+import org.apache.felix.framework.util.StringMap;
+import org.apache.qpid.server.exchange.ExchangeType;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.ACLPluginFactory;
+import org.apache.qpid.server.security.access.plugins.AllowAll;
+import org.apache.qpid.server.security.access.plugins.DenyAll;
+import org.apache.qpid.server.security.access.plugins.LegacyAccessPlugin;
+import org.apache.qpid.server.security.access.plugins.SimpleXML;
+import org.apache.qpid.server.security.access.plugins.network.FirewallPlugin;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleException;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ *
+ * @author aidan
+ *
+ * Provides access to pluggable elements, such as exchanges
+ */
+
+public class PluginManager
+{
+
+ private Felix _felix = null;
+ private ServiceTracker _exchangeTracker = null;
+ private ServiceTracker _securityTracker = null;
+ private Activator _activator = null;
+ private boolean _empty;
+ private Map<String, ACLPluginFactory> _securityPlugins;
+
+ public PluginManager(String plugindir) throws Exception
+ {
+ StringMap configMap = new StringMap(false);
+
+ // Tell felix it's being embedded
+ configMap.put(FelixConstants.EMBEDDED_EXECUTION_PROP, "true");
+ // Add the bundle provided service interface package and the core OSGi
+ // packages to be exported from the class path via the system bundle.
+ configMap.put(FelixConstants.FRAMEWORK_SYSTEMPACKAGES, "org.osgi.framework; version=1.3.0,"
+ + "org.osgi.service.packageadmin; version=1.2.0," +
+ "org.osgi.service.startlevel; version=1.0.0," +
+ "org.osgi.service.url; version=1.0.0," +
+ "org.apache.qpid.framing; version=0.2.1," +
+ "org.apache.qpid.server.exchange; version=0.2.1," +
+ "org.apache.qpid.server.management; version=0.2.1,"+
+ "org.apache.qpid.protocol; version=0.2.1,"+
+ "org.apache.qpid.server.virtualhost; version=0.2.1," +
+ "org.apache.qpid; version=0.2.1," +
+ "org.apache.qpid.server.queue; version=0.2.1," +
+ "javax.management.openmbean; version=1.0.0,"+
+ "javax.management; version=1.0.0,"+
+ "org.apache.qpid.junit.extensions.util; version=0.6.1,"
+ );
+
+ if (plugindir == null)
+ {
+ _empty = true;
+ return;
+ }
+
+ // Set the list of bundles to load
+ File dir = new File(plugindir);
+ if (!dir.exists())
+ {
+ _empty = true;
+ return;
+ }
+ StringBuffer pluginJars = new StringBuffer();
+
+ if (dir.isDirectory())
+ {
+ for (String child : dir.list())
+ {
+ if (child.endsWith("jar"))
+ {
+ pluginJars.append(String.format(" file:%s%s%s", plugindir,File.separator,child));
+ }
+ }
+ }
+ if (pluginJars.length() == 0)
+ {
+ _empty = true;
+ return;
+ }
+
+ configMap.put(FelixConstants.AUTO_START_PROP + ".1", pluginJars.toString());
+ configMap.put(BundleCache.CACHE_PROFILE_DIR_PROP, plugindir);
+
+ List<BundleActivator> activators = new ArrayList<BundleActivator>();
+ _activator = new Activator();
+ activators.add(_activator);
+
+ _felix = new Felix(configMap, activators);
+ try
+ {
+ _felix.start();
+
+ _exchangeTracker = new ServiceTracker(_activator.getContext(), ExchangeType.class.getName(), null);
+ _exchangeTracker.open();
+
+ _securityTracker = new ServiceTracker(_activator.getContext(), ACLPlugin.class.getName(), null);
+ _exchangeTracker.open();
+
+ }
+ catch (BundleException e)
+ {
+ throw new Exception("Could not create bundle");
+ }
+ }
+
+ private <type> Map<String, type> getServices(ServiceTracker tracker)
+ {
+ Map<String, type>exchanges = new HashMap<String, type>();
+
+ if (tracker != null)
+ {
+ for (Object service : tracker.getServices())
+ {
+ exchanges.put(service.getClass().getName(), (type) service);
+ }
+ }
+
+ return exchanges;
+ }
+
+ public Map<String, ExchangeType<?>> getExchanges()
+ {
+ return getServices(_exchangeTracker);
+ }
+
+ public Map<String, ACLPluginFactory> getSecurityPlugins()
+ {
+ if (_securityPlugins == null)
+ {
+ _securityPlugins = getServices(_securityTracker);
+ // A little gross that we have to add them here, but not all the plugins are OSGIfied
+ _securityPlugins.put(SimpleXML.class.getName(), SimpleXML.FACTORY);
+ _securityPlugins.put(AllowAll.class.getName(), AllowAll.FACTORY);
+ _securityPlugins.put(DenyAll.class.getName(), DenyAll.FACTORY);
+ _securityPlugins.put(LegacyAccessPlugin.class.getName(), LegacyAccessPlugin.FACTORY);
+ _securityPlugins.put(FirewallPlugin.class.getName(), FirewallPlugin.FACTORY);
+ }
+ return _securityPlugins;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java
new file mode 100644
index 0000000000..205ca73f13
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java
@@ -0,0 +1,859 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol;
+
+import org.apache.log4j.Logger;
+
+import org.apache.mina.common.IdleStatus;
+import org.apache.mina.common.IoServiceConfig;
+import org.apache.mina.common.IoSession;
+import org.apache.mina.common.CloseFuture;
+import org.apache.mina.transport.vmpipe.VmPipeAddress;
+
+import org.apache.qpid.AMQChannelException;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.codec.AMQDecoder;
+import org.apache.qpid.common.ClientProperties;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.pool.ReadWriteThreadModel;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.protocol.AMQMethodListener;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.handler.ServerMethodDispatcherImpl;
+import org.apache.qpid.server.management.Managable;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.server.output.ProtocolOutputConverterRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.state.AMQState;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+import org.apache.qpid.transport.Sender;
+
+import javax.management.JMException;
+import javax.security.sasl.SaslServer;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+public class AMQMinaProtocolSession implements AMQProtocolSession, Managable
+{
+ private static final Logger _logger = Logger.getLogger(AMQProtocolSession.class);
+
+ private static final String CLIENT_PROPERTIES_INSTANCE = ClientProperties.instance.toString();
+
+ // to save boxing the channelId and looking up in a map... cache in an array the low numbered
+ // channels. This value must be of the form 2^x - 1.
+ private static final int CHANNEL_CACHE_SIZE = 0xff;
+
+ private final IoSession _minaProtocolSession;
+
+ private AMQShortString _contextKey;
+
+ private AMQShortString _clientVersion = null;
+
+ private VirtualHost _virtualHost;
+
+ private final Map<Integer, AMQChannel> _channelMap = new HashMap<Integer, AMQChannel>();
+
+ private final AMQChannel[] _cachedChannels = new AMQChannel[CHANNEL_CACHE_SIZE + 1];
+
+ private final CopyOnWriteArraySet<AMQMethodListener> _frameListeners = new CopyOnWriteArraySet<AMQMethodListener>();
+
+ private final AMQStateManager _stateManager;
+
+ private AMQCodecFactory _codecFactory;
+
+ private AMQProtocolSessionMBean _managedObject;
+
+ private SaslServer _saslServer;
+
+ private Object _lastReceived;
+
+ private Object _lastSent;
+
+ protected boolean _closed;
+ // maximum number of channels this session should have
+ private long _maxNoOfChannels = 1000;
+
+ /* AMQP Version for this session */
+ private ProtocolVersion _protocolVersion = ProtocolVersion.getLatestSupportedVersion();
+
+ private FieldTable _clientProperties;
+ private final List<Task> _taskList = new CopyOnWriteArrayList<Task>();
+
+ private List<Integer> _closingChannelsList = new CopyOnWriteArrayList<Integer>();
+ private ProtocolOutputConverter _protocolOutputConverter;
+ private Principal _authorizedID;
+ private MethodDispatcher _dispatcher;
+ private ProtocolSessionIdentifier _sessionIdentifier;
+
+ private static final long LAST_WRITE_FUTURE_JOIN_TIMEOUT = 60000L;
+ private org.apache.mina.common.WriteFuture _lastWriteFuture;
+
+ public ManagedObject getManagedObject()
+ {
+ return _managedObject;
+ }
+
+ public AMQMinaProtocolSession(IoSession session, VirtualHostRegistry virtualHostRegistry, AMQCodecFactory codecFactory)
+ throws AMQException
+ {
+ _stateManager = new AMQStateManager(virtualHostRegistry, this);
+ _minaProtocolSession = session;
+ session.setAttachment(this);
+
+ _codecFactory = codecFactory;
+
+ try
+ {
+ IoServiceConfig config = session.getServiceConfig();
+ ReadWriteThreadModel threadModel = (ReadWriteThreadModel) config.getThreadModel();
+ threadModel.getAsynchronousReadFilter().createNewJobForSession(session);
+ threadModel.getAsynchronousWriteFilter().createNewJobForSession(session);
+ }
+ catch (RuntimeException e)
+ {
+ e.printStackTrace();
+ throw e;
+
+ }
+ }
+
+ public AMQMinaProtocolSession(IoSession session, VirtualHostRegistry virtualHostRegistry, AMQCodecFactory codecFactory,
+ AMQStateManager stateManager) throws AMQException
+ {
+ _stateManager = stateManager;
+ _minaProtocolSession = session;
+ session.setAttachment(this);
+
+ _codecFactory = codecFactory;
+
+ }
+
+ private AMQProtocolSessionMBean createMBean() throws AMQException
+ {
+ try
+ {
+ return new AMQProtocolSessionMBean(this);
+ }
+ catch (JMException ex)
+ {
+ _logger.error("AMQProtocolSession MBean creation has failed ", ex);
+ throw new AMQException("AMQProtocolSession MBean creation has failed ", ex);
+ }
+ }
+
+ public IoSession getIOSession()
+ {
+ return _minaProtocolSession;
+ }
+
+ public static AMQProtocolSession getAMQProtocolSession(IoSession minaProtocolSession)
+ {
+ return (AMQProtocolSession) minaProtocolSession.getAttachment();
+ }
+
+ public void dataBlockReceived(AMQDataBlock message) throws Exception
+ {
+ _lastReceived = message;
+ if (message instanceof ProtocolInitiation)
+ {
+ protocolInitiationReceived((ProtocolInitiation) message);
+
+ }
+ else if (message instanceof AMQFrame)
+ {
+ AMQFrame frame = (AMQFrame) message;
+ frameReceived(frame);
+
+ }
+ else
+ {
+ throw new UnknnownMessageTypeException(message);
+ }
+ }
+
+ private void frameReceived(AMQFrame frame) throws AMQException
+ {
+ int channelId = frame.getChannel();
+ AMQBody body = frame.getBodyFrame();
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Frame Received: " + frame);
+ }
+
+ // Check that this channel is not closing
+ if (channelAwaitingClosure(channelId))
+ {
+ if ((frame.getBodyFrame() instanceof ChannelCloseOkBody))
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Channel[" + channelId + "] awaiting closure - processing close-ok");
+ }
+ }
+ else
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Channel[" + channelId + "] awaiting closure. Should close socket as client did not close-ok :" + frame);
+ }
+
+ closeProtocolSession();
+ return;
+ }
+ }
+
+ try
+ {
+ body.handle(channelId, this);
+ }
+ catch (AMQException e)
+ {
+ closeChannel(channelId);
+ throw e;
+ }
+
+ }
+
+ private void protocolInitiationReceived(ProtocolInitiation pi)
+ {
+ // this ensures the codec never checks for a PI message again
+ ((AMQDecoder) _codecFactory.getDecoder()).setExpectProtocolInitiation(false);
+ try
+ {
+ ProtocolVersion pv = pi.checkVersion(); // Fails if not correct
+
+ // This sets the protocol version (and hence framing classes) for this session.
+ setProtocolVersion(pv);
+
+ String mechanisms = ApplicationRegistry.getInstance().getAuthenticationManager().getMechanisms();
+
+ String locales = "en_US";
+
+ AMQMethodBody responseBody = getMethodRegistry().createConnectionStartBody((short) getProtocolMajorVersion(),
+ (short) getProtocolMinorVersion(),
+ null,
+ mechanisms.getBytes(),
+ locales.getBytes());
+ _minaProtocolSession.write(responseBody.generateFrame(0));
+
+ }
+ catch (AMQException e)
+ {
+ _logger.info("Received unsupported protocol initiation for protocol version: " + getProtocolVersion());
+
+ _minaProtocolSession.write(new ProtocolInitiation(ProtocolVersion.getLatestSupportedVersion()));
+
+ // TODO: Close connection (but how to wait until message is sent?)
+ // ritchiem 2006-12-04 will this not do?
+ // WriteFuture future = _minaProtocolSession.write(new ProtocolInitiation(pv[i][PROTOCOLgetProtocolMajorVersion()], pv[i][PROTOCOLgetProtocolMinorVersion()]));
+ // future.join();
+ // close connection
+
+ }
+ }
+
+ public void methodFrameReceived(int channelId, AMQMethodBody methodBody)
+ {
+
+ final AMQMethodEvent<AMQMethodBody> evt = new AMQMethodEvent<AMQMethodBody>(channelId, methodBody);
+
+ try
+ {
+ try
+ {
+
+ boolean wasAnyoneInterested = _stateManager.methodReceived(evt);
+
+ if (!_frameListeners.isEmpty())
+ {
+ for (AMQMethodListener listener : _frameListeners)
+ {
+ wasAnyoneInterested = listener.methodReceived(evt) || wasAnyoneInterested;
+ }
+ }
+
+ if (!wasAnyoneInterested)
+ {
+ throw new AMQNoMethodHandlerException(evt);
+ }
+ }
+ catch (AMQChannelException e)
+ {
+ if (getChannel(channelId) != null)
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Closing channel due to: " + e.getMessage());
+ }
+
+ writeFrame(e.getCloseFrame(channelId));
+ closeChannel(channelId);
+ }
+ else
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("ChannelException occured on non-existent channel:" + e.getMessage());
+ }
+
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Closing connection due to: " + e.getMessage());
+ }
+
+ AMQConnectionException ce =
+ evt.getMethod().getConnectionException(AMQConstant.CHANNEL_ERROR,
+ AMQConstant.CHANNEL_ERROR.getName().toString());
+
+ closeConnection(channelId, ce, false);
+ }
+ }
+ catch (AMQConnectionException e)
+ {
+ closeConnection(channelId, e, false);
+ }
+ }
+ catch (Exception e)
+ {
+
+ for (AMQMethodListener listener : _frameListeners)
+ {
+ listener.error(e);
+ }
+
+ _logger.error("Unexpected exception while processing frame. Closing connection.", e);
+
+ closeProtocolSession();
+ }
+ }
+
+ public void contentHeaderReceived(int channelId, ContentHeaderBody body) throws AMQException
+ {
+
+ AMQChannel channel = getAndAssertChannel(channelId);
+
+ channel.publishContentHeader(body);
+
+ }
+
+ public void contentBodyReceived(int channelId, ContentBody body) throws AMQException
+ {
+ AMQChannel channel = getAndAssertChannel(channelId);
+
+ channel.publishContentBody(body);
+ }
+
+ public void heartbeatBodyReceived(int channelId, HeartbeatBody body)
+ {
+ // NO - OP
+ }
+
+ /**
+ * Convenience method that writes a frame to the protocol session. Equivalent to calling
+ * getProtocolSession().write().
+ *
+ * @param frame the frame to write
+ */
+ public void writeFrame(AMQDataBlock frame)
+ {
+ _lastSent = frame;
+
+ _lastWriteFuture = _minaProtocolSession.write(frame);
+ }
+
+ public AMQShortString getContextKey()
+ {
+ return _contextKey;
+ }
+
+ public void setContextKey(AMQShortString contextKey)
+ {
+ _contextKey = contextKey;
+ }
+
+ public List<AMQChannel> getChannels()
+ {
+ return new ArrayList<AMQChannel>(_channelMap.values());
+ }
+
+ public AMQChannel getAndAssertChannel(int channelId) throws AMQException
+ {
+ AMQChannel channel = getChannel(channelId);
+ if (channel == null)
+ {
+ throw new AMQException(AMQConstant.NOT_FOUND, "Channel not found with id:" + channelId);
+ }
+
+ return channel;
+ }
+
+ public AMQChannel getChannel(int channelId) throws AMQException
+ {
+ final AMQChannel channel =
+ ((channelId & CHANNEL_CACHE_SIZE) == channelId) ? _cachedChannels[channelId] : _channelMap.get(channelId);
+ if ((channel == null) || channel.isClosing())
+ {
+ return null;
+ }
+ else
+ {
+ return channel;
+ }
+ }
+
+ public boolean channelAwaitingClosure(int channelId)
+ {
+ return !_closingChannelsList.isEmpty() && _closingChannelsList.contains(channelId);
+ }
+
+ public void addChannel(AMQChannel channel) throws AMQException
+ {
+ if (_closed)
+ {
+ throw new AMQException("Session is closed");
+ }
+
+ final int channelId = channel.getChannelId();
+
+ if (_closingChannelsList.contains(channelId))
+ {
+ throw new AMQException("Session is marked awaiting channel close");
+ }
+
+ if (_channelMap.size() == _maxNoOfChannels)
+ {
+ String errorMessage =
+ toString() + ": maximum number of channels has been reached (" + _maxNoOfChannels
+ + "); can't create channel";
+ _logger.error(errorMessage);
+ throw new AMQException(AMQConstant.NOT_ALLOWED, errorMessage);
+ }
+ else
+ {
+ _channelMap.put(channel.getChannelId(), channel);
+ }
+
+ if (((channelId & CHANNEL_CACHE_SIZE) == channelId))
+ {
+ _cachedChannels[channelId] = channel;
+ }
+
+ checkForNotification();
+ }
+
+ private void checkForNotification()
+ {
+ int channelsCount = _channelMap.size();
+ if (channelsCount >= _maxNoOfChannels)
+ {
+ _managedObject.notifyClients("Channel count (" + channelsCount + ") has reached the threshold value");
+ }
+ }
+
+ public Long getMaximumNumberOfChannels()
+ {
+ return _maxNoOfChannels;
+ }
+
+ public void setMaximumNumberOfChannels(Long value)
+ {
+ _maxNoOfChannels = value;
+ }
+
+ public void commitTransactions(AMQChannel channel) throws AMQException
+ {
+ if ((channel != null) && channel.isTransactional())
+ {
+ channel.commit();
+ }
+ }
+
+ public void rollbackTransactions(AMQChannel channel) throws AMQException
+ {
+ if ((channel != null) && channel.isTransactional())
+ {
+ channel.rollback();
+ }
+ }
+
+ /**
+ * 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 = getChannel(channelId);
+ if (channel == null)
+ {
+ throw new IllegalArgumentException("Unknown channel id");
+ }
+ else
+ {
+ try
+ {
+ channel.close();
+ markChannelAwaitingCloseOk(channelId);
+ }
+ finally
+ {
+ removeChannel(channelId);
+ }
+ }
+ }
+
+ public void closeChannelOk(int channelId)
+ {
+ // todo QPID-847 - This is called from two lcoations ChannelCloseHandler and ChannelCloseOkHandler.
+ // When it is the CC_OK_Handler then it makes sence to remove the channel else we will leak memory.
+ // We do it from the Close Handler as we are sending the OK back to the client.
+ // While this is AMQP spec compliant. The Java client in the event of an IllegalArgumentException
+ // will send a close-ok.. Where we should call removeChannel.
+ // However, due to the poor exception handling on the client. The client-user will be notified of the
+ // InvalidArgument and if they then decide to close the session/connection then the there will be time
+ // for that to occur i.e. a new close method be sent before the exeption handling can mark the session closed.
+ //removeChannel(channelId);
+ _closingChannelsList.remove(new Integer(channelId));
+ }
+
+ private void markChannelAwaitingCloseOk(int channelId)
+ {
+ _closingChannelsList.add(channelId);
+ }
+
+ /**
+ * In our current implementation this is used by the clustering code.
+ *
+ * @param channelId The channel to remove
+ */
+ public void removeChannel(int channelId)
+ {
+ _channelMap.remove(channelId);
+ if ((channelId & CHANNEL_CACHE_SIZE) == channelId)
+ {
+ _cachedChannels[channelId] = null;
+ }
+ }
+
+ /**
+ * 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, (int) (ApplicationRegistry.getInstance().getConfiguration().getHeartBeatTimeout() * 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();
+ }
+
+ _channelMap.clear();
+ for (int i = 0; i <= CHANNEL_CACHE_SIZE; i++)
+ {
+ _cachedChannels[i] = null;
+ }
+ }
+
+ /** 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;
+
+ if (_virtualHost != null)
+ {
+ _virtualHost.getConnectionRegistry().deregisterConnection(this);
+ }
+
+ closeAllChannels();
+ if (_managedObject != null)
+ {
+ _managedObject.unregister();
+ }
+
+ for (Task task : _taskList)
+ {
+ task.doTask(this);
+ }
+ }
+ }
+
+ public void closeConnection(int channelId, AMQConnectionException e, boolean closeProtocolSession) throws AMQException
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Closing connection due to: " + e.getMessage());
+ }
+
+ markChannelAwaitingCloseOk(channelId);
+ closeSession();
+ _stateManager.changeState(AMQState.CONNECTION_CLOSING);
+ writeFrame(e.getCloseFrame(channelId));
+
+ if (closeProtocolSession)
+ {
+ closeProtocolSession();
+ }
+ }
+
+ public void closeProtocolSession()
+ {
+ closeProtocolSession(true);
+ }
+
+ public void closeProtocolSession(boolean waitLast)
+ {
+ if (waitLast && (_lastWriteFuture != null))
+ {
+ _logger.debug("Waiting for last write to join.");
+ _lastWriteFuture.join(LAST_WRITE_FUTURE_JOIN_TIMEOUT);
+ }
+
+ _logger.debug("REALLY Closing protocol session:" + _minaProtocolSession);
+ final CloseFuture future = _minaProtocolSession.close();
+ future.join(LAST_WRITE_FUTURE_JOIN_TIMEOUT);
+
+ try
+ {
+ _stateManager.changeState(AMQState.CONNECTION_CLOSED);
+ }
+ catch (AMQException e)
+ {
+ _logger.info(e.getMessage());
+ }
+ }
+
+ public String toString()
+ {
+ return _minaProtocolSession.getRemoteAddress() + "(" + (getAuthorizedID() == null ? "?" : getAuthorizedID().getName() + ")");
+ }
+
+ 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;
+ }
+
+ public FieldTable getClientProperties()
+ {
+ return _clientProperties;
+ }
+
+ public void setClientProperties(FieldTable clientProperties)
+ {
+ _clientProperties = clientProperties;
+ if (_clientProperties != null)
+ {
+ if (_clientProperties.getString(CLIENT_PROPERTIES_INSTANCE) != null)
+ {
+ setContextKey(new AMQShortString(_clientProperties.getString(CLIENT_PROPERTIES_INSTANCE)));
+ }
+
+ if (_clientProperties.getString(ClientProperties.version.toString()) != null)
+ {
+ _clientVersion = new AMQShortString(_clientProperties.getString(ClientProperties.version.toString()));
+ }
+ }
+ _sessionIdentifier = new ProtocolSessionIdentifier(this);
+ }
+
+ private void setProtocolVersion(ProtocolVersion pv)
+ {
+ _protocolVersion = pv;
+
+ _protocolOutputConverter = ProtocolOutputConverterRegistry.getConverter(this);
+ _dispatcher = ServerMethodDispatcherImpl.createMethodDispatcher(_stateManager, _protocolVersion);
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return _protocolVersion.getMajorVersion();
+ }
+
+ public ProtocolVersion getProtocolVersion()
+ {
+ return _protocolVersion;
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return _protocolVersion.getMinorVersion();
+ }
+
+ public boolean isProtocolVersion(byte major, byte minor)
+ {
+ return (getProtocolMajorVersion() == major) && (getProtocolMinorVersion() == minor);
+ }
+
+ public MethodRegistry getRegistry()
+ {
+ return getMethodRegistry();
+ }
+
+ public Object getClientIdentifier()
+ {
+ return (_minaProtocolSession != null) ? _minaProtocolSession.getRemoteAddress() : null;
+ }
+
+ public VirtualHost getVirtualHost()
+ {
+ return _virtualHost;
+ }
+
+ public void setVirtualHost(VirtualHost virtualHost) throws AMQException
+ {
+ _virtualHost = virtualHost;
+
+ _virtualHost.getConnectionRegistry().registerConnection(this);
+
+ _managedObject = createMBean();
+ _managedObject.register();
+ }
+
+ public void addSessionCloseTask(Task task)
+ {
+ _taskList.add(task);
+ }
+
+ public void removeSessionCloseTask(Task task)
+ {
+ _taskList.remove(task);
+ }
+
+ public ProtocolOutputConverter getProtocolOutputConverter()
+ {
+ return _protocolOutputConverter;
+ }
+
+ public void setAuthorizedID(Principal authorizedID)
+ {
+ _authorizedID = authorizedID;
+ }
+
+ public Principal getAuthorizedID()
+ {
+ return _authorizedID;
+ }
+
+ public MethodRegistry getMethodRegistry()
+ {
+ return MethodRegistry.getMethodRegistry(getProtocolVersion());
+ }
+
+ public MethodDispatcher getMethodDispatcher()
+ {
+ return _dispatcher;
+ }
+
+ public ProtocolSessionIdentifier getSessionIdentifier()
+ {
+ return _sessionIdentifier;
+ }
+
+ public String getClientVersion()
+ {
+ return (_clientVersion == null) ? null : _clientVersion.toString();
+ }
+
+ public void setSender(Sender<java.nio.ByteBuffer> sender)
+ {
+ // No-op, interface munging between this and AMQProtocolSession
+ }
+
+ public void init()
+ {
+ // No-op, interface munging between this and AMQProtocolSession
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQNoMethodHandlerException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQNoMethodHandlerException.java
new file mode 100644
index 0000000000..a7599a3e0d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQNoMethodHandlerException.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+
+/**
+ * AMQNoMethodHandlerException represents the case where no method handler exists to handle an AQMP method.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represents failure to handle an AMQP method.
+ * </table>
+ *
+ * @todo Not an AMQP exception as no status code.
+ *
+ * @todo Missing method handler. Unlikely to ever happen, and if it does its a coding error. Consider replacing with a
+ * Runtime.
+ */
+public class AMQNoMethodHandlerException extends AMQException
+{
+ public AMQNoMethodHandlerException(AMQMethodEvent<AMQMethodBody> evt)
+ {
+ super("AMQMethodEvent " + evt + " was not processed by any listener on Broker.");
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java
new file mode 100644
index 0000000000..0dbefd8798
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java
@@ -0,0 +1,284 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import org.apache.log4j.Logger;
+import org.apache.mina.common.ByteBuffer;
+import org.apache.mina.common.IdleStatus;
+import org.apache.mina.common.IoFilterChain;
+import org.apache.mina.common.IoHandlerAdapter;
+import org.apache.mina.common.IoSession;
+import org.apache.mina.filter.ReadThrottleFilterBuilder;
+import org.apache.mina.filter.SSLFilter;
+import org.apache.mina.filter.WriteBufferLimitFilterBuilder;
+import org.apache.mina.filter.codec.QpidProtocolCodecFilter;
+import org.apache.mina.filter.executor.ExecutorFilter;
+import org.apache.mina.util.SessionUtil;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.framing.AMQDataBlock;
+import org.apache.qpid.framing.AMQProtocolHeaderException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ConnectionCloseBody;
+import org.apache.qpid.framing.HeartbeatBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.ProtocolInitiation;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.ssl.SSLContextFactory;
+
+/**
+ * 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
+{
+ private static final Logger _logger = Logger.getLogger(AMQPFastProtocolHandler.class);
+
+ private final IApplicationRegistry _applicationRegistry;
+
+ private final int BUFFER_READ_LIMIT_SIZE;
+ private final int BUFFER_WRITE_LIMIT_SIZE;
+
+ public AMQPFastProtocolHandler(Integer applicationRegistryInstance)
+ {
+ this(ApplicationRegistry.getInstance(applicationRegistryInstance));
+ }
+
+ public AMQPFastProtocolHandler(IApplicationRegistry applicationRegistry)
+ {
+ _applicationRegistry = applicationRegistry;
+
+ // Read the configuration from the application registry
+ BUFFER_READ_LIMIT_SIZE = _applicationRegistry.getConfiguration().getBufferReadLimit();
+ BUFFER_WRITE_LIMIT_SIZE = _applicationRegistry.getConfiguration().getBufferWriteLimit();
+
+ _logger.debug("AMQPFastProtocolHandler created");
+ }
+
+ protected AMQPFastProtocolHandler(AMQPFastProtocolHandler handler)
+ {
+ this(handler._applicationRegistry);
+ }
+
+ public void sessionCreated(IoSession protocolSession) throws Exception
+ {
+ SessionUtil.initialize(protocolSession);
+ final AMQCodecFactory codecFactory = new AMQCodecFactory(true);
+
+ createSession(protocolSession, _applicationRegistry, codecFactory);
+ _logger.info("Protocol session created for:" + protocolSession.getRemoteAddress());
+
+ final QpidProtocolCodecFilter pcf = new QpidProtocolCodecFilter(codecFactory);
+ final ServerConfiguration config = _applicationRegistry.getConfiguration();
+
+ String keystorePath = config.getKeystorePath();
+ String keystorePassword = config.getKeystorePassword();
+ String certType = config.getCertType();
+ SSLContextFactory sslContextFactory = null;
+ boolean isSsl = false;
+ if (config.getEnableSSL() && isSSLClient(config, protocolSession))
+ {
+ sslContextFactory = new SSLContextFactory(keystorePath, keystorePassword, certType);
+ isSsl = true;
+ }
+ if (config.getEnableExecutorPool())
+ {
+ if (isSsl)
+ {
+ protocolSession.getFilterChain().addAfter("AsynchronousReadFilter", "sslFilter",
+ new SSLFilter(sslContextFactory.buildServerContext()));
+ }
+ protocolSession.getFilterChain().addBefore("AsynchronousWriteFilter", "protocolFilter", pcf);
+ }
+ else
+ {
+ protocolSession.getFilterChain().addLast("protocolFilter", pcf);
+ if (isSsl)
+ {
+ protocolSession.getFilterChain().addBefore("protocolFilter", "sslFilter",
+ new SSLFilter(sslContextFactory.buildServerContext()));
+ }
+ }
+
+ if (ApplicationRegistry.getInstance().getConfiguration().getProtectIOEnabled())
+ {
+ try
+ {
+// //Add IO Protection Filters
+ IoFilterChain chain = protocolSession.getFilterChain();
+
+
+ protocolSession.getFilterChain().addLast("tempExecutorFilterForFilterBuilder", new ExecutorFilter());
+
+ ReadThrottleFilterBuilder readfilter = new ReadThrottleFilterBuilder();
+ readfilter.setMaximumConnectionBufferSize(BUFFER_READ_LIMIT_SIZE);
+ readfilter.attach(chain);
+
+ WriteBufferLimitFilterBuilder writefilter = new WriteBufferLimitFilterBuilder();
+ writefilter.setMaximumConnectionBufferSize(BUFFER_WRITE_LIMIT_SIZE);
+ writefilter.attach(chain);
+
+ protocolSession.getFilterChain().remove("tempExecutorFilterForFilterBuilder");
+ _logger.info("Using IO Read/Write Filter Protection");
+ }
+ catch (Exception e)
+ {
+ _logger.error("Unable to attach IO Read/Write Filter Protection :" + e.getMessage());
+ }
+ }
+ }
+
+ /** Separated into its own, protected, method to allow easier reuse */
+ protected void createSession(IoSession session, IApplicationRegistry applicationRegistry, AMQCodecFactory codec) throws AMQException
+ {
+ new AMQMinaProtocolSession(session, applicationRegistry.getVirtualHostRegistry(), codec);
+ }
+
+ public void sessionOpened(IoSession protocolSession) throws Exception
+ {
+ _logger.info("Session opened for:" + protocolSession.getRemoteAddress());
+ }
+
+ public void sessionClosed(IoSession protocolSession) throws Exception
+ {
+ _logger.info("Protocol Session closed for:" + protocolSession.getRemoteAddress());
+ final AMQProtocolSession amqProtocolSession = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession);
+ //fixme -- this can be null
+ if (amqProtocolSession != null)
+ {
+ try
+ {
+ amqProtocolSession.closeSession();
+ }
+ catch (AMQException e)
+ {
+ _logger.error("Caught AMQException whilst closingSession:" + e);
+ }
+ }
+ }
+
+ public void sessionIdle(IoSession session, IdleStatus status) throws Exception
+ {
+ _logger.debug("Protocol Session [" + this + "] idle: " + status + " :for:" + session.getRemoteAddress());
+ if (IdleStatus.WRITER_IDLE.equals(status))
+ {
+ //write heartbeat frame:
+ session.write(HeartbeatBody.FRAME);
+ }
+ else if (IdleStatus.READER_IDLE.equals(status))
+ {
+ //failover:
+ throw new IOException("Timed out while waiting for heartbeat from peer.");
+ }
+
+ }
+
+ public void exceptionCaught(IoSession protocolSession, Throwable throwable) throws Exception
+ {
+ AMQProtocolSession session = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession);
+ if (throwable instanceof AMQProtocolHeaderException)
+ {
+
+ protocolSession.write(new ProtocolInitiation(ProtocolVersion.getLatestSupportedVersion()));
+
+ protocolSession.close();
+
+ _logger.error("Error in protocol initiation " + session + ":" + protocolSession.getRemoteAddress() + " :" + throwable.getMessage(), throwable);
+ }
+ else if (throwable instanceof IOException)
+ {
+ _logger.error("IOException caught in" + session + ", session closed implictly: " + throwable);
+ }
+ else
+ {
+ _logger.error("Exception caught in" + session + ", closing session explictly: " + throwable, throwable);
+
+
+ MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(session.getProtocolVersion());
+ ConnectionCloseBody closeBody = methodRegistry.createConnectionCloseBody(200,new AMQShortString(throwable.getMessage()),0,0);
+
+ protocolSession.write(closeBody.generateFrame(0));
+
+ 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);
+ }
+ }
+
+ protected boolean isSSLClient(ServerConfiguration connectionConfig,
+ IoSession protocolSession)
+ {
+ InetSocketAddress addr = (InetSocketAddress) protocolSession.getLocalAddress();
+ return addr.getPort() == connectionConfig.getSSLPort();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java
new file mode 100644
index 0000000000..07c153bfe8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.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);
+ }
+
+ public AMQPFastProtocolHandler getHandler()
+ {
+ return _handler;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java
new file mode 100644
index 0000000000..1bac601225
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java
@@ -0,0 +1,207 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol;
+
+import javax.security.sasl.SaslServer;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.common.ClientProperties;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.qpid.protocol.AMQVersionAwareProtocolSession;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import java.security.Principal;
+
+
+public interface AMQProtocolSession extends AMQVersionAwareProtocolSession
+{
+
+ public static final class ProtocolSessionIdentifier
+ {
+ private final Object _sessionIdentifier;
+ private final Object _sessionInstance;
+
+ ProtocolSessionIdentifier(AMQProtocolSession session)
+ {
+ _sessionIdentifier = session.getClientIdentifier();
+ _sessionInstance = session.getClientProperties() == null ? null : session.getClientProperties().getObject(ClientProperties.instance.toAMQShortString());
+ }
+
+ public Object getSessionIdentifier()
+ {
+ return _sessionIdentifier;
+ }
+
+ public Object getSessionInstance()
+ {
+ return _sessionInstance;
+ }
+ }
+
+ public static interface Task
+ {
+ public void doTask(AMQProtocolSession session) throws AMQException;
+ }
+
+ /**
+ * 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;
+
+ /**
+ * Get the context key associated with this session. Context key is described in the AMQ protocol specification (RFC
+ * 6).
+ *
+ * @return the context key
+ */
+ AMQShortString 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(AMQShortString 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) throws AMQException;
+
+ /**
+ * 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;
+
+ /**
+ * Markes the specific channel as closed. This will release the lock for that channel id so a new channel can be
+ * created on that id.
+ *
+ * @param channelId id of the channel to close
+ */
+ void closeChannelOk(int channelId);
+
+ /**
+ * Check to see if this chanel is closing
+ *
+ * @param channelId id to check
+ * @return boolean with state of channel awaiting closure
+ */
+ boolean channelAwaitingClosure(int channelId);
+
+ /**
+ * 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;
+
+ /** This must be called to close the session in order to free up any resources managed by the session. */
+ void closeConnection(int channelId, AMQConnectionException e, boolean closeProtocolSession) 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);
+
+
+ FieldTable getClientProperties();
+
+ void setClientProperties(FieldTable clientProperties);
+
+ Object getClientIdentifier();
+
+ VirtualHost getVirtualHost();
+
+ void setVirtualHost(VirtualHost virtualHost) throws AMQException;
+
+ void addSessionCloseTask(Task task);
+
+ void removeSessionCloseTask(Task task);
+
+ public ProtocolOutputConverter getProtocolOutputConverter();
+
+ void setAuthorizedID(Principal authorizedID);
+
+ /** @return a Principal that was used to authorized this session */
+ Principal getAuthorizedID();
+
+ public MethodRegistry getMethodRegistry();
+
+ public MethodDispatcher getMethodDispatcher();
+
+ public ProtocolSessionIdentifier getSessionIdentifier();
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBean.java
new file mode 100644
index 0000000000..5dd3cc075a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBean.java
@@ -0,0 +1,304 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+/*
+ *
+ * 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;
+import java.util.List;
+
+import javax.management.JMException;
+import javax.management.MBeanException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.NotCompliantMBeanException;
+import javax.management.Notification;
+import javax.management.monitor.MonitorNotification;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ConnectionCloseBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.management.AMQManagedObject;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.management.ManagedObject;
+
+/**
+ * This MBean class implements the management interface. 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")
+public class AMQProtocolSessionMBean extends AMQManagedObject implements ManagedConnection
+{
+ private AMQMinaProtocolSession _session = null;
+ private String _name = null;
+
+ // openmbean data types for representing the channel attributes
+ private static final String[] _channelAtttibuteNames =
+ { "Channel Id", "Transactional", "Default Queue", "Unacknowledged Message Count" };
+ private static final String[] _indexNames = { _channelAtttibuteNames[0] };
+ private static final OpenType[] _channelAttributeTypes =
+ { SimpleType.INTEGER, SimpleType.BOOLEAN, SimpleType.STRING, SimpleType.INTEGER };
+ private static CompositeType _channelType = null; // represents the data type for channel data
+ private static TabularType _channelsType = null; // Data type for list of channels type
+ private static final AMQShortString BROKER_MANAGEMENT_CONSOLE_HAS_CLOSED_THE_CONNECTION =
+ new AMQShortString("Broker Management Console has closed the connection.");
+
+ @MBeanConstructor("Creates an MBean exposing an AMQ Broker Connection")
+ public AMQProtocolSessionMBean(AMQMinaProtocolSession session) throws NotCompliantMBeanException, OpenDataException
+ {
+ super(ManagedConnection.class, ManagedConnection.TYPE, ManagedConnection.VERSION);
+ _session = session;
+ String remote = getRemoteAddress();
+ remote = "anonymous".equals(remote) ? (remote + hashCode()) : remote;
+ _name = jmxEncode(new StringBuffer(remote), 0).toString();
+ init();
+ }
+
+ static
+ {
+ try
+ {
+ init();
+ }
+ catch (JMException ex)
+ {
+ // This is not expected to ever occur.
+ throw new RuntimeException("Got JMException in static initializer.", ex);
+ }
+ }
+
+ /**
+ * initialises the openmbean data types
+ */
+ private static void init() throws OpenDataException
+ {
+ _channelType =
+ new CompositeType("Channel", "Channel Details", _channelAtttibuteNames, _channelAtttibuteNames,
+ _channelAttributeTypes);
+ _channelsType = new TabularType("Channels", "Channels", _channelType, _indexNames);
+ }
+
+ public String getClientId()
+ {
+ return (_session.getContextKey() == null) ? null : _session.getContextKey().toString();
+ }
+
+ public String getAuthorizedId()
+ {
+ return (_session.getAuthorizedID() != null ) ? _session.getAuthorizedID().getName() : null;
+ }
+
+ public String getVersion()
+ {
+ return (_session.getClientVersion() == null) ? null : _session.getClientVersion().toString();
+ }
+
+ public Date getLastIoTime()
+ {
+ return new Date(_session.getIOSession().getLastIoTime());
+ }
+
+ public String getRemoteAddress()
+ {
+ return _session.getIOSession().getRemoteAddress().toString();
+ }
+
+ public ManagedObject getParentObject()
+ {
+ return _session.getVirtualHost().getManagedObject();
+ }
+
+ public Long getWrittenBytes()
+ {
+ return _session.getIOSession().getWrittenBytes();
+ }
+
+ public Long getReadBytes()
+ {
+ return _session.getIOSession().getReadBytes();
+ }
+
+ public Long getMaximumNumberOfChannels()
+ {
+ return _session.getMaximumNumberOfChannels();
+ }
+
+ public void setMaximumNumberOfChannels(Long value)
+ {
+ _session.setMaximumNumberOfChannels(value);
+ }
+
+ public String getObjectInstanceName()
+ {
+ return _name;
+ }
+
+ /**
+ * commits transactions for a transactional channel
+ *
+ * @param channelId
+ * @throws JMException if channel with given id doesn't exist or if commit fails
+ */
+ public void commitTransactions(int channelId) throws JMException
+ {
+ try
+ {
+ AMQChannel channel = _session.getChannel(channelId);
+ if (channel == null)
+ {
+ throw new JMException("The channel (channel Id = " + channelId + ") does not exist");
+ }
+
+ _session.commitTransactions(channel);
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+
+ /**
+ * rollsback the transactions for a transactional channel
+ *
+ * @param channelId
+ * @throws JMException if channel with given id doesn't exist or if rollback fails
+ */
+ public void rollbackTransactions(int channelId) throws JMException
+ {
+ try
+ {
+ AMQChannel channel = _session.getChannel(channelId);
+ if (channel == null)
+ {
+ throw new JMException("The channel (channel Id = " + channelId + ") does not exist");
+ }
+
+ _session.rollbackTransactions(channel);
+ }
+ 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 channels() throws OpenDataException
+ {
+ TabularDataSupport channelsList = new TabularDataSupport(_channelsType);
+ List<AMQChannel> list = _session.getChannels();
+
+ for (AMQChannel channel : list)
+ {
+ Object[] itemValues =
+ {
+ channel.getChannelId(), channel.isTransactional(),
+ (channel.getDefaultQueue() != null) ? channel.getDefaultQueue().getName().asString() : null,
+ channel.getUnacknowledgedMessageMap().size()
+ };
+
+ CompositeData channelData = new CompositeDataSupport(_channelType, _channelAtttibuteNames, itemValues);
+ channelsList.put(channelData);
+ }
+
+ return channelsList;
+ }
+
+ /**
+ * closes the connection. The administrator can use this management operation to close connection to free up
+ * resources.
+ * @throws JMException
+ */
+ public void closeConnection() throws JMException
+ {
+
+ MethodRegistry methodRegistry = _session.getMethodRegistry();
+ ConnectionCloseBody responseBody =
+ methodRegistry.createConnectionCloseBody(AMQConstant.REPLY_SUCCESS.getCode(),
+ // replyCode
+ BROKER_MANAGEMENT_CONSOLE_HAS_CLOSED_THE_CONNECTION,
+ // replyText,
+ 0,
+ 0);
+
+ _session.writeFrame(responseBody.generateFrame(0));
+
+ try
+ {
+ _session.closeSession();
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+
+ @Override
+ public MBeanNotificationInfo[] getNotificationInfo()
+ {
+ String[] notificationTypes = new String[] { MonitorNotification.THRESHOLD_VALUE_EXCEEDED };
+ String name = MonitorNotification.class.getName();
+ String description = "Channel count has reached threshold value";
+ MBeanNotificationInfo info1 = new MBeanNotificationInfo(notificationTypes, name, description);
+
+ return new MBeanNotificationInfo[] { info1 };
+ }
+
+ public void notifyClients(String notificationMsg)
+ {
+ Notification n =
+ new Notification(MonitorNotification.THRESHOLD_VALUE_EXCEEDED, this, ++_notificationSequenceNumber,
+ System.currentTimeMillis(), notificationMsg);
+ _broadcaster.sendNotification(n);
+ }
+
+} // End of MBean class
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java
new file mode 100644
index 0000000000..2abcecb6de
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java
@@ -0,0 +1,51 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.exchange.ExchangeType;
+
+public class ExchangeInitialiser
+{
+ public void initialise(ExchangeFactory factory, ExchangeRegistry registry) throws AMQException{
+ for (ExchangeType<? extends Exchange> type : factory.getRegisteredTypes())
+ {
+ define (registry, factory, type.getDefaultExchangeName(), type.getName());
+ }
+
+ define(registry, factory, ExchangeDefaults.DEFAULT_EXCHANGE_NAME, ExchangeDefaults.DIRECT_EXCHANGE_CLASS);
+ registry.setDefaultExchange(registry.getExchange(ExchangeDefaults.DEFAULT_EXCHANGE_NAME));
+ }
+
+ private void define(ExchangeRegistry r, ExchangeFactory f,
+ AMQShortString name, AMQShortString type) throws AMQException
+ {
+ if(r.getExchange(name)== null)
+ {
+ r.registerExchange(f.createExchange(name, type, true, false, 0));
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java
new file mode 100644
index 0000000000..e75b09a0cb
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java
@@ -0,0 +1,136 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.protocol;
+
+import java.io.IOException;
+import java.util.Date;
+import java.security.Principal;
+
+import javax.management.JMException;
+import javax.management.MBeanOperationInfo;
+import javax.management.openmbean.TabularData;
+
+import org.apache.qpid.server.management.MBeanAttribute;
+import org.apache.qpid.server.management.MBeanOperation;
+import org.apache.qpid.server.management.MBeanOperationParameter;
+
+/**
+ * The management interface exposed to allow management of Connections.
+ * @author Bhupendra Bhardwaj
+ * @version 0.1
+ */
+public interface ManagedConnection
+{
+ static final String TYPE = "Connection";
+ static final int VERSION = 1;
+
+ @MBeanAttribute(name = "ClientId", description = "Client Id")
+ String getClientId();
+
+ @MBeanAttribute(name = "AuthorizedId", description = "User Name")
+ String getAuthorizedId();
+
+ @MBeanAttribute(name = "Version", description = "Client Version")
+ String getVersion();
+
+ /**
+ * Tells the remote address of this connection.
+ * @return remote address
+ */
+ @MBeanAttribute(name="RemoteAddress", description=TYPE + " Address")
+ String getRemoteAddress();
+
+ /**
+ * 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 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();
+ */
+
+ /**
+ * Threshold high value for no of channels. This is useful in setting notifications or
+ * taking required action is there are more channels being created.
+ * @return threshold limit for no of channels
+ */
+ Long getMaximumNumberOfChannels();
+
+ /**
+ * Sets the threshold high value for number of channels for a connection
+ * @param value
+ */
+ @MBeanAttribute(name="MaximumNumberOfChannels", description="The threshold high value for number of channels for this connection")
+ void setMaximumNumberOfChannels(Long value);
+
+ //********** Operations *****************//
+
+ /**
+ * channel details of all the channels opened for this connection.
+ * @return general channel details
+ * @throws IOException
+ * @throws JMException
+ */
+ @MBeanOperation(name="channels", description="Channel details for this connection")
+ TabularData channels() throws IOException, JMException;
+
+ /**
+ * Commits the transactions if the channel is transactional.
+ * @param channelId
+ * @throws JMException
+ */
+ @MBeanOperation(name="commitTransaction",
+ description="Commits the transactions for given channel Id, 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 channel Id, if the channel is transactional",
+ impact= MBeanOperationInfo.ACTION)
+ void rollbackTransactions(@MBeanOperationParameter(name="channel Id", description="channel Id")int channelId) throws JMException;
+
+ /**
+ * 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;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/UnknnownMessageTypeException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/UnknnownMessageTypeException.java
new file mode 100644
index 0000000000..6e72aa062f
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/UnknnownMessageTypeException.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQDataBlock;
+
+/**
+ * UnknnownMessageTypeException represents a failure when Mina passes an unexpected frame type.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represents failure to cast a frame to its expected type.
+ * </table>
+ *
+ * @todo Not an AMQP exception as no status code.
+ *
+ * @todo Seems like this exception was created to handle an unsafe type cast that will never happen in practice. Would
+ * be better just to leave that as a ClassCastException. However, check the framing layer catches this error
+ * first.
+ */
+public class UnknnownMessageTypeException extends AMQException
+{
+ public UnknnownMessageTypeException(AMQDataBlock message)
+ {
+ super("Unknown message type: " + message.getClass().getName() + ": " + message);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java
new file mode 100644
index 0000000000..a485649410
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java
@@ -0,0 +1,482 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQBody;
+import org.apache.qpid.framing.AMQDataBlock;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.txn.TransactionalContext;
+
+
+import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A deliverable message.
+ */
+public class AMQMessage implements Filterable<AMQException>
+{
+ /** Used for debugging purposes. */
+ private static final Logger _log = Logger.getLogger(AMQMessage.class);
+
+ private final AtomicInteger _referenceCount = new AtomicInteger(1);
+
+ private final AMQMessageHandle _messageHandle;
+
+ /** Holds the transactional context in which this message is being processed. */
+ private StoreContext _storeContext;
+
+ /** Flag to indicate that this message requires 'immediate' delivery. */
+
+ private static final byte IMMEDIATE = 0x01;
+
+ /**
+ * Flag to indicate whether this message has been delivered to a consumer. Used in implementing return functionality
+ * for messages published with the 'immediate' flag.
+ */
+
+ private static final byte DELIVERED_TO_CONSUMER = 0x02;
+
+ private byte _flags = 0;
+
+ private long _expiration;
+
+ private final long _size;
+
+ private AMQProtocolSession.ProtocolSessionIdentifier _sessionIdentifier;
+ private static final byte IMMEDIATE_AND_DELIVERED = (byte) (IMMEDIATE | DELIVERED_TO_CONSUMER);
+
+
+
+ /**
+ * 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 AMQProtocolSession _protocolSession;
+
+ private BodyFrameIterator(AMQProtocolSession protocolSession, int channel)
+ {
+ _channel = channel;
+ _protocolSession = protocolSession;
+ }
+
+ public boolean hasNext()
+ {
+ try
+ {
+ return _index < (_messageHandle.getBodyCount(getStoreContext()) - 1);
+ }
+ catch (AMQException e)
+ {
+ _log.error("Unable to get body count: " + e, e);
+
+ return false;
+ }
+ }
+
+ public AMQDataBlock next()
+ {
+ try
+ {
+
+ AMQBody cb =
+ getProtocolVersionMethodConverter().convertToBody(_messageHandle.getContentChunk(getStoreContext(),
+ ++_index));
+
+ return new AMQFrame(_channel, cb);
+ }
+ catch (AMQException e)
+ {
+ // have no choice but to throw a runtime exception
+ throw new RuntimeException("Error getting content body: " + e, e);
+ }
+
+ }
+
+ private ProtocolVersionMethodConverter getProtocolVersionMethodConverter()
+ {
+ return _protocolSession.getMethodRegistry().getProtocolVersionMethodConverter();
+ }
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ public void clearStoreContext()
+ {
+ _storeContext = new StoreContext();
+ }
+
+ public StoreContext getStoreContext()
+ {
+ return _storeContext;
+ }
+
+ private class BodyContentIterator implements Iterator<ContentChunk>
+ {
+
+ private int _index = -1;
+
+ public boolean hasNext()
+ {
+ try
+ {
+ return _index < (_messageHandle.getBodyCount(getStoreContext()) - 1);
+ }
+ catch (AMQException e)
+ {
+ _log.error("Error getting body count: " + e, e);
+
+ return false;
+ }
+ }
+
+ public ContentChunk next()
+ {
+ try
+ {
+ return _messageHandle.getContentChunk(getStoreContext(), ++_index);
+ }
+ catch (AMQException e)
+ {
+ throw new RuntimeException("Error getting content body: " + e, e);
+ }
+ }
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+
+
+ /**
+ * Used when recovering, i.e. when the message store is creating references to messages. In that case, the normal
+ * enqueue/routingComplete is not done since the recovery process is responsible for routing the messages to
+ * queues.
+ *
+ * @param messageId
+ * @param store
+ * @param factory
+ *
+ * @throws AMQException
+ */
+ public AMQMessage(Long messageId, MessageStore store, MessageHandleFactory factory, TransactionalContext txnConext)
+ throws AMQException
+ {
+ _messageHandle = factory.createMessageHandle(messageId, store, true);
+ _storeContext = txnConext.getStoreContext();
+ _size = _messageHandle.getBodySize(txnConext.getStoreContext());
+ }
+
+ /**
+ * Used when recovering, i.e. when the message store is creating references to messages. In that case, the normal
+ * enqueue/routingComplete is not done since the recovery process is responsible for routing the messages to
+ * queues.
+ *
+ * @param messageHandle
+ *
+ * @throws AMQException
+ */
+ public AMQMessage(
+ AMQMessageHandle messageHandle,
+ StoreContext storeConext,
+ MessagePublishInfo info)
+ throws AMQException
+ {
+ _messageHandle = messageHandle;
+ _storeContext = storeConext;
+
+ if(info.isImmediate())
+ {
+ _flags |= IMMEDIATE;
+ }
+ _size = messageHandle.getBodySize(storeConext);
+
+ }
+
+
+ protected AMQMessage(AMQMessage msg) throws AMQException
+ {
+ _messageHandle = msg._messageHandle;
+ _storeContext = msg._storeContext;
+ _flags = msg._flags;
+ _size = msg._size;
+
+ }
+
+
+ public String debugIdentity()
+ {
+ return "(HC:" + System.identityHashCode(this) + " ID:" + getMessageId() + " Ref:" + _referenceCount.get() + ")";
+ }
+
+ public void setExpiration(final long expiration)
+ {
+
+ _expiration = expiration;
+
+ }
+
+ public boolean isReferenced()
+ {
+ return _referenceCount.get() > 0;
+ }
+
+ public Iterator<AMQDataBlock> getBodyFrameIterator(AMQProtocolSession protocolSession, int channel)
+ {
+ return new BodyFrameIterator(protocolSession, channel);
+ }
+
+ public Iterator<ContentChunk> getContentBodyIterator()
+ {
+ return new BodyContentIterator();
+ }
+
+ public ContentHeaderBody getContentHeaderBody() throws AMQException
+ {
+ return _messageHandle.getContentHeaderBody(getStoreContext());
+ }
+
+
+
+ public Long getMessageId()
+ {
+ return _messageHandle.getMessageId();
+ }
+
+ /**
+ * Creates a long-lived reference to this message, and increments the count of such references, as an atomic
+ * operation.
+ */
+ public AMQMessage takeReference()
+ {
+ incrementReference(); // _referenceCount.incrementAndGet();
+
+ return this;
+ }
+
+ public boolean incrementReference()
+ {
+ return incrementReference(1);
+ }
+
+ /* Threadsafe. Increment the reference count on the message. */
+ public boolean incrementReference(int count)
+ {
+ if(_referenceCount.addAndGet(count) <= 1)
+ {
+ _referenceCount.addAndGet(-count);
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+
+ }
+
+ /**
+ * Threadsafe. This will decrement the reference count and when it reaches zero will remove the message from the
+ * message store.
+ *
+ * @param storeContext
+ *
+ * @throws MessageCleanupException when an attempt was made to remove the message from the message store and that
+ * failed
+ */
+ public void decrementReference(StoreContext storeContext) throws MessageCleanupException
+ {
+
+ int count = _referenceCount.decrementAndGet();
+
+ // 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 (count == 0)
+ {
+ // set the reference count way below 0 so that we can detect that the message has been deleted
+ // this is to guard against the message being spontaneously recreated (from the mgmt console)
+ // by copying from other queues at the same time as it is being removed.
+ _referenceCount.set(Integer.MIN_VALUE/2);
+
+ try
+ {
+ // must check if the handle is null since there may be cases where we decide to throw away a message
+ // and the handle has not yet been constructed
+ if (_messageHandle != null)
+ {
+ _messageHandle.removeMessage(storeContext);
+ }
+ }
+ catch (AMQException e)
+ {
+ // to maintain consistency, we revert the count
+ incrementReference();
+ throw new MessageCleanupException(getMessageId(), e);
+ }
+ }
+ else
+ {
+ if (count < 0)
+ {
+ throw new MessageCleanupException("Reference count for message id " + debugIdentity()
+ + " has gone below 0.");
+ }
+ }
+ }
+
+
+ /**
+ * Called selectors to determin if the message has already been sent
+ *
+ * @return _deliveredToConsumer
+ */
+ public boolean getDeliveredToConsumer()
+ {
+ return (_flags & DELIVERED_TO_CONSUMER) != 0;
+ }
+
+ public boolean isPersistent() throws AMQException
+ {
+ return _messageHandle.isPersistent();
+ }
+
+ /**
+ * Called to enforce the 'immediate' flag.
+ *
+ * @returns true if the message is marked for immediate delivery but has not been marked as delivered
+ * to a consumer
+ */
+ public boolean immediateAndNotDelivered()
+ {
+
+ return (_flags & IMMEDIATE_AND_DELIVERED) == IMMEDIATE;
+
+ }
+
+ public MessagePublishInfo getMessagePublishInfo() throws AMQException
+ {
+ return _messageHandle.getMessagePublishInfo(getStoreContext());
+ }
+
+ public boolean isRedelivered()
+ {
+ return _messageHandle.isRedelivered();
+ }
+
+ public void setRedelivered(boolean redelivered)
+ {
+ _messageHandle.setRedelivered(redelivered);
+ }
+
+ public long getArrivalTime()
+ {
+ return _messageHandle.getArrivalTime();
+ }
+
+ /**
+ * Checks to see if the message has expired. If it has the message is dequeued.
+ *
+ * @param queue The queue to check the expiration against. (Currently not used)
+ *
+ * @return true if the message has expire
+ *
+ * @throws AMQException
+ */
+ public boolean expired(AMQQueue queue) throws AMQException
+ {
+
+ if (_expiration != 0L)
+ {
+ long now = System.currentTimeMillis();
+
+ return (now > _expiration);
+ }
+
+ return false;
+ }
+
+ /**
+ * Called when this message is delivered to a consumer. (used to implement the 'immediate' flag functionality).
+ * And for selector efficiency.
+ */
+ public void setDeliveredToConsumer()
+ {
+ _flags |= DELIVERED_TO_CONSUMER;
+ }
+
+
+
+ public AMQMessageHandle getMessageHandle()
+ {
+ return _messageHandle;
+ }
+
+ public long getSize()
+ {
+ return _size;
+
+ }
+
+ public Object getPublisherClientInstance()
+ {
+ return _sessionIdentifier.getSessionInstance();
+ }
+
+ public Object getPublisherIdentifier()
+ {
+ return _sessionIdentifier.getSessionIdentifier();
+ }
+
+ public void setClientIdentifier(final AMQProtocolSession.ProtocolSessionIdentifier sessionIdentifier)
+ {
+ _sessionIdentifier = sessionIdentifier;
+ }
+
+
+ public String toString()
+ {
+ // return "Message[" + debugIdentity() + "]: " + _messageId + "; ref count: " + _referenceCount + "; taken : " +
+ // _taken + " by :" + _takenBySubcription;
+
+ return "Message[" + debugIdentity() + "]: " + getMessageId() + "; ref count: " + _referenceCount;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java
new file mode 100644
index 0000000000..0ddd4e4d92
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java
@@ -0,0 +1,79 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+
+/**
+ * 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.
+ */
+public interface AMQMessageHandle
+{
+ ContentHeaderBody getContentHeaderBody(StoreContext context) throws AMQException;
+
+ /**
+ *
+ * @return the messageId for the message associated with this handle
+ */
+ Long getMessageId();
+
+
+ /**
+ * @return the number of body frames associated with this message
+ */
+ int getBodyCount(StoreContext context) throws AMQException;
+
+ /**
+ * @return the size of the body
+ */
+ long getBodySize(StoreContext context) 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
+ */
+ ContentChunk getContentChunk(StoreContext context, int index) throws IllegalArgumentException, AMQException;
+
+ void addContentBodyFrame(StoreContext storeContext, ContentChunk contentBody, boolean isLastContentBody) throws AMQException;
+
+ MessagePublishInfo getMessagePublishInfo(StoreContext context) throws AMQException;
+
+ boolean isRedelivered();
+
+ void setRedelivered(boolean redelivered);
+
+ boolean isPersistent();
+
+ void setPublishAndContentHeaderBody(StoreContext storeContext, MessagePublishInfo messagePublishInfo,
+ ContentHeaderBody contentHeaderBody)
+ throws AMQException;
+
+ void removeMessage(StoreContext storeContext) throws AMQException;
+
+ long getArrivalTime();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java
new file mode 100644
index 0000000000..bb6ce65d42
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java
@@ -0,0 +1,70 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.subscription.SubscriptionList;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.AMQException;
+
+public class AMQPriorityQueue extends SimpleAMQQueue
+{
+ protected AMQPriorityQueue(final AMQShortString name,
+ final boolean durable,
+ final AMQShortString owner,
+ final boolean autoDelete,
+ final VirtualHost virtualHost,
+ int priorities)
+ throws AMQException
+ {
+ super(name, durable, owner, autoDelete, virtualHost, new PriorityQueueList.Factory(priorities));
+ }
+
+ public int getPriorities()
+ {
+ return ((PriorityQueueList) _entries).getPriorities();
+ }
+
+ @Override
+ protected void checkSubscriptionsNotAheadOfDelivery(final QueueEntry entry)
+ {
+ // check that all subscriptions are not in advance of the entry
+ SubscriptionList.SubscriptionNodeIterator subIter = _subscriptionList.iterator();
+ while(subIter.advance() && !entry.isAcquired())
+ {
+ final Subscription subscription = subIter.getNode().getSubscription();
+ QueueEntry subnode = subscription.getLastSeenEntry();
+ while(subnode != null && entry.compareTo(subnode) < 0 && !entry.isAcquired())
+ {
+ if(subscription.setLastSeenEntry(subnode,entry))
+ {
+ break;
+ }
+ else
+ {
+ subnode = subscription.getLastSeenEntry();
+ }
+ }
+
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java
new file mode 100644
index 0000000000..014b348822
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java
@@ -0,0 +1,220 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.configuration.Configuration;
+import org.apache.qpid.server.management.Managable;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.configuration.QueueConfiguration;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.AMQException;
+
+import java.util.List;
+import java.util.Set;
+
+public interface AMQQueue extends Managable, Comparable<AMQQueue>
+{
+
+ AMQShortString getName();
+
+ boolean isDurable();
+
+ boolean isAutoDelete();
+
+ AMQShortString getOwner();
+
+ VirtualHost getVirtualHost();
+
+
+ void bind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException;
+
+ void unBind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException;
+
+ List<ExchangeBinding> getExchangeBindings();
+
+
+ void registerSubscription(final Subscription subscription, final boolean exclusive) throws AMQException;
+
+ void unregisterSubscription(final Subscription subscription) throws AMQException;
+
+
+ int getConsumerCount();
+
+ int getActiveConsumerCount();
+
+ boolean isUnused();
+
+ boolean isEmpty();
+
+ int getMessageCount();
+
+ int getUndeliveredMessageCount();
+
+
+ long getQueueDepth();
+
+ long getReceivedMessageCount();
+
+ long getOldestMessageArrivalTime();
+
+ boolean isDeleted();
+
+
+ int delete() throws AMQException;
+
+
+ QueueEntry enqueue(StoreContext storeContext, AMQMessage message) throws AMQException;
+
+ void requeue(StoreContext storeContext, QueueEntry entry) throws AMQException;
+
+ void dequeue(StoreContext storeContext, QueueEntry entry) throws FailedDequeueException;
+
+
+
+ boolean resend(final QueueEntry entry, final Subscription subscription) throws AMQException;
+
+
+
+ void addQueueDeleteTask(final Task task);
+
+
+ List<QueueEntry> getMessagesOnTheQueue();
+
+ List<QueueEntry> getMessagesOnTheQueue(long fromMessageId, long toMessageId);
+
+ List<Long> getMessagesOnTheQueue(int num);
+
+ List<Long> getMessagesOnTheQueue(int num, int offest);
+
+ QueueEntry getMessageOnTheQueue(long messageId);
+
+
+ void moveMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName,
+ StoreContext storeContext);
+
+ void copyMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName, StoreContext storeContext);
+
+ void removeMessagesFromQueue(long fromMessageId, long toMessageId, StoreContext storeContext);
+
+
+
+ long getMaximumMessageSize();
+
+ void setMaximumMessageSize(long value);
+
+
+ long getMaximumMessageCount();
+
+ void setMaximumMessageCount(long value);
+
+
+ long getMaximumQueueDepth();
+
+ void setMaximumQueueDepth(long value);
+
+
+ long getMaximumMessageAge();
+
+ void setMaximumMessageAge(final long maximumMessageAge);
+
+
+ long getMinimumAlertRepeatGap();
+
+ void setMinimumAlertRepeatGap(long value);
+
+
+ void deleteMessageFromTop(StoreContext storeContext) throws AMQException;
+
+ long clearQueue(StoreContext storeContext) throws AMQException;
+
+ /**
+ * Checks the status of messages on the queue, purging expired ones, firing age related alerts etc.
+ * @throws AMQException
+ */
+ void checkMessageStatus() throws AMQException;
+
+ Set<NotificationCheck> getNotificationChecks();
+
+ void flushSubscription(final Subscription sub) throws AMQException;
+
+ void deliverAsync(final Subscription sub);
+
+ void deliverAsync();
+
+ void stop();
+
+ /**
+ * ExistingExclusiveSubscription signals a failure to create a subscription, because an exclusive subscription
+ * already exists.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represent failure to create a subscription, because an exclusive subscription already exists.
+ * </table>
+ *
+ * @todo Not an AMQP exception as no status code.
+ *
+ * @todo Move to top level, used outside this class.
+ */
+ static final class ExistingExclusiveSubscription extends AMQException
+ {
+
+ public ExistingExclusiveSubscription()
+ {
+ super("");
+ }
+ }
+
+ /**
+ * ExistingSubscriptionPreventsExclusive signals a failure to create an exclusize subscription, as a subscription
+ * already exists.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represent failure to create an exclusize subscription, as a subscription already exists.
+ * </table>
+ *
+ * @todo Not an AMQP exception as no status code.
+ *
+ * @todo Move to top level, used outside this class.
+ */
+ static final class ExistingSubscriptionPreventsExclusive extends AMQException
+ {
+ public ExistingSubscriptionPreventsExclusive()
+ {
+ super("");
+ }
+ }
+
+ static interface Task
+ {
+ public void doTask(AMQQueue queue) throws AMQException;
+ }
+
+ void configure(QueueConfiguration config);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java
new file mode 100644
index 0000000000..84b59456cb
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java
@@ -0,0 +1,86 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.configuration.QueueConfiguration;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+
+public class AMQQueueFactory
+{
+ public static final AMQShortString X_QPID_PRIORITIES = new AMQShortString("x-qpid-priorities");
+
+ public static AMQQueue createAMQQueueImpl(AMQShortString name,
+ boolean durable,
+ AMQShortString owner,
+ boolean autoDelete,
+ VirtualHost virtualHost, final FieldTable arguments)
+ throws AMQException
+ {
+
+ final int priorities = arguments == null ? 1 : arguments.containsKey(X_QPID_PRIORITIES) ? arguments.getInteger(X_QPID_PRIORITIES) : 1;
+
+ AMQQueue q = null;
+ if(priorities > 1)
+ {
+ q = new AMQPriorityQueue(name, durable, owner, autoDelete, virtualHost, priorities);
+ }
+ else
+ {
+ q = new SimpleAMQQueue(name, durable, owner, autoDelete, virtualHost);
+ }
+
+ //Register the new queue
+ virtualHost.getQueueRegistry().registerQueue(q);
+ return q;
+ }
+
+ public static AMQQueue createAMQQueueImpl(QueueConfiguration config, VirtualHost host) throws AMQException
+ {
+ AMQShortString queueName = new AMQShortString(config.getName());
+
+ boolean durable = config.getDurable();
+ boolean autodelete = config.getAutoDelete();
+ AMQShortString owner = (config.getOwner() != null) ? new AMQShortString(config.getOwner()) : null;
+ FieldTable arguments = null;
+ boolean priority = config.getPriority();
+ int priorities = config.getPriorities();
+ if(priority || priorities > 0)
+ {
+ if(arguments == null)
+ {
+ arguments = new FieldTable();
+ }
+ if (priorities < 0)
+ {
+ priorities = 10;
+ }
+ arguments.put(new AMQShortString("x-qpid-priorities"), priorities);
+ }
+
+ AMQQueue q = createAMQQueueImpl(queueName, durable, owner, autodelete, host, arguments);
+ q.configure(config);
+ return q;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueMBean.java
new file mode 100644
index 0000000000..c8ead67b1f
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueMBean.java
@@ -0,0 +1,478 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.log4j.Logger;
+
+import org.apache.mina.common.ByteBuffer;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.CommonContentHeaderProperties;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.server.management.AMQManagedObject;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.store.StoreContext;
+
+import javax.management.JMException;
+import javax.management.MBeanException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.Notification;
+import javax.management.OperationsException;
+import javax.management.monitor.MonitorNotification;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * AMQQueueMBean is the management bean for an {@link AMQQueue}.
+ *
+ * <p/><tablse id="crc"><caption>CRC Caption</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * </table>
+ */
+@MBeanDescription("Management Interface for AMQQueue")
+public class AMQQueueMBean extends AMQManagedObject implements ManagedQueue, QueueNotificationListener
+{
+ /** Used for debugging purposes. */
+ private static final Logger _logger = Logger.getLogger(AMQQueueMBean.class);
+
+ private static final SimpleDateFormat _dateFormat = new SimpleDateFormat("MM-dd-yy HH:mm:ss.SSS z");
+
+ /**
+ * Since the MBean is not associated with a real channel we can safely create our own store context
+ * for use in the few methods that require one.
+ */
+ private StoreContext _storeContext = new StoreContext();
+
+ private AMQQueue _queue = null;
+ private String _queueName = null;
+ // OpenMBean data types for viewMessages method
+ private static final String[] _msgAttributeNames = { "AMQ MessageId", "Header", "Size(bytes)", "Redelivered" };
+ private static String[] _msgAttributeIndex = { _msgAttributeNames[0] };
+ private static OpenType[] _msgAttributeTypes = new OpenType[4]; // AMQ message attribute types.
+ private static CompositeType _messageDataType = null; // Composite type for representing AMQ Message data.
+ private static TabularType _messagelistDataType = null; // Datatype for representing AMQ messages list.
+
+ // OpenMBean data types for viewMessageContent method
+ private static CompositeType _msgContentType = null;
+ private static final String[] _msgContentAttributes = { "AMQ MessageId", "MimeType", "Encoding", "Content" };
+ private static OpenType[] _msgContentAttributeTypes = new OpenType[4];
+
+ private final long[] _lastNotificationTimes = new long[NotificationCheck.values().length];
+ private Notification _lastNotification = null;
+
+
+
+
+ @MBeanConstructor("Creates an MBean exposing an AMQQueue")
+ public AMQQueueMBean(AMQQueue queue) throws JMException
+ {
+ super(ManagedQueue.class, ManagedQueue.TYPE, ManagedQueue.VERSION);
+ _queue = queue;
+ _queueName = jmxEncode(new StringBuffer(queue.getName()), 0).toString();
+ }
+
+ public ManagedObject getParentObject()
+ {
+ return _queue.getVirtualHost().getManagedObject();
+ }
+
+ static
+ {
+ try
+ {
+ init();
+ }
+ catch (JMException ex)
+ {
+ // This is not expected to ever occur.
+ throw new RuntimeException("Got JMException in static initializer.", ex);
+ }
+ }
+
+ /**
+ * initialises the openmbean data types
+ */
+ private static void init() throws OpenDataException
+ {
+ _msgContentAttributeTypes[0] = SimpleType.LONG; // For message id
+ _msgContentAttributeTypes[1] = SimpleType.STRING; // For MimeType
+ _msgContentAttributeTypes[2] = SimpleType.STRING; // For Encoding
+ _msgContentAttributeTypes[3] = new ArrayType(1, SimpleType.BYTE); // For message content
+ _msgContentType =
+ new CompositeType("Message Content", "AMQ Message Content", _msgContentAttributes, _msgContentAttributes,
+ _msgContentAttributeTypes);
+
+ _msgAttributeTypes[0] = SimpleType.LONG; // For message id
+ _msgAttributeTypes[1] = new ArrayType(1, SimpleType.STRING); // For header attributes
+ _msgAttributeTypes[2] = SimpleType.LONG; // For size
+ _msgAttributeTypes[3] = SimpleType.BOOLEAN; // For redelivered
+
+ _messageDataType =
+ new CompositeType("Message", "AMQ Message", _msgAttributeNames, _msgAttributeNames, _msgAttributeTypes);
+ _messagelistDataType = new TabularType("Messages", "List of messages", _messageDataType, _msgAttributeIndex);
+ }
+
+ public String getObjectInstanceName()
+ {
+ return _queueName;
+ }
+
+ public String getName()
+ {
+ return _queueName;
+ }
+
+ public boolean isDurable()
+ {
+ return _queue.isDurable();
+ }
+
+ public String getOwner()
+ {
+ return String.valueOf(_queue.getOwner());
+ }
+
+ public boolean isAutoDelete()
+ {
+ return _queue.isAutoDelete();
+ }
+
+ public Integer getMessageCount()
+ {
+ return _queue.getMessageCount();
+ }
+
+ public Long getMaximumMessageSize()
+ {
+ return _queue.getMaximumMessageSize();
+ }
+
+ public Long getMaximumMessageAge()
+ {
+ return _queue.getMaximumMessageAge();
+ }
+
+ public void setMaximumMessageAge(Long maximumMessageAge)
+ {
+ _queue.setMaximumMessageAge(maximumMessageAge);
+ }
+
+ public void setMaximumMessageSize(Long value)
+ {
+ _queue.setMaximumMessageSize(value);
+ }
+
+ public Integer getConsumerCount()
+ {
+ return _queue.getConsumerCount();
+ }
+
+ public Integer getActiveConsumerCount()
+ {
+ return _queue.getActiveConsumerCount();
+ }
+
+ public Long getReceivedMessageCount()
+ {
+ return _queue.getReceivedMessageCount();
+ }
+
+ public Long getMaximumMessageCount()
+ {
+ return _queue.getMaximumMessageCount();
+ }
+
+ public void setMaximumMessageCount(Long value)
+ {
+ _queue.setMaximumMessageCount(value);
+ }
+
+ /**
+ * returns the maximum total size of messages(bytes) in the queue.
+ */
+ public Long getMaximumQueueDepth()
+ {
+ return _queue.getMaximumQueueDepth();
+ }
+
+ public void setMaximumQueueDepth(Long value)
+ {
+ _queue.setMaximumQueueDepth(value);
+ }
+
+ /**
+ * returns the total size of messages(bytes) in the queue.
+ */
+ public Long getQueueDepth() throws JMException
+ {
+ return _queue.getQueueDepth();
+ }
+
+ /**
+ * Checks if there is any notification to be send to the listeners
+ */
+ public void checkForNotification(AMQMessage msg) throws AMQException
+ {
+
+ final Set<NotificationCheck> notificationChecks = _queue.getNotificationChecks();
+
+ if(!notificationChecks.isEmpty())
+ {
+ final long currentTime = System.currentTimeMillis();
+ final long thresholdTime = currentTime - _queue.getMinimumAlertRepeatGap();
+
+ for (NotificationCheck check : notificationChecks)
+ {
+ if (check.isMessageSpecific() || (_lastNotificationTimes[check.ordinal()] < thresholdTime))
+ {
+ if (check.notifyIfNecessary(msg, _queue, this))
+ {
+ _lastNotificationTimes[check.ordinal()] = currentTime;
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Sends the notification to the listeners
+ */
+ public void notifyClients(NotificationCheck notification, AMQQueue queue, String notificationMsg)
+ {
+ // important : add log to the log file - monitoring tools may be looking for this
+ _logger.info(notification.name() + " On Queue " + queue.getName() + " - " + notificationMsg);
+ notificationMsg = notification.name() + " " + notificationMsg;
+
+ _lastNotification =
+ new Notification(MonitorNotification.THRESHOLD_VALUE_EXCEEDED, this, ++_notificationSequenceNumber,
+ System.currentTimeMillis(), notificationMsg);
+
+ _broadcaster.sendNotification(_lastNotification);
+ }
+
+ public Notification getLastNotification()
+ {
+ return _lastNotification;
+ }
+
+ /**
+ * @see AMQQueue#deleteMessageFromTop
+ */
+ public void deleteMessageFromTop() throws JMException
+ {
+ try
+ {
+ _queue.deleteMessageFromTop(_storeContext);
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+
+ /**
+ * @see AMQQueue#clearQueue
+ */
+ public void clearQueue() throws JMException
+ {
+ try
+ {
+ _queue.clearQueue(_storeContext);
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+
+ /**
+ * returns message content as byte array and related attributes for the given message id.
+ */
+ public CompositeData viewMessageContent(long msgId) throws JMException
+ {
+ QueueEntry entry = _queue.getMessageOnTheQueue(msgId);
+
+ if (entry == null)
+ {
+ throw new OperationsException("AMQMessage with message id = " + msgId + " is not in the " + _queueName);
+ }
+
+ AMQMessage msg = entry.getMessage();
+ // get message content
+ Iterator<ContentChunk> cBodies = msg.getContentBodyIterator();
+ List<Byte> msgContent = new ArrayList<Byte>();
+ while (cBodies.hasNext())
+ {
+ ContentChunk body = cBodies.next();
+ if (body.getSize() != 0)
+ {
+ if (body.getSize() != 0)
+ {
+ ByteBuffer slice = body.getData().slice();
+ for (int j = 0; j < slice.limit(); j++)
+ {
+ msgContent.add(slice.get());
+ }
+ }
+ }
+ }
+
+ try
+ {
+ // Create header attributes list
+ CommonContentHeaderProperties headerProperties =
+ (CommonContentHeaderProperties) msg.getContentHeaderBody().properties;
+ String mimeType = null, encoding = null;
+ if (headerProperties != null)
+ {
+ AMQShortString mimeTypeShortSting = headerProperties.getContentType();
+ mimeType = (mimeTypeShortSting == null) ? null : mimeTypeShortSting.toString();
+ encoding = (headerProperties.getEncoding() == null) ? "" : headerProperties.getEncoding().toString();
+ }
+
+ Object[] itemValues = { msgId, mimeType, encoding, msgContent.toArray(new Byte[0]) };
+
+ return new CompositeDataSupport(_msgContentType, _msgContentAttributes, itemValues);
+ }
+ catch (AMQException e)
+ {
+ JMException jme = new JMException("Error creating header attributes list: " + e);
+ jme.initCause(e);
+ throw jme;
+ }
+ }
+
+ /**
+ * Returns the header contents of the messages stored in this queue in tabular form.
+ */
+ public TabularData viewMessages(int beginIndex, int endIndex) throws JMException
+ {
+ if ((beginIndex > endIndex) || (beginIndex < 1))
+ {
+ throw new OperationsException("From Index = " + beginIndex + ", To Index = " + endIndex
+ + "\n\"From Index\" should be greater than 0 and less than \"To Index\"");
+ }
+
+ List<QueueEntry> list = _queue.getMessagesOnTheQueue();
+ TabularDataSupport _messageList = new TabularDataSupport(_messagelistDataType);
+
+ try
+ {
+ // Create the tabular list of message header contents
+ for (int i = beginIndex; (i <= endIndex) && (i <= list.size()); i++)
+ {
+ AMQMessage msg = list.get(i - 1).getMessage();
+ ContentHeaderBody headerBody = msg.getContentHeaderBody();
+ // Create header attributes list
+ String[] headerAttributes = getMessageHeaderProperties(headerBody);
+ Object[] itemValues = { msg.getMessageId(), headerAttributes, headerBody.bodySize, msg.isRedelivered() };
+ CompositeData messageData = new CompositeDataSupport(_messageDataType, _msgAttributeNames, itemValues);
+ _messageList.put(messageData);
+ }
+ }
+ catch (AMQException e)
+ {
+ JMException jme = new JMException("Error creating message contents: " + e);
+ jme.initCause(e);
+ throw jme;
+ }
+
+ return _messageList;
+ }
+
+ private String[] getMessageHeaderProperties(ContentHeaderBody headerBody)
+ {
+ List<String> list = new ArrayList<String>();
+ BasicContentHeaderProperties headerProperties = (BasicContentHeaderProperties) headerBody.properties;
+ list.add("reply-to = " + headerProperties.getReplyToAsString());
+ list.add("propertyFlags = " + headerProperties.getPropertyFlags());
+ list.add("ApplicationID = " + headerProperties.getAppIdAsString());
+ list.add("ClusterID = " + headerProperties.getClusterIdAsString());
+ list.add("UserId = " + headerProperties.getUserIdAsString());
+ list.add("JMSMessageID = " + headerProperties.getMessageIdAsString());
+ list.add("JMSCorrelationID = " + headerProperties.getCorrelationIdAsString());
+
+ int delMode = headerProperties.getDeliveryMode();
+ list.add("JMSDeliveryMode = " + ((delMode == 1) ? "Persistent" : "Non_Persistent"));
+
+ list.add("JMSPriority = " + headerProperties.getPriority());
+ list.add("JMSType = " + headerProperties.getType());
+
+ long longDate = headerProperties.getExpiration();
+ String strDate = (longDate != 0) ? _dateFormat.format(new Date(longDate)) : null;
+ list.add("JMSExpiration = " + strDate);
+
+ longDate = headerProperties.getTimestamp();
+ strDate = (longDate != 0) ? _dateFormat.format(new Date(longDate)) : null;
+ list.add("JMSTimestamp = " + strDate);
+
+ return list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * @see ManagedQueue#moveMessages
+ * @param fromMessageId
+ * @param toMessageId
+ * @param toQueueName
+ * @throws JMException
+ */
+ public void moveMessages(long fromMessageId, long toMessageId, String toQueueName) throws JMException
+ {
+ if ((fromMessageId > toMessageId) || (fromMessageId < 1))
+ {
+ throw new OperationsException("\"From MessageId\" should be greater then 0 and less then \"To MessageId\"");
+ }
+
+ _queue.moveMessagesToAnotherQueue(fromMessageId, toMessageId, toQueueName, _storeContext);
+ }
+
+ /**
+ * returns Notifications sent by this MBean.
+ */
+ @Override
+ public MBeanNotificationInfo[] getNotificationInfo()
+ {
+ String[] notificationTypes = new String[] { MonitorNotification.THRESHOLD_VALUE_EXCEEDED };
+ String name = MonitorNotification.class.getName();
+ String description = "Either Message count or Queue depth or Message size has reached threshold high value";
+ MBeanNotificationInfo info1 = new MBeanNotificationInfo(notificationTypes, name, description);
+
+ return new MBeanNotificationInfo[] { info1 };
+ }
+
+} // End of AMQQueueMBean class
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java
new file mode 100644
index 0000000000..cbe9246f09
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java
@@ -0,0 +1,71 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public class DefaultQueueRegistry implements QueueRegistry
+{
+ private ConcurrentMap<AMQShortString, AMQQueue> _queueMap = new ConcurrentHashMap<AMQShortString, AMQQueue>();
+
+ private final VirtualHost _virtualHost;
+
+ public DefaultQueueRegistry(VirtualHost virtualHost)
+ {
+ _virtualHost = virtualHost;
+ }
+
+ public VirtualHost getVirtualHost()
+ {
+ return _virtualHost;
+ }
+
+ public void registerQueue(AMQQueue queue) throws AMQException
+ {
+ _queueMap.put(queue.getName(), queue);
+ }
+
+ public void unregisterQueue(AMQShortString name) throws AMQException
+ {
+ _queueMap.remove(name);
+ }
+
+ public AMQQueue getQueue(AMQShortString name)
+ {
+ return _queueMap.get(name);
+ }
+
+ public Collection<AMQShortString> getQueueNames()
+ {
+ return _queueMap.keySet();
+ }
+
+ public Collection<AMQQueue> getQueues()
+ {
+ return _queueMap.values();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBinding.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBinding.java
new file mode 100644
index 0000000000..a2fcab9e73
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBinding.java
@@ -0,0 +1,84 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.AMQException;
+
+public class ExchangeBinding
+{
+ private final Exchange _exchange;
+ private final AMQShortString _routingKey;
+ private final FieldTable _arguments;
+
+ private static final FieldTable EMPTY_ARGUMENTS = new FieldTable();
+
+ ExchangeBinding(AMQShortString routingKey, Exchange exchange)
+ {
+ this(routingKey, exchange, EMPTY_ARGUMENTS);
+ }
+
+ ExchangeBinding(AMQShortString routingKey, Exchange exchange, FieldTable arguments)
+ {
+ _routingKey = routingKey == null ? AMQShortString.EMPTY_STRING : routingKey;
+ _exchange = exchange;
+ _arguments = arguments == null ? EMPTY_ARGUMENTS : arguments;
+ }
+
+ void unbind(AMQQueue queue) throws AMQException
+ {
+ _exchange.deregisterQueue(_routingKey, queue, _arguments);
+ }
+
+ public Exchange getExchange()
+ {
+ return _exchange;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return _routingKey;
+ }
+
+ public FieldTable getArguments()
+ {
+ return _arguments;
+ }
+
+ public int hashCode()
+ {
+ return (_exchange == null ? 0 : _exchange.hashCode())
+ + (_routingKey == null ? 0 : _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);
+ }
+} \ No newline at end of file
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java
new file mode 100644
index 0000000000..fb839c1783
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java
@@ -0,0 +1,82 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.exchange.Exchange;
+
+/**
+ * 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
+{
+ 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(AMQShortString routingKey, FieldTable arguments, Exchange exchange)
+ {
+ _bindings.add(new ExchangeBinding(routingKey, exchange, arguments));
+ }
+
+
+ public boolean remove(AMQShortString routingKey, FieldTable arguments, Exchange exchange)
+ {
+ return _bindings.remove(new ExchangeBinding(routingKey, exchange, arguments));
+ }
+
+
+ /**
+ * 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/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java
new file mode 100644
index 0000000000..6466e81dd2
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java
@@ -0,0 +1,50 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+
+/**
+ * Signals that the dequeue of a message from a queue failed.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Indicates the a message could not be dequeued from a queue.
+ * <tr><td>
+ * </table>
+ *
+ * @todo Not an AMQP exception as no status code.
+ *
+ * @todo Happens as a consequence of a message store failure, or reference counting error. Both of which migh become
+ * runtime exceptions, as unrecoverable conditions? In which case this one might be dropped too.
+ */
+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/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Filterable.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Filterable.java
new file mode 100644
index 0000000000..d38932bb61
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Filterable.java
@@ -0,0 +1,33 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.AMQException;
+
+public interface Filterable<E extends Exception>
+{
+ ContentHeaderBody getContentHeaderBody() throws E;
+
+ boolean isPersistent() throws E;
+
+ boolean isRedelivered();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java
new file mode 100644
index 0000000000..35ad5be4e0
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java
@@ -0,0 +1,157 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Collections;
+import java.util.ArrayList;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.server.store.StoreContext;
+
+/**
+ */
+public class InMemoryMessageHandle implements AMQMessageHandle
+{
+
+ private ContentHeaderBody _contentHeaderBody;
+
+ private MessagePublishInfo _messagePublishInfo;
+
+ private List<ContentChunk> _contentBodies;
+
+ private boolean _redelivered;
+
+ private long _arrivalTime;
+
+ private final Long _messageId;
+
+ public InMemoryMessageHandle(final Long messageId)
+ {
+ _messageId = messageId;
+ }
+
+ public ContentHeaderBody getContentHeaderBody(StoreContext context) throws AMQException
+ {
+ return _contentHeaderBody;
+ }
+
+ public Long getMessageId()
+ {
+ return _messageId;
+ }
+
+ public int getBodyCount(StoreContext context)
+ {
+ return _contentBodies.size();
+ }
+
+ public long getBodySize(StoreContext context) throws AMQException
+ {
+ return getContentHeaderBody(context).bodySize;
+ }
+
+ public ContentChunk getContentChunk(StoreContext context, 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(StoreContext storeContext, ContentChunk contentBody, boolean isLastContentBody)
+ throws AMQException
+ {
+ if(_contentBodies == null)
+ {
+ if(isLastContentBody)
+ {
+ _contentBodies = Collections.singletonList(contentBody);
+ }
+ else
+ {
+ _contentBodies = new ArrayList<ContentChunk>();
+ _contentBodies.add(contentBody);
+ }
+ }
+ else
+ {
+ _contentBodies.add(contentBody);
+ }
+ }
+
+ public MessagePublishInfo getMessagePublishInfo(StoreContext context) throws AMQException
+ {
+ return _messagePublishInfo;
+ }
+
+ public boolean isRedelivered()
+ {
+ return _redelivered;
+ }
+
+
+ public void setRedelivered(boolean redelivered)
+ {
+ _redelivered = redelivered;
+ }
+
+ public boolean isPersistent()
+ {
+ return false;
+ }
+
+ /**
+ * This is called when all the content has been received.
+ * @param messagePublishInfo
+ * @param contentHeaderBody
+ * @throws AMQException
+ */
+ public void setPublishAndContentHeaderBody(StoreContext storeContext, MessagePublishInfo messagePublishInfo,
+ ContentHeaderBody contentHeaderBody)
+ throws AMQException
+ {
+ _messagePublishInfo = messagePublishInfo;
+ _contentHeaderBody = contentHeaderBody;
+ if(contentHeaderBody.bodySize == 0)
+ {
+ _contentBodies = Collections.EMPTY_LIST;
+ }
+ _arrivalTime = System.currentTimeMillis();
+ }
+
+ public void removeMessage(StoreContext storeContext) throws AMQException
+ {
+ // NO OP
+ }
+
+ public long getArrivalTime()
+ {
+ return _arrivalTime;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/IncomingMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/IncomingMessage.java
new file mode 100644
index 0000000000..091baf2751
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/IncomingMessage.java
@@ -0,0 +1,319 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.exchange.NoRouteException;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.AMQException;
+import org.apache.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class IncomingMessage implements Filterable<RuntimeException>
+{
+
+ /** Used for debugging purposes. */
+ private static final Logger _logger = Logger.getLogger(IncomingMessage.class);
+
+ private static final boolean SYNCHED_CLOCKS =
+ ApplicationRegistry.getInstance().getConfiguration().getSynchedClocks();
+
+ private final MessagePublishInfo _messagePublishInfo;
+ private ContentHeaderBody _contentHeaderBody;
+ private AMQMessageHandle _messageHandle;
+ private final Long _messageId;
+ private final TransactionalContext _txnContext;
+
+ private static final boolean MSG_AUTH =
+ ApplicationRegistry.getInstance().getConfiguration().getMsgAuth();
+
+
+ /**
+ * Keeps a track of how many bytes we have received in body frames
+ */
+ private long _bodyLengthReceived = 0;
+
+ /**
+ * 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 ArrayList<AMQQueue> _destinationQueues;
+
+ private AMQProtocolSession _publisher;
+ private MessageStore _messageStore;
+ private long _expiration;
+
+ private Exchange _exchange;
+
+
+ public IncomingMessage(final Long messageId,
+ final MessagePublishInfo info,
+ final TransactionalContext txnContext,
+ final AMQProtocolSession publisher)
+ {
+ _messageId = messageId;
+ _messagePublishInfo = info;
+ _txnContext = txnContext;
+ _publisher = publisher;
+
+ }
+
+ public void setContentHeaderBody(final ContentHeaderBody contentHeaderBody) throws AMQException
+ {
+ _contentHeaderBody = contentHeaderBody;
+ }
+
+ public void setExpiration()
+ {
+ long expiration =
+ ((BasicContentHeaderProperties) _contentHeaderBody.properties).getExpiration();
+ long timestamp =
+ ((BasicContentHeaderProperties) _contentHeaderBody.properties).getTimestamp();
+
+ if (SYNCHED_CLOCKS)
+ {
+ _expiration = expiration;
+ }
+ else
+ {
+ // Update TTL to be in broker time.
+ if (expiration != 0L)
+ {
+ if (timestamp != 0L)
+ {
+ // todo perhaps use arrival time
+ long diff = (System.currentTimeMillis() - timestamp);
+
+ if ((diff > 1000L) || (diff < 1000L))
+ {
+ _expiration = expiration + diff;
+ }
+ }
+ }
+ }
+
+ }
+
+ public void routingComplete(final MessageStore store,
+ final 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
+
+ if(_destinationQueues != null)
+ {
+ for (int i = 0; i < _destinationQueues.size(); i++)
+ {
+ store.enqueueMessage(_txnContext.getStoreContext(),
+ _destinationQueues.get(i), _messageId);
+ }
+ }
+ }
+ }
+
+ public AMQMessage deliverToQueues()
+ throws AMQException
+ {
+
+ // we get a reference to the destination queues now so that we can clear the
+ // transient message data as quickly as possible
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Delivering message " + _messageId + " to " + _destinationQueues);
+ }
+
+ AMQMessage message = null;
+
+ try
+ {
+ // 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
+ _messageHandle.setPublishAndContentHeaderBody(_txnContext.getStoreContext(),
+ _messagePublishInfo, getContentHeaderBody());
+
+
+
+ message = new AMQMessage(_messageHandle,_txnContext.getStoreContext(), _messagePublishInfo);
+
+ message.setExpiration(_expiration);
+ message.setClientIdentifier(_publisher.getSessionIdentifier());
+
+ // 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());
+
+ AMQShortString userID = getContentHeaderBody().properties instanceof BasicContentHeaderProperties ?
+ ((BasicContentHeaderProperties) getContentHeaderBody().properties).getUserId() : null;
+
+ if (MSG_AUTH && !_publisher.getAuthorizedID().getName().equals(userID == null? "" : userID.toString()))
+ {
+ throw new UnauthorizedAccessException("Acccess Refused",message);
+ }
+
+ if ((_destinationQueues == null) || _destinationQueues.size() == 0)
+ {
+
+ if (isMandatory() || isImmediate())
+ {
+ throw new NoRouteException("No Route for message", message);
+
+ }
+ else
+ {
+ _logger.warn("MESSAGE DISCARDED: No routes for message - " + message);
+ }
+ }
+ else
+ {
+ int offset;
+ final int queueCount = _destinationQueues.size();
+ message.incrementReference(queueCount);
+ if(queueCount == 1)
+ {
+ offset = 0;
+ }
+ else
+ {
+ offset = ((int)(message.getMessageId().longValue())) % queueCount;
+ if(offset < 0)
+ {
+ offset = -offset;
+ }
+ }
+ for (int i = offset; i < queueCount; i++)
+ {
+ // normal deliver so add this message at the end.
+ _txnContext.deliver(_destinationQueues.get(i), message);
+ }
+ for (int i = 0; i < offset; i++)
+ {
+ // normal deliver so add this message at the end.
+ _txnContext.deliver(_destinationQueues.get(i), message);
+ }
+ }
+
+ message.clearStoreContext();
+ return message;
+ }
+ finally
+ {
+ // Remove refence for routing process . Reference count should now == delivered queue count
+ if(message != null) message.decrementReference(_txnContext.getStoreContext());
+ }
+
+ }
+
+ public void addContentBodyFrame(final ContentChunk contentChunk)
+ throws AMQException
+ {
+
+ _bodyLengthReceived += contentChunk.getSize();
+
+ _messageHandle.addContentBodyFrame(_txnContext.getStoreContext(), contentChunk, allContentReceived());
+
+ }
+
+ public boolean allContentReceived()
+ {
+ return (_bodyLengthReceived == getContentHeaderBody().bodySize);
+ }
+
+ public AMQShortString getExchange() throws AMQException
+ {
+ return _messagePublishInfo.getExchange();
+ }
+
+ public AMQShortString getRoutingKey() throws AMQException
+ {
+ return _messagePublishInfo.getRoutingKey();
+ }
+
+ public boolean isMandatory() throws AMQException
+ {
+ return _messagePublishInfo.isMandatory();
+ }
+
+
+ public boolean isImmediate() throws AMQException
+ {
+ return _messagePublishInfo.isImmediate();
+ }
+
+ public ContentHeaderBody getContentHeaderBody()
+ {
+ return _contentHeaderBody;
+ }
+
+
+ public boolean isPersistent()
+ {
+ //todo remove literal values to a constant file such as AMQConstants in common
+ return getContentHeaderBody().properties instanceof BasicContentHeaderProperties &&
+ ((BasicContentHeaderProperties) getContentHeaderBody().properties).getDeliveryMode() == 2;
+ }
+
+ public boolean isRedelivered()
+ {
+ return false;
+ }
+
+ public void setMessageStore(final MessageStore messageStore)
+ {
+ _messageStore = messageStore;
+ }
+
+ public Long getMessageId()
+ {
+ return _messageId;
+ }
+
+ public void setExchange(final Exchange e)
+ {
+ _exchange = e;
+ }
+
+ public void route() throws AMQException
+ {
+ _exchange.route(this);
+ }
+
+ public void enqueue(final ArrayList<AMQQueue> queues)
+ {
+ _destinationQueues = queues;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java
new file mode 100644
index 0000000000..579656893b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java
@@ -0,0 +1,245 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import java.io.IOException;
+
+import javax.management.JMException;
+import javax.management.MBeanOperationInfo;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.TabularData;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.management.MBeanAttribute;
+import org.apache.qpid.server.management.MBeanOperation;
+import org.apache.qpid.server.management.MBeanOperationParameter;
+
+/**
+ * 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";
+ static final int VERSION = 2;
+
+ /**
+ * Returns the Name of the ManagedQueue.
+ * @return the name of the managedQueue.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="Name", description = TYPE + " Name")
+ String getName() 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;
+
+ /**
+ * 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;
+
+ /**
+ * Size of messages in the queue
+ * @return
+ * @throws IOException
+ */
+ @MBeanAttribute(name="QueueDepth", description="The total size(Bytes) of messages in the queue")
+ Long getQueueDepth() throws IOException, JMException;
+
+ /**
+ * 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;
+
+ /**
+ * 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;
+
+ /**
+ * Tells the Owner of the ManagedQueue.
+ * @return the owner's name.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="Owner", description = "Owner")
+ String getOwner() 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 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;
+
+ /**
+ * Returns the maximum age of a message (expiration time) in milliseconds
+ * @return the maximum age
+ * @throws IOException
+ */
+ Long getMaximumMessageAge() throws IOException;
+
+ /**
+ * Sets the maximum age of a message in milliseconds
+ * @param age maximum age of message.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="MaximumMessageAge", description="Threshold high value(milliseconds) for message age")
+ void setMaximumMessageAge(Long age) throws IOException;
+
+ /**
+ * Returns the maximum size of a message (in Bytes) 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 Bytes) that is allowed to be
+ * accepted by the Queue.
+ * @param size maximum size of message.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="MaximumMessageSize", description="Threshold high value(Bytes) for a message size")
+ void setMaximumMessageSize(Long size) 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
+ */
+ Long 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="Threshold high value for number of undelivered messages in the queue")
+ void setMaximumMessageCount(Long value) throws IOException;
+
+ /**
+ * This is useful for setting notifications or taking required action if the size of messages
+ * stored in the queue increases over this limit.
+ * @return threshold high value for Queue Depth
+ * @throws IOException
+ */
+ Long getMaximumQueueDepth() throws IOException;
+
+ /**
+ * Sets the maximum size of all the messages together, that can be stored
+ * in the queue.
+ * @param value
+ * @throws IOException
+ */
+ @MBeanAttribute(name="MaximumQueueDepth", description="The threshold high value(Bytes) for Queue Depth")
+ void setMaximumQueueDepth(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="Message headers for messages in this queue within given index range. 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, AMQException;
+
+ @MBeanOperation(name="viewMessageContent", description="The message content for given Message Id")
+ CompositeData viewMessageContent(@MBeanOperationParameter(name="Message Id", description="Message Id")long messageId)
+ 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;
+
+ /**
+ * Moves the messages in given range of message Ids to given Queue. QPID-170
+ * @param fromMessageId first in the range of message ids
+ * @param toMessageId last in the range of message ids
+ * @param toQueue where the messages are to be moved
+ * @throws IOException
+ * @throws JMException
+ * @throws AMQException
+ */
+ @MBeanOperation(name="moveMessages",
+ description="You can move messages to another queue from this queue ",
+ impact= MBeanOperationInfo.ACTION)
+ void moveMessages(@MBeanOperationParameter(name="from MessageId", description="from MessageId")long fromMessageId,
+ @MBeanOperationParameter(name="to MessageId", description="to MessageId")long toMessageId,
+ @MBeanOperationParameter(name= ManagedQueue.TYPE, description="to Queue Name")String toQueue)
+ throws IOException, JMException, AMQException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java
new file mode 100644
index 0000000000..090096d3c3
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+
+/**
+ * MessageCleanupException represents the failure to perform reference counting on messages correctly. This should not
+ * happen, but there may be programming errors giving race conditions that cause the reference counting to go wrong.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Signals that the reference count of a message has gone below zero.
+ * <tr><td> Indicates that a message store has lost a message which is still referenced.
+ * </table>
+ *
+ * @todo Not an AMQP exception as no status code.
+ *
+ * @todo The race conditions leading to this error should be cleaned up, and a runtime exception used instead. If the
+ * message store loses messages, then something is seriously wrong and it would be sensible to terminate the
+ * broker. This may be disguising out of memory errors.
+ */
+public class MessageCleanupException extends AMQException
+{
+ public MessageCleanupException(long messageId, AMQException e)
+ {
+ super("Failed to cleanup message with id " + messageId, e);
+ }
+
+ public MessageCleanupException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.java
new file mode 100644
index 0000000000..0b214ca336
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.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(messageId, store);
+ }
+ else
+ {
+ return new InMemoryMessageHandle(messageId);
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java
new file mode 100644
index 0000000000..6118a4c11f
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java
@@ -0,0 +1,92 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+
+/**
+ * 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 MessagePublishInfo _messagePublishInfo;
+
+ private ContentHeaderBody _contentHeaderBody;
+
+ private int _contentChunkCount;
+
+ private long _arrivalTime;
+
+ public MessageMetaData(MessagePublishInfo publishBody, ContentHeaderBody contentHeaderBody, int contentChunkCount)
+ {
+ this(publishBody,contentHeaderBody, contentChunkCount, System.currentTimeMillis());
+ }
+
+ public MessageMetaData(MessagePublishInfo publishBody, ContentHeaderBody contentHeaderBody, int contentChunkCount, long arrivalTime)
+ {
+ _contentHeaderBody = contentHeaderBody;
+ _messagePublishInfo = publishBody;
+ _contentChunkCount = contentChunkCount;
+ _arrivalTime = arrivalTime;
+ }
+
+ 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 MessagePublishInfo getMessagePublishInfo()
+ {
+ return _messagePublishInfo;
+ }
+
+ public void setMessagePublishInfo(MessagePublishInfo messagePublishInfo)
+ {
+ _messagePublishInfo = messagePublishInfo;
+ }
+
+ public long getArrivalTime()
+ {
+ return _arrivalTime;
+ }
+
+ public void setArrivalTime(long arrivalTime)
+ {
+ _arrivalTime = arrivalTime;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java
new file mode 100644
index 0000000000..d6fd1eec89
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java
@@ -0,0 +1,47 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.RequiredDeliveryException;
+
+/**
+ * NoConsumersException is a {@link RequiredDeliveryException} that represents the failure case where an immediate
+ * message cannot be delivered because there are presently no consumers for the message. The AMQP status code, 313, is
+ * always used to report this condition.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represent failure to deliver a message that must be delivered.
+ * </table>
+ */
+public class NoConsumersException extends RequiredDeliveryException
+{
+ public NoConsumersException(AMQMessage message)
+ {
+ super("Immediate delivery is not possible.", message);
+ }
+
+ public AMQConstant getReplyCode()
+ {
+ return AMQConstant.NO_CONSUMERS;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java
new file mode 100644
index 0000000000..6f9efd3200
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java
@@ -0,0 +1,138 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+
+public enum NotificationCheck
+{
+
+ MESSAGE_COUNT_ALERT
+ {
+ boolean notifyIfNecessary(AMQMessage msg, AMQQueue queue, QueueNotificationListener listener)
+ {
+ int msgCount;
+ final long maximumMessageCount = queue.getMaximumMessageCount();
+ if (maximumMessageCount!= 0 && (msgCount = queue.getMessageCount()) >= maximumMessageCount)
+ {
+ listener.notifyClients(this, queue, msgCount + ": Maximum count on queue threshold ("+ maximumMessageCount +") breached.");
+ return true;
+ }
+ return false;
+ }
+ },
+ MESSAGE_SIZE_ALERT(true)
+ {
+ boolean notifyIfNecessary(AMQMessage msg, AMQQueue queue, QueueNotificationListener listener)
+ {
+ final long maximumMessageSize = queue.getMaximumMessageSize();
+ if(maximumMessageSize != 0)
+ {
+ // Check for threshold message size
+ long messageSize;
+ try
+ {
+ messageSize = (msg == null) ? 0 : msg.getContentHeaderBody().bodySize;
+ }
+ catch (AMQException e)
+ {
+ messageSize = 0;
+ }
+
+
+ if (messageSize >= maximumMessageSize)
+ {
+ listener.notifyClients(this, queue, messageSize + "b : Maximum message size threshold ("+ maximumMessageSize +") breached. [Message ID=" + msg.getMessageId() + "]");
+ return true;
+ }
+ }
+ return false;
+ }
+
+ },
+ QUEUE_DEPTH_ALERT
+ {
+ boolean notifyIfNecessary(AMQMessage msg, AMQQueue queue, QueueNotificationListener listener)
+ {
+ // Check for threshold queue depth in bytes
+ final long maximumQueueDepth = queue.getMaximumQueueDepth();
+
+ if(maximumQueueDepth != 0)
+ {
+ final long queueDepth = queue.getQueueDepth();
+
+ if (queueDepth >= maximumQueueDepth)
+ {
+ listener.notifyClients(this, queue, (queueDepth>>10) + "Kb : Maximum queue depth threshold ("+(maximumQueueDepth>>10)+"Kb) breached.");
+ return true;
+ }
+ }
+ return false;
+ }
+
+ },
+ MESSAGE_AGE_ALERT
+ {
+ boolean notifyIfNecessary(AMQMessage msg, AMQQueue queue, QueueNotificationListener listener)
+ {
+
+ final long maxMessageAge = queue.getMaximumMessageAge();
+ if(maxMessageAge != 0)
+ {
+ final long currentTime = System.currentTimeMillis();
+ final long thresholdTime = currentTime - maxMessageAge;
+ final long firstArrivalTime = queue.getOldestMessageArrivalTime();
+
+ if(firstArrivalTime < thresholdTime)
+ {
+ long oldestAge = currentTime - firstArrivalTime;
+ listener.notifyClients(this, queue, (oldestAge/1000) + "s : Maximum age on queue threshold ("+(maxMessageAge /1000)+"s) breached.");
+
+ return true;
+ }
+ }
+ return false;
+
+ }
+
+ }
+ ;
+
+ private final boolean _messageSpecific;
+
+ NotificationCheck()
+ {
+ this(false);
+ }
+
+ NotificationCheck(boolean messageSpecific)
+ {
+ _messageSpecific = messageSpecific;
+ }
+
+ public boolean isMessageSpecific()
+ {
+ return _messageSpecific;
+ }
+
+ abstract boolean notifyIfNecessary(AMQMessage msg, AMQQueue queue, QueueNotificationListener listener);
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java
new file mode 100644
index 0000000000..fd46a8a5ff
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java
@@ -0,0 +1,169 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.framing.CommonContentHeaderProperties;
+import org.apache.qpid.AMQException;
+
+public class PriorityQueueList implements QueueEntryList
+{
+ private final AMQQueue _queue;
+ private final QueueEntryList[] _priorityLists;
+ private final int _priorities;
+ private final int _priorityOffset;
+
+ public PriorityQueueList(AMQQueue queue, int priorities)
+ {
+ _queue = queue;
+ _priorityLists = new QueueEntryList[priorities];
+ _priorities = priorities;
+ _priorityOffset = 5-((priorities + 1)/2);
+ for(int i = 0; i < priorities; i++)
+ {
+ _priorityLists[i] = new SimpleQueueEntryList(queue);
+ }
+ }
+
+ public int getPriorities()
+ {
+ return _priorities;
+ }
+
+ public AMQQueue getQueue()
+ {
+ return _queue;
+ }
+
+ public QueueEntry add(AMQMessage message)
+ {
+ try
+ {
+ int index = ((CommonContentHeaderProperties)((message.getContentHeaderBody().properties))).getPriority() - _priorityOffset;
+ if(index >= _priorities)
+ {
+ index = _priorities-1;
+ }
+ else if(index < 0)
+ {
+ index = 0;
+ }
+ return _priorityLists[index].add(message);
+ }
+ catch (AMQException e)
+ {
+ // TODO - fix AMQ Exception
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public QueueEntry next(QueueEntry node)
+ {
+ QueueEntryImpl nodeImpl = (QueueEntryImpl)node;
+ QueueEntry next = nodeImpl.getNext();
+
+ if(next == null)
+ {
+ QueueEntryList nodeEntryList = nodeImpl.getQueueEntryList();
+ int index;
+ for(index = _priorityLists.length-1; _priorityLists[index] != nodeEntryList; index--);
+
+ while(next == null && index != 0)
+ {
+ index--;
+ next = ((QueueEntryImpl)_priorityLists[index].getHead()).getNext();
+ }
+
+ }
+ return next;
+ }
+
+ private final class PriorityQueueEntryListIterator implements QueueEntryIterator
+ {
+ private final QueueEntryIterator[] _iterators = new QueueEntryIterator[ _priorityLists.length ];
+ private QueueEntry _lastNode;
+
+ PriorityQueueEntryListIterator()
+ {
+ for(int i = 0; i < _priorityLists.length; i++)
+ {
+ _iterators[i] = _priorityLists[i].iterator();
+ }
+ _lastNode = _iterators[_iterators.length - 1].getNode();
+ }
+
+
+ public boolean atTail()
+ {
+ for(int i = 0; i < _iterators.length; i++)
+ {
+ if(!_iterators[i].atTail())
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public QueueEntry getNode()
+ {
+ return _lastNode;
+ }
+
+ public boolean advance()
+ {
+ for(int i = _iterators.length-1; i >= 0; i--)
+ {
+ if(_iterators[i].advance())
+ {
+ _lastNode = _iterators[i].getNode();
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ public QueueEntryIterator iterator()
+ {
+ return new PriorityQueueEntryListIterator();
+ }
+
+ public QueueEntry getHead()
+ {
+ return _priorityLists[_priorities-1].getHead();
+ }
+
+ static class Factory implements QueueEntryListFactory
+ {
+ private final int _priorities;
+
+ Factory(int priorities)
+ {
+ _priorities = priorities;
+ }
+
+ public QueueEntryList createQueueEntryList(AMQQueue queue)
+ {
+ return new PriorityQueueList(queue, _priorities);
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.java
new file mode 100644
index 0000000000..2657c459a9
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.java
@@ -0,0 +1,184 @@
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.subscription.Subscription;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public interface QueueEntry extends Comparable<QueueEntry>
+{
+
+
+
+ public static enum State
+ {
+ AVAILABLE,
+ ACQUIRED,
+ EXPIRED,
+ DEQUEUED,
+ DELETED
+ }
+
+ public static interface StateChangeListener
+ {
+ public void stateChanged(QueueEntry entry, State oldSate, State newState);
+ }
+
+ public abstract class EntryState
+ {
+ private EntryState()
+ {
+ }
+
+ public abstract State getState();
+ }
+
+
+ public final class AvailableState extends EntryState
+ {
+
+ public State getState()
+ {
+ return State.AVAILABLE;
+ }
+ }
+
+
+ public final class DequeuedState extends EntryState
+ {
+
+ public State getState()
+ {
+ return State.DEQUEUED;
+ }
+ }
+
+
+ public final class DeletedState extends EntryState
+ {
+
+ public State getState()
+ {
+ return State.DELETED;
+ }
+ }
+
+ public final class ExpiredState extends EntryState
+ {
+
+ public State getState()
+ {
+ return State.EXPIRED;
+ }
+ }
+
+
+ public final class NonSubscriptionAcquiredState extends EntryState
+ {
+ public State getState()
+ {
+ return State.ACQUIRED;
+ }
+ }
+
+ public final class SubscriptionAcquiredState extends EntryState
+ {
+ private final Subscription _subscription;
+
+ public SubscriptionAcquiredState(Subscription subscription)
+ {
+ _subscription = subscription;
+ }
+
+
+ public State getState()
+ {
+ return State.ACQUIRED;
+ }
+
+ public Subscription getSubscription()
+ {
+ return _subscription;
+ }
+ }
+
+
+ final static EntryState AVAILABLE_STATE = new AvailableState();
+ final static EntryState DELETED_STATE = new DeletedState();
+ final static EntryState DEQUEUED_STATE = new DequeuedState();
+ final static EntryState EXPIRED_STATE = new ExpiredState();
+ final static EntryState NON_SUBSCRIPTION_ACQUIRED_STATE = new NonSubscriptionAcquiredState();
+
+
+
+
+ AMQQueue getQueue();
+
+ AMQMessage getMessage();
+
+ long getSize();
+
+ boolean getDeliveredToConsumer();
+
+ boolean expired() throws AMQException;
+
+ boolean isAcquired();
+
+ boolean acquire();
+ boolean acquire(Subscription sub);
+
+ boolean delete();
+ boolean isDeleted();
+
+ boolean acquiredBySubscription();
+
+ void setDeliveredToSubscription();
+
+ void release();
+
+ String debugIdentity();
+
+ boolean immediateAndNotDelivered();
+
+ void setRedelivered(boolean b);
+
+ Subscription getDeliveredSubscription();
+
+ void reject();
+
+ void reject(Subscription subscription);
+
+ boolean isRejectedBy(Subscription subscription);
+
+ void requeue(StoreContext storeContext) throws AMQException;
+
+ void dequeue(final StoreContext storeContext) throws FailedDequeueException;
+
+ void dispose(final StoreContext storeContext) throws MessageCleanupException;
+
+ void discard(StoreContext storeContext) throws FailedDequeueException, MessageCleanupException;
+
+ boolean isQueueDeleted();
+
+ void addStateChangeListener(StateChangeListener listener);
+ boolean removeStateChangeListener(StateChangeListener listener);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java
new file mode 100644
index 0000000000..dbad5438dc
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java
@@ -0,0 +1,388 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.log4j.Logger;
+
+import java.util.Set;
+import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+
+public class QueueEntryImpl implements QueueEntry
+{
+
+ /**
+ * Used for debugging purposes.
+ */
+ private static final Logger _log = Logger.getLogger(QueueEntryImpl.class);
+
+ private final SimpleQueueEntryList _queueEntryList;
+
+ private AMQMessage _message;
+
+
+ private Set<Subscription> _rejectedBy = null;
+
+ private volatile EntryState _state = AVAILABLE_STATE;
+
+ private static final
+ AtomicReferenceFieldUpdater<QueueEntryImpl, EntryState>
+ _stateUpdater =
+ AtomicReferenceFieldUpdater.newUpdater
+ (QueueEntryImpl.class, EntryState.class, "_state");
+
+
+ private volatile Set<StateChangeListener> _stateChangeListeners;
+
+ private static final
+ AtomicReferenceFieldUpdater<QueueEntryImpl, Set>
+ _listenersUpdater =
+ AtomicReferenceFieldUpdater.newUpdater
+ (QueueEntryImpl.class, Set.class, "_stateChangeListeners");
+
+
+ private static final
+ AtomicLongFieldUpdater<QueueEntryImpl>
+ _entryIdUpdater =
+ AtomicLongFieldUpdater.newUpdater
+ (QueueEntryImpl.class, "_entryId");
+
+
+ private volatile long _entryId;
+
+ volatile QueueEntryImpl _next;
+
+
+ QueueEntryImpl(SimpleQueueEntryList queueEntryList)
+ {
+ this(queueEntryList,null,Long.MIN_VALUE);
+ _state = DELETED_STATE;
+ }
+
+
+ public QueueEntryImpl(SimpleQueueEntryList queueEntryList, AMQMessage message, final long entryId)
+ {
+ _queueEntryList = queueEntryList;
+ _message = message;
+
+ _entryIdUpdater.set(this, entryId);
+ }
+
+ public QueueEntryImpl(SimpleQueueEntryList queueEntryList, AMQMessage message)
+ {
+ _queueEntryList = queueEntryList;
+ _message = message;
+ }
+
+ protected void setEntryId(long entryId)
+ {
+ _entryIdUpdater.set(this, entryId);
+ }
+
+ protected long getEntryId()
+ {
+ return _entryId;
+ }
+
+ public AMQQueue getQueue()
+ {
+ return _queueEntryList.getQueue();
+ }
+
+ public AMQMessage getMessage()
+ {
+ return _message;
+ }
+
+ public long getSize()
+ {
+ return getMessage().getSize();
+ }
+
+ public boolean getDeliveredToConsumer()
+ {
+ return getMessage().getDeliveredToConsumer();
+ }
+
+ public boolean expired() throws AMQException
+ {
+ return getMessage().expired(getQueue());
+ }
+
+ public boolean isAcquired()
+ {
+ return _state.getState() == State.ACQUIRED;
+ }
+
+ public boolean acquire()
+ {
+ return acquire(NON_SUBSCRIPTION_ACQUIRED_STATE);
+ }
+
+ private boolean acquire(final EntryState state)
+ {
+ boolean acquired = _stateUpdater.compareAndSet(this,AVAILABLE_STATE, state);
+ if(acquired && _stateChangeListeners != null)
+ {
+ notifyStateChange(State.AVAILABLE, State.ACQUIRED);
+ }
+
+ return acquired;
+ }
+
+ public boolean acquire(Subscription sub)
+ {
+ return acquire(sub.getOwningState());
+ }
+
+ public boolean acquiredBySubscription()
+ {
+
+ return (_state instanceof SubscriptionAcquiredState);
+ }
+
+ public void setDeliveredToSubscription()
+ {
+ getMessage().setDeliveredToConsumer();
+ }
+
+ public void release()
+ {
+ _stateUpdater.set(this,AVAILABLE_STATE);
+ }
+
+ public String debugIdentity()
+ {
+ return getMessage().debugIdentity();
+ }
+
+
+ public boolean immediateAndNotDelivered()
+ {
+ return _message.immediateAndNotDelivered();
+ }
+
+ public void setRedelivered(boolean b)
+ {
+ getMessage().setRedelivered(b);
+ }
+
+ public Subscription getDeliveredSubscription()
+ {
+ EntryState state = _state;
+ if (state instanceof SubscriptionAcquiredState)
+ {
+ return ((SubscriptionAcquiredState) state).getSubscription();
+ }
+ else
+ {
+ return null;
+ }
+
+ }
+
+ public void reject()
+ {
+ reject(getDeliveredSubscription());
+ }
+
+ public void reject(Subscription subscription)
+ {
+ if (subscription != null)
+ {
+ if (_rejectedBy == null)
+ {
+ _rejectedBy = new HashSet<Subscription>();
+ }
+
+ _rejectedBy.add(subscription);
+ }
+ else
+ {
+ _log.warn("Requesting rejection by null subscriber:" + debugIdentity());
+ }
+ }
+
+ public boolean isRejectedBy(Subscription subscription)
+ {
+
+ if (_rejectedBy != null) // We have subscriptions that rejected this message
+ {
+ return _rejectedBy.contains(subscription);
+ }
+ else // This messasge hasn't been rejected yet.
+ {
+ return false;
+ }
+ }
+
+
+ public void requeue(final StoreContext storeContext) throws AMQException
+ {
+ getQueue().requeue(storeContext, this);
+ if(_stateChangeListeners != null)
+ {
+ notifyStateChange(QueueEntry.State.ACQUIRED, QueueEntry.State.AVAILABLE);
+ }
+ }
+
+ public void dequeue(final StoreContext storeContext) throws FailedDequeueException
+ {
+ EntryState state = _state;
+
+ if((state.getState() == State.ACQUIRED) &&_stateUpdater.compareAndSet(this, state, DEQUEUED_STATE))
+ {
+ if (state instanceof SubscriptionAcquiredState)
+ {
+ Subscription s = ((SubscriptionAcquiredState) state).getSubscription();
+ s.restoreCredit(this);
+ }
+
+ getQueue().dequeue(storeContext, this);
+ if(_stateChangeListeners != null)
+ {
+ notifyStateChange(state.getState() , QueueEntry.State.DEQUEUED);
+ }
+
+ }
+
+ }
+
+ private void notifyStateChange(final State oldState, final State newState)
+ {
+ for(StateChangeListener l : _stateChangeListeners)
+ {
+ l.stateChanged(this, oldState, newState);
+ }
+ }
+
+ public void dispose(final StoreContext storeContext) throws MessageCleanupException
+ {
+ if(delete())
+ {
+ getMessage().decrementReference(storeContext);
+ }
+ }
+
+ public void discard(StoreContext storeContext) throws FailedDequeueException, MessageCleanupException
+ {
+ //if the queue is null then the message is waiting to be acked, but has been removed.
+ if (getQueue() != null)
+ {
+ dequeue(storeContext);
+ }
+
+ dispose(storeContext);
+ }
+
+ public boolean isQueueDeleted()
+ {
+ return getQueue().isDeleted();
+ }
+
+ public void addStateChangeListener(StateChangeListener listener)
+ {
+ Set<StateChangeListener> listeners = _stateChangeListeners;
+ if(listeners == null)
+ {
+ _listenersUpdater.compareAndSet(this, null, new CopyOnWriteArraySet<StateChangeListener>());
+ listeners = _stateChangeListeners;
+ }
+
+ listeners.add(listener);
+ }
+
+ public boolean removeStateChangeListener(StateChangeListener listener)
+ {
+ Set<StateChangeListener> listeners = _stateChangeListeners;
+ if(listeners != null)
+ {
+ return listeners.remove(listener);
+ }
+
+ return false;
+ }
+
+
+ public int compareTo(final QueueEntry o)
+ {
+ QueueEntryImpl other = (QueueEntryImpl)o;
+ return getEntryId() > other.getEntryId() ? 1 : getEntryId() < other.getEntryId() ? -1 : 0;
+ }
+
+ public QueueEntryImpl getNext()
+ {
+
+ QueueEntryImpl next = nextNode();
+ while(next != null && next.isDeleted())
+ {
+
+ final QueueEntryImpl newNext = next.nextNode();
+ if(newNext != null)
+ {
+ SimpleQueueEntryList._nextUpdater.compareAndSet(this,next, newNext);
+ next = nextNode();
+ }
+ else
+ {
+ next = null;
+ }
+
+ }
+ return next;
+ }
+
+ QueueEntryImpl nextNode()
+ {
+ return _next;
+ }
+
+ public boolean isDeleted()
+ {
+ return _state == DELETED_STATE;
+ }
+
+ public boolean delete()
+ {
+ EntryState state = _state;
+
+ if(state != DELETED_STATE && _stateUpdater.compareAndSet(this,state,DELETED_STATE))
+ {
+ _queueEntryList.advanceHead();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public QueueEntryList getQueueEntryList()
+ {
+ return _queueEntryList;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java
new file mode 100644
index 0000000000..c5c115a2d1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java
@@ -0,0 +1,30 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.queue;
+
+public interface QueueEntryIterator
+{
+ boolean atTail();
+
+ QueueEntry getNode();
+
+ boolean advance();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java
new file mode 100644
index 0000000000..313e076f61
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java
@@ -0,0 +1,34 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.queue;
+
+public interface QueueEntryList
+{
+ AMQQueue getQueue();
+
+ QueueEntry add(AMQMessage message);
+
+ QueueEntry next(QueueEntry node);
+
+ QueueEntryIterator iterator();
+
+ QueueEntry getHead();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java
new file mode 100644
index 0000000000..4dbce45f67
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java
@@ -0,0 +1,26 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.queue;
+
+interface QueueEntryListFactory
+{
+ public QueueEntryList createQueueEntryList(AMQQueue queue);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java
new file mode 100644
index 0000000000..959ca03c80
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java
@@ -0,0 +1,27 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+
+public interface QueueNotificationListener
+{
+ void notifyClients(NotificationCheck notification, AMQQueue queue, String notificationMsg);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java
new file mode 100644
index 0000000000..1210f0e97c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import java.util.Collection;
+
+public interface QueueRegistry
+{
+ VirtualHost getVirtualHost();
+
+ void registerQueue(AMQQueue queue) throws AMQException;
+
+ void unregisterQueue(AMQShortString name) throws AMQException;
+
+ AMQQueue getQueue(AMQShortString name);
+
+ Collection<AMQShortString> getQueueNames();
+
+ Collection<AMQQueue> getQueues();
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java
new file mode 100644
index 0000000000..09258682bd
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java
@@ -0,0 +1,1600 @@
+package org.apache.qpid.server.queue;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.management.JMException;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.pool.ReadWriteRunnable;
+import org.apache.qpid.pool.ReferenceCountingExecutorService;
+import org.apache.qpid.server.configuration.QueueConfiguration;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.subscription.SubscriptionList;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class SimpleAMQQueue implements AMQQueue, Subscription.StateListener
+{
+ private static final Logger _logger = Logger.getLogger(SimpleAMQQueue.class);
+
+ private final AMQShortString _name;
+
+ /** null means shared */
+ private final AMQShortString _owner;
+
+ private final boolean _durable;
+
+ /** If true, this queue is deleted when the last subscriber is removed */
+ private final boolean _autoDelete;
+
+ private final VirtualHost _virtualHost;
+
+ /** Used to track bindings to exchanges so that on deletion they can easily be cancelled. */
+ private final ExchangeBindings _bindings = new ExchangeBindings(this);
+
+ private final AtomicBoolean _deleted = new AtomicBoolean(false);
+
+ private final List<Task> _deleteTaskList = new CopyOnWriteArrayList<Task>();
+
+ private final AtomicInteger _atomicQueueCount = new AtomicInteger(0);
+
+ private final AtomicLong _atomicQueueSize = new AtomicLong(0L);
+
+ private final AtomicInteger _activeSubscriberCount = new AtomicInteger();
+
+ protected final SubscriptionList _subscriptionList = new SubscriptionList(this);
+ private final AtomicReference<SubscriptionList.SubscriptionNode> _lastSubscriptionNode = new AtomicReference<SubscriptionList.SubscriptionNode>(_subscriptionList.getHead());
+
+ private volatile Subscription _exclusiveSubscriber;
+
+ protected final QueueEntryList _entries;
+
+ private final AMQQueueMBean _managedObject;
+ private final Executor _asyncDelivery;
+ private final AtomicLong _totalMessagesReceived = new AtomicLong();
+
+ /** max allowed size(KB) of a single message */
+ public long _maximumMessageSize = ApplicationRegistry.getInstance().getConfiguration().getMaximumMessageSize();
+
+ /** max allowed number of messages on a queue. */
+ public long _maximumMessageCount = ApplicationRegistry.getInstance().getConfiguration().getMaximumMessageCount();
+
+ /** max queue depth for the queue */
+ public long _maximumQueueDepth = ApplicationRegistry.getInstance().getConfiguration().getMaximumQueueDepth();
+
+ /** maximum message age before alerts occur */
+ public long _maximumMessageAge = ApplicationRegistry.getInstance().getConfiguration().getMaximumMessageAge();
+
+ /** the minimum interval between sending out consecutive alerts of the same type */
+ public long _minimumAlertRepeatGap = ApplicationRegistry.getInstance().getConfiguration().getMinimumAlertRepeatGap();
+
+ private static final int MAX_ASYNC_DELIVERIES = 10;
+
+ private final Set<NotificationCheck> _notificationChecks = EnumSet.noneOf(NotificationCheck.class);
+
+ private final AtomicLong _stateChangeCount = new AtomicLong(Long.MIN_VALUE);
+ private AtomicReference _asynchronousRunner = new AtomicReference(null);
+ private AtomicInteger _deliveredMessages = new AtomicInteger();
+ private AtomicBoolean _stopped = new AtomicBoolean(false);
+
+ protected SimpleAMQQueue(AMQShortString name, boolean durable, AMQShortString owner, boolean autoDelete, VirtualHost virtualHost)
+ throws AMQException
+ {
+ this(name, durable, owner, autoDelete, virtualHost, new SimpleQueueEntryList.Factory());
+ }
+
+ protected SimpleAMQQueue(AMQShortString name,
+ boolean durable,
+ AMQShortString owner,
+ boolean autoDelete,
+ VirtualHost virtualHost,
+ QueueEntryListFactory entryListFactory)
+ throws AMQException
+ {
+
+ if (name == null)
+ {
+ throw new IllegalArgumentException("Queue name must not be null");
+ }
+
+ if (virtualHost == null)
+ {
+ throw new IllegalArgumentException("Virtual Host must not be null");
+ }
+
+ _name = name;
+ _durable = durable;
+ _owner = owner;
+ _autoDelete = autoDelete;
+ _virtualHost = virtualHost;
+ _entries = entryListFactory.createQueueEntryList(this);
+
+ _asyncDelivery = ReferenceCountingExecutorService.getInstance().acquireExecutorService();
+
+ try
+ {
+ _managedObject = new AMQQueueMBean(this);
+ _managedObject.register();
+ }
+ catch (JMException e)
+ {
+ throw new AMQException("AMQQueue MBean creation has failed ", e);
+ }
+
+ resetNotifications();
+
+ }
+
+ public void resetNotifications()
+ {
+ // This ensure that the notification checks for the configured alerts are created.
+ setMaximumMessageAge(_maximumMessageAge);
+ setMaximumMessageCount(_maximumMessageCount);
+ setMaximumMessageSize(_maximumMessageSize);
+ setMaximumQueueDepth(_maximumQueueDepth);
+ }
+
+ // ------ Getters and Setters
+
+ public AMQShortString getName()
+ {
+ return _name;
+ }
+
+ public boolean isDurable()
+ {
+ return _durable;
+ }
+
+ public boolean isAutoDelete()
+ {
+ return _autoDelete;
+ }
+
+ public AMQShortString getOwner()
+ {
+ return _owner;
+ }
+
+ public VirtualHost getVirtualHost()
+ {
+ return _virtualHost;
+ }
+
+ // ------ bind and unbind
+
+ public void bind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException
+ {
+ exchange.registerQueue(routingKey, this, arguments);
+ if (isDurable() && exchange.isDurable())
+ {
+ _virtualHost.getMessageStore().bindQueue(exchange, routingKey, this, arguments);
+ }
+
+ _bindings.addBinding(routingKey, arguments, exchange);
+ }
+
+ public void unBind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException
+ {
+ exchange.deregisterQueue(routingKey, this, arguments);
+ if (isDurable() && exchange.isDurable())
+ {
+ _virtualHost.getMessageStore().unbindQueue(exchange, routingKey, this, arguments);
+ }
+
+ boolean removed = _bindings.remove(routingKey, arguments, exchange);
+ if (!removed)
+ {
+ _logger.error("Mismatch between queue bindings and exchange record of bindings");
+ }
+ }
+
+ public List<ExchangeBinding> getExchangeBindings()
+ {
+ return new ArrayList<ExchangeBinding>(_bindings.getExchangeBindings());
+ }
+
+ // ------ Manage Subscriptions
+
+ public synchronized void registerSubscription(final Subscription subscription, final boolean exclusive) throws AMQException
+ {
+
+ if (isExclusiveSubscriber())
+ {
+ throw new ExistingExclusiveSubscription();
+ }
+
+ if (exclusive)
+ {
+ if (getConsumerCount() != 0)
+ {
+ throw new ExistingSubscriptionPreventsExclusive();
+ }
+ else
+ {
+ _exclusiveSubscriber = subscription;
+
+ }
+ }
+
+ _activeSubscriberCount.incrementAndGet();
+ subscription.setStateListener(this);
+ subscription.setLastSeenEntry(null, _entries.getHead());
+
+ if (!isDeleted())
+ {
+ subscription.setQueue(this);
+ _subscriptionList.add(subscription);
+ if (isDeleted())
+ {
+ subscription.queueDeleted(this);
+ }
+ }
+ else
+ {
+ // TODO
+ }
+
+ deliverAsync(subscription);
+
+ }
+
+ public synchronized void unregisterSubscription(final Subscription subscription) throws AMQException
+ {
+ if (subscription == null)
+ {
+ throw new NullPointerException("subscription argument is null");
+ }
+
+ boolean removed = _subscriptionList.remove(subscription);
+
+ if (removed)
+ {
+ subscription.close();
+ // No longer can the queue have an exclusive consumer
+ setExclusiveSubscriber(null);
+
+ QueueEntry lastSeen;
+
+ while ((lastSeen = subscription.getLastSeenEntry()) != null)
+ {
+ subscription.setLastSeenEntry(lastSeen, null);
+ }
+
+ // auto-delete queues must be deleted if there are no remaining subscribers
+
+ if (_autoDelete && getConsumerCount() == 0)
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Auto-deleteing queue:" + this);
+ }
+
+ delete();
+
+ // 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
+ subscription.queueDeleted(this);
+ }
+ }
+
+ }
+
+ // ------ Enqueue / Dequeue
+
+ public QueueEntry enqueue(StoreContext storeContext, AMQMessage message) throws AMQException
+ {
+
+ incrementQueueCount();
+ incrementQueueSize(message);
+
+ _totalMessagesReceived.incrementAndGet();
+
+ QueueEntry entry;
+ Subscription exclusiveSub = _exclusiveSubscriber;
+
+ if (exclusiveSub != null)
+ {
+ exclusiveSub.getSendLock();
+
+ try
+ {
+ entry = _entries.add(message);
+
+ deliverToSubscription(exclusiveSub, entry);
+
+ // where there is more than one producer there's a reasonable chance that even though there is
+ // no "queueing" we do not deliver because we get an interleving of _entries.add and
+ // deliverToSubscription between threads. Therefore have one more try.
+ if (!(entry.isAcquired() || entry.isDeleted()))
+ {
+ deliverToSubscription(exclusiveSub, entry);
+ }
+ }
+ finally
+ {
+ exclusiveSub.releaseSendLock();
+ }
+ }
+ else
+ {
+ entry = _entries.add(message);
+ /*
+
+ iterate over subscriptions and if any is at the end of the queue and can deliver this message, then deliver the message
+
+ */
+ SubscriptionList.SubscriptionNode node = _lastSubscriptionNode.get();
+ SubscriptionList.SubscriptionNode nextNode = node.getNext();
+ if (nextNode == null)
+ {
+ nextNode = _subscriptionList.getHead().getNext();
+ }
+ while (nextNode != null)
+ {
+ if (_lastSubscriptionNode.compareAndSet(node, nextNode))
+ {
+ break;
+ }
+ else
+ {
+ node = _lastSubscriptionNode.get();
+ nextNode = node.getNext();
+ if (nextNode == null)
+ {
+ nextNode = _subscriptionList.getHead().getNext();
+ }
+ }
+ }
+
+ // always do one extra loop after we believe we've finished
+ // this catches the case where we *just* miss an update
+ int loops = 2;
+
+ while (!(entry.isAcquired() || entry.isDeleted()) && loops != 0)
+ {
+ if (nextNode == null)
+ {
+ loops--;
+ nextNode = _subscriptionList.getHead();
+ }
+ else
+ {
+ // if subscription at end, and active, offer
+ Subscription sub = nextNode.getSubscription();
+ deliverToSubscription(sub, entry);
+ }
+ nextNode = nextNode.getNext();
+
+ }
+ }
+
+ if (entry.immediateAndNotDelivered())
+ {
+ dequeue(storeContext, entry);
+ entry.dispose(storeContext);
+ }
+ else if (!(entry.isAcquired() || entry.isDeleted()))
+ {
+ checkSubscriptionsNotAheadOfDelivery(entry);
+
+ deliverAsync();
+ }
+
+ _managedObject.checkForNotification(entry.getMessage());
+
+ return entry;
+ }
+
+ private void deliverToSubscription(final Subscription sub, final QueueEntry entry)
+ throws AMQException
+ {
+
+ sub.getSendLock();
+ try
+ {
+ if (subscriptionReadyAndHasInterest(sub, entry)
+ && !sub.isSuspended())
+ {
+ if (!sub.wouldSuspend(entry))
+ {
+ if (!sub.isBrowser() && !entry.acquire(sub))
+ {
+ // restore credit here that would have been taken away by wouldSuspend since we didn't manage
+ // to acquire the entry for this subscription
+ sub.restoreCredit(entry);
+ }
+ else
+ {
+
+ deliverMessage(sub, entry);
+
+ }
+ }
+ }
+ }
+ finally
+ {
+ sub.releaseSendLock();
+ }
+ }
+
+ protected void checkSubscriptionsNotAheadOfDelivery(final QueueEntry entry)
+ {
+ // This method is only required for queues which mess with ordering
+ // Simple Queues don't :-)
+ }
+
+ private void incrementQueueSize(final AMQMessage message)
+ {
+ getAtomicQueueSize().addAndGet(message.getSize());
+ }
+
+ private void incrementQueueCount()
+ {
+ getAtomicQueueCount().incrementAndGet();
+ }
+
+ private void deliverMessage(final Subscription sub, final QueueEntry entry)
+ throws AMQException
+ {
+ _deliveredMessages.incrementAndGet();
+ sub.send(entry);
+
+ }
+
+ private boolean subscriptionReadyAndHasInterest(final Subscription sub, final QueueEntry entry)
+ {
+
+ // We need to move this subscription on, past entries which are already acquired, or deleted or ones it has no
+ // interest in.
+ QueueEntry node = sub.getLastSeenEntry();
+ while (node != null && (node.isAcquired() || node.isDeleted() || !sub.hasInterest(node)))
+ {
+
+ QueueEntry newNode = _entries.next(node);
+ if (newNode != null)
+ {
+ sub.setLastSeenEntry(node, newNode);
+ node = sub.getLastSeenEntry();
+ }
+ else
+ {
+ node = null;
+ break;
+ }
+
+ }
+
+ if (node == entry)
+ {
+ // If the first entry that subscription can process is the one we are trying to deliver to it, then we are
+ // good
+ return true;
+ }
+ else
+ {
+ // Otherwise we should try to update the subscription's last seen entry to the entry we got to, providing
+ // no-one else has updated it to something furhter on in the list
+ //TODO - check
+ //updateLastSeenEntry(sub, entry);
+ return false;
+ }
+
+ }
+
+ private void updateLastSeenEntry(final Subscription sub, final QueueEntry entry)
+ {
+ QueueEntry node = sub.getLastSeenEntry();
+
+ if (node != null && entry.compareTo(node) < 0 && sub.hasInterest(entry))
+ {
+ do
+ {
+ if (sub.setLastSeenEntry(node, entry))
+ {
+ return;
+ }
+ else
+ {
+ node = sub.getLastSeenEntry();
+ }
+ }
+ while (node != null && entry.compareTo(node) < 0);
+ }
+
+ }
+
+ public void requeue(StoreContext storeContext, QueueEntry entry) throws AMQException
+ {
+
+ SubscriptionList.SubscriptionNodeIterator subscriberIter = _subscriptionList.iterator();
+ // iterate over all the subscribers, and if they are in advance of this queue entry then move them backwards
+ while (subscriberIter.advance())
+ {
+ Subscription sub = subscriberIter.getNode().getSubscription();
+
+ // we don't make browsers send the same stuff twice
+ if (!sub.isBrowser())
+ {
+ updateLastSeenEntry(sub, entry);
+ }
+ }
+
+ deliverAsync();
+
+ }
+
+ public void dequeue(StoreContext storeContext, QueueEntry entry) throws FailedDequeueException
+ {
+ decrementQueueCount();
+ decrementQueueSize(entry);
+ if (entry.acquiredBySubscription())
+ {
+ _deliveredMessages.decrementAndGet();
+ }
+
+ try
+ {
+ AMQMessage msg = entry.getMessage();
+ if (msg.isPersistent())
+ {
+ _virtualHost.getMessageStore().dequeueMessage(storeContext, this, msg.getMessageId());
+ }
+ //entry.dispose(storeContext);
+
+ }
+ catch (MessageCleanupException e)
+ {
+ // Message was dequeued, but could not then 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.toString(), e);
+ }
+
+ }
+
+ private void decrementQueueSize(final QueueEntry entry)
+ {
+ getAtomicQueueSize().addAndGet(-entry.getMessage().getSize());
+ }
+
+ void decrementQueueCount()
+ {
+ getAtomicQueueCount().decrementAndGet();
+ }
+
+ public boolean resend(final QueueEntry entry, final Subscription subscription) throws AMQException
+ {
+ /* TODO : This is wrong as the subscription may be suspended, we should instead change the state of the message
+ entry to resend and move back the subscription pointer. */
+
+ subscription.getSendLock();
+ try
+ {
+ if (!subscription.isClosed())
+ {
+ deliverMessage(subscription, entry);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ finally
+ {
+ subscription.releaseSendLock();
+ }
+ }
+
+ public int getConsumerCount()
+ {
+ return _subscriptionList.size();
+ }
+
+ public int getActiveConsumerCount()
+ {
+ return _activeSubscriberCount.get();
+ }
+
+ public boolean isUnused()
+ {
+ return getConsumerCount() == 0;
+ }
+
+ public boolean isEmpty()
+ {
+ return getMessageCount() == 0;
+ }
+
+ public int getMessageCount()
+ {
+ return getAtomicQueueCount().get();
+ }
+
+ public long getQueueDepth()
+ {
+ return getAtomicQueueSize().get();
+ }
+
+ public int getUndeliveredMessageCount()
+ {
+ int count = getMessageCount() - _deliveredMessages.get();
+ if (count < 0)
+ {
+ return 0;
+ }
+ else
+ {
+ return count;
+ }
+ }
+
+ public long getReceivedMessageCount()
+ {
+ return _totalMessagesReceived.get();
+ }
+
+ public long getOldestMessageArrivalTime()
+ {
+ QueueEntry entry = getOldestQueueEntry();
+ return entry == null ? Long.MAX_VALUE : entry.getMessage().getArrivalTime();
+ }
+
+ protected QueueEntry getOldestQueueEntry()
+ {
+ return _entries.next(_entries.getHead());
+ }
+
+ public boolean isDeleted()
+ {
+ return _deleted.get();
+ }
+
+ public List<QueueEntry> getMessagesOnTheQueue()
+ {
+ ArrayList<QueueEntry> entryList = new ArrayList<QueueEntry>();
+ QueueEntryIterator queueListIterator = _entries.iterator();
+ while (queueListIterator.advance())
+ {
+ QueueEntry node = queueListIterator.getNode();
+ if (node != null && !node.isDeleted())
+ {
+ entryList.add(node);
+ }
+ }
+ return entryList;
+
+ }
+
+ public void stateChange(Subscription sub, Subscription.State oldState, Subscription.State newState)
+ {
+ if (oldState == Subscription.State.ACTIVE && newState != Subscription.State.ACTIVE)
+ {
+ _activeSubscriberCount.decrementAndGet();
+
+ }
+ else if (newState == Subscription.State.ACTIVE)
+ {
+ if (oldState != Subscription.State.ACTIVE)
+ {
+ _activeSubscriberCount.incrementAndGet();
+
+ }
+ deliverAsync(sub);
+ }
+ }
+
+ public int compareTo(final AMQQueue o)
+ {
+ return _name.compareTo(o.getName());
+ }
+
+ public AtomicInteger getAtomicQueueCount()
+ {
+ return _atomicQueueCount;
+ }
+
+ public AtomicLong getAtomicQueueSize()
+ {
+ return _atomicQueueSize;
+ }
+
+ private boolean isExclusiveSubscriber()
+ {
+ return _exclusiveSubscriber != null;
+ }
+
+ private void setExclusiveSubscriber(Subscription exclusiveSubscriber)
+ {
+ _exclusiveSubscriber = exclusiveSubscriber;
+ }
+
+ public static interface QueueEntryFilter
+ {
+ public boolean accept(QueueEntry entry);
+
+ public boolean filterComplete();
+ }
+
+ public List<QueueEntry> getMessagesOnTheQueue(final long fromMessageId, final long toMessageId)
+ {
+ return getMessagesOnTheQueue(new QueueEntryFilter()
+ {
+
+ public boolean accept(QueueEntry entry)
+ {
+ final long messageId = entry.getMessage().getMessageId();
+ return messageId >= fromMessageId && messageId <= toMessageId;
+ }
+
+ public boolean filterComplete()
+ {
+ return false;
+ }
+ });
+ }
+
+ public QueueEntry getMessageOnTheQueue(final long messageId)
+ {
+ List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter()
+ {
+ private boolean _complete;
+
+ public boolean accept(QueueEntry entry)
+ {
+ _complete = entry.getMessage().getMessageId() == messageId;
+ return _complete;
+ }
+
+ public boolean filterComplete()
+ {
+ return _complete;
+ }
+ });
+ return entries.isEmpty() ? null : entries.get(0);
+ }
+
+ public List<QueueEntry> getMessagesOnTheQueue(QueueEntryFilter filter)
+ {
+ ArrayList<QueueEntry> entryList = new ArrayList<QueueEntry>();
+ QueueEntryIterator queueListIterator = _entries.iterator();
+ while (queueListIterator.advance() && !filter.filterComplete())
+ {
+ QueueEntry node = queueListIterator.getNode();
+ if (!node.isDeleted() && filter.accept(node))
+ {
+ entryList.add(node);
+ }
+ }
+ return entryList;
+
+ }
+
+ public void moveMessagesToAnotherQueue(final long fromMessageId,
+ final long toMessageId,
+ String queueName,
+ StoreContext storeContext)
+ {
+
+ AMQQueue toQueue = getVirtualHost().getQueueRegistry().getQueue(new AMQShortString(queueName));
+ MessageStore store = getVirtualHost().getMessageStore();
+
+ List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter()
+ {
+
+ public boolean accept(QueueEntry entry)
+ {
+ final long messageId = entry.getMessage().getMessageId();
+ return (messageId >= fromMessageId)
+ && (messageId <= toMessageId)
+ && entry.acquire();
+ }
+
+ public boolean filterComplete()
+ {
+ return false;
+ }
+ });
+
+ try
+ {
+ store.beginTran(storeContext);
+
+ // Move the messages in on the message store.
+ for (QueueEntry entry : entries)
+ {
+ AMQMessage message = entry.getMessage();
+
+ if (message.isPersistent() && toQueue.isDurable())
+ {
+ store.enqueueMessage(storeContext, toQueue, message.getMessageId());
+ }
+ // dequeue does not decrement the refence count
+ entry.dequeue(storeContext);
+ }
+
+ // Commit and flush the move transcations.
+ try
+ {
+ store.commitTran(storeContext);
+ }
+ catch (AMQException e)
+ {
+ throw new RuntimeException("Failed to commit transaction whilst moving messages on message store.", e);
+ }
+ }
+ catch (AMQException e)
+ {
+ try
+ {
+ store.abortTran(storeContext);
+ }
+ catch (AMQException rollbackEx)
+ {
+ _logger.error("Failed to rollback transaction when error occured moving messages", rollbackEx);
+ }
+ throw new RuntimeException(e);
+ }
+
+ try
+ {
+ for (QueueEntry entry : entries)
+ {
+ toQueue.enqueue(storeContext, entry.getMessage());
+
+ }
+ }
+ catch (MessageCleanupException e)
+ {
+ throw new RuntimeException(e);
+ }
+ catch (AMQException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public void copyMessagesToAnotherQueue(final long fromMessageId,
+ final long toMessageId,
+ String queueName,
+ final StoreContext storeContext)
+ {
+ AMQQueue toQueue = getVirtualHost().getQueueRegistry().getQueue(new AMQShortString(queueName));
+ MessageStore store = getVirtualHost().getMessageStore();
+
+ List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter()
+ {
+
+ public boolean accept(QueueEntry entry)
+ {
+ final long messageId = entry.getMessage().getMessageId();
+ if ((messageId >= fromMessageId)
+ && (messageId <= toMessageId))
+ {
+ if (!entry.isDeleted())
+ {
+ return entry.getMessage().incrementReference();
+ }
+ }
+
+ return false;
+ }
+
+ public boolean filterComplete()
+ {
+ return false;
+ }
+ });
+
+ try
+ {
+ store.beginTran(storeContext);
+
+ // Move the messages in on the message store.
+ for (QueueEntry entry : entries)
+ {
+ AMQMessage message = entry.getMessage();
+
+ if (message.isReferenced() && message.isPersistent() && toQueue.isDurable())
+ {
+ store.enqueueMessage(storeContext, toQueue, message.getMessageId());
+ }
+ }
+
+ // Commit and flush the move transcations.
+ try
+ {
+ store.commitTran(storeContext);
+ }
+ catch (AMQException e)
+ {
+ throw new RuntimeException("Failed to commit transaction whilst moving messages on message store.", e);
+ }
+ }
+ catch (AMQException e)
+ {
+ try
+ {
+ store.abortTran(storeContext);
+ }
+ catch (AMQException rollbackEx)
+ {
+ _logger.error("Failed to rollback transaction when error occured moving messages", rollbackEx);
+ }
+ throw new RuntimeException(e);
+ }
+
+ try
+ {
+ for (QueueEntry entry : entries)
+ {
+ if (entry.getMessage().isReferenced())
+ {
+ toQueue.enqueue(storeContext, entry.getMessage());
+ }
+ }
+ }
+ catch (MessageCleanupException e)
+ {
+ throw new RuntimeException(e);
+ }
+ catch (AMQException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public void removeMessagesFromQueue(long fromMessageId, long toMessageId, StoreContext storeContext)
+ {
+
+ try
+ {
+ QueueEntryIterator queueListIterator = _entries.iterator();
+
+ while (queueListIterator.advance())
+ {
+ QueueEntry node = queueListIterator.getNode();
+
+ final long messageId = node.getMessage().getMessageId();
+
+ if ((messageId >= fromMessageId)
+ && (messageId <= toMessageId)
+ && !node.isDeleted()
+ && node.acquire())
+ {
+ node.discard(storeContext);
+ }
+
+ }
+ }
+ catch (AMQException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ // ------ Management functions
+
+ public void deleteMessageFromTop(StoreContext storeContext) throws AMQException
+ {
+ QueueEntryIterator queueListIterator = _entries.iterator();
+ boolean noDeletes = true;
+
+ while (noDeletes && queueListIterator.advance())
+ {
+ QueueEntry node = queueListIterator.getNode();
+ if (!node.isDeleted() && node.acquire())
+ {
+ node.discard(storeContext);
+ noDeletes = false;
+ }
+
+ }
+ }
+
+ public long clearQueue(StoreContext storeContext) throws AMQException
+ {
+
+ QueueEntryIterator queueListIterator = _entries.iterator();
+ long count = 0;
+
+ while (queueListIterator.advance())
+ {
+ QueueEntry node = queueListIterator.getNode();
+ if (!node.isDeleted() && node.acquire())
+ {
+ node.discard(storeContext);
+ count++;
+ }
+
+ }
+ return count;
+
+ }
+
+ public void addQueueDeleteTask(final Task task)
+ {
+ _deleteTaskList.add(task);
+ }
+
+ public int delete() throws AMQException
+ {
+ if (!_deleted.getAndSet(true))
+ {
+
+ SubscriptionList.SubscriptionNodeIterator subscriptionIter = _subscriptionList.iterator();
+
+ while (subscriptionIter.advance())
+ {
+ Subscription s = subscriptionIter.getNode().getSubscription();
+ if (s != null)
+ {
+ s.queueDeleted(this);
+ }
+ }
+
+ _bindings.deregister();
+ _virtualHost.getQueueRegistry().unregisterQueue(_name);
+
+ _managedObject.unregister();
+ for (Task task : _deleteTaskList)
+ {
+ task.doTask(this);
+ }
+
+ _deleteTaskList.clear();
+ stop();
+ }
+ return getMessageCount();
+
+ }
+
+ public void stop()
+ {
+ if (!_stopped.getAndSet(true))
+ {
+ ReferenceCountingExecutorService.getInstance().releaseExecutorService();
+ }
+ }
+
+ public void deliverAsync()
+ {
+ _stateChangeCount.incrementAndGet();
+
+ Runner runner = new Runner();
+
+ if (_asynchronousRunner.compareAndSet(null, runner))
+ {
+ _asyncDelivery.execute(runner);
+ }
+ }
+
+ public void deliverAsync(Subscription sub)
+ {
+ _asyncDelivery.execute(new SubFlushRunner(sub));
+ }
+
+ private class Runner implements ReadWriteRunnable
+ {
+ public void run()
+ {
+ try
+ {
+ processQueue(this);
+ }
+ catch (AMQException e)
+ {
+ _logger.error(e);
+ }
+
+ }
+
+ public boolean isRead()
+ {
+ return false;
+ }
+
+ public boolean isWrite()
+ {
+ return true;
+ }
+ }
+
+ private class SubFlushRunner implements ReadWriteRunnable
+ {
+ private final Subscription _sub;
+
+ public SubFlushRunner(Subscription sub)
+ {
+ _sub = sub;
+ }
+
+ public void run()
+ {
+ boolean complete = false;
+ try
+ {
+ complete = flushSubscription(_sub, new Long(MAX_ASYNC_DELIVERIES));
+
+ }
+ catch (AMQException e)
+ {
+ _logger.error(e);
+ }
+ if (!complete && !_sub.isSuspended())
+ {
+ _asyncDelivery.execute(this);
+ }
+
+ }
+
+ public boolean isRead()
+ {
+ return false;
+ }
+
+ public boolean isWrite()
+ {
+ return true;
+ }
+ }
+
+ public void flushSubscription(Subscription sub) throws AMQException
+ {
+ flushSubscription(sub, Long.MAX_VALUE);
+ }
+
+ public boolean flushSubscription(Subscription sub, Long iterations) throws AMQException
+ {
+ boolean atTail = false;
+
+ while (!sub.isSuspended() && !atTail && iterations != 0)
+ {
+ try
+ {
+ sub.getSendLock();
+ atTail = attemptDelivery(sub);
+ if (atTail && sub.isAutoClose())
+ {
+ unregisterSubscription(sub);
+
+ ProtocolOutputConverter converter = sub.getChannel().getProtocolSession().getProtocolOutputConverter();
+ converter.confirmConsumerAutoClose(sub.getChannel().getChannelId(), sub.getConsumerTag());
+ }
+ else if (!atTail)
+ {
+ iterations--;
+ }
+ }
+ finally
+ {
+ sub.releaseSendLock();
+ }
+ }
+
+ // if there's (potentially) more than one subscription the others will potentially not have been advanced to the
+ // next entry they are interested in yet. This would lead to holding on to references to expired messages, etc
+ // which would give us memory "leak".
+
+ if (!isExclusiveSubscriber())
+ {
+ advanceAllSubscriptions();
+ }
+ return atTail;
+ }
+
+ private boolean attemptDelivery(Subscription sub) throws AMQException
+ {
+ boolean atTail = false;
+ boolean advanced = false;
+ boolean subActive = sub.isActive();
+ if (subActive)
+ {
+ QueueEntry node = moveSubscriptionToNextNode(sub);
+ if (!(node.isAcquired() || node.isDeleted()))
+ {
+ if (!sub.isSuspended())
+ {
+ if (sub.hasInterest(node))
+ {
+ if (!sub.wouldSuspend(node))
+ {
+ if (!sub.isBrowser() && !node.acquire(sub))
+ {
+ sub.restoreCredit(node);
+ }
+ else
+ {
+ deliverMessage(sub, node);
+
+ if (sub.isBrowser())
+ {
+ QueueEntry newNode = _entries.next(node);
+
+ if (newNode != null)
+ {
+ advanced = true;
+ sub.setLastSeenEntry(node, newNode);
+ node = sub.getLastSeenEntry();
+ }
+ }
+ }
+
+ }
+ else // Not enough Credit for message and wouldSuspend
+ {
+ //QPID-1187 - Treat the subscription as suspended for this message
+ // and wait for the message to be removed to continue delivery.
+ subActive = false;
+ node.addStateChangeListener(new QueueEntryListener(sub, node));
+ }
+ }
+ else
+ {
+ // this subscription is not interested in this node so we can skip over it
+ QueueEntry newNode = _entries.next(node);
+ if (newNode != null)
+ {
+ sub.setLastSeenEntry(node, newNode);
+ }
+ }
+ }
+
+ }
+ atTail = (_entries.next(node) == null) && !advanced;
+ }
+ return atTail || !subActive;
+ }
+
+ protected void advanceAllSubscriptions() throws AMQException
+ {
+ SubscriptionList.SubscriptionNodeIterator subscriberIter = _subscriptionList.iterator();
+ while (subscriberIter.advance())
+ {
+ SubscriptionList.SubscriptionNode subNode = subscriberIter.getNode();
+ Subscription sub = subNode.getSubscription();
+ moveSubscriptionToNextNode(sub);
+ }
+ }
+
+ private QueueEntry moveSubscriptionToNextNode(final Subscription sub)
+ throws AMQException
+ {
+ QueueEntry node = sub.getLastSeenEntry();
+
+ while (node != null && (node.isAcquired() || node.isDeleted() || node.expired()))
+ {
+ if (!node.isAcquired() && !node.isDeleted() && node.expired())
+ {
+ if (node.acquire())
+ {
+ final StoreContext reapingStoreContext = new StoreContext();
+ node.discard(reapingStoreContext);
+ }
+ }
+ QueueEntry newNode = _entries.next(node);
+ if (newNode != null)
+ {
+ sub.setLastSeenEntry(node, newNode);
+ node = sub.getLastSeenEntry();
+ }
+ else
+ {
+ break;
+ }
+
+ }
+ return node;
+ }
+
+ private void processQueue(Runnable runner) throws AMQException
+ {
+ long stateChangeCount;
+ long previousStateChangeCount = Long.MIN_VALUE;
+ boolean deliveryIncomplete = true;
+
+ int extraLoops = 1;
+ Long iterations = new Long(MAX_ASYNC_DELIVERIES);
+
+ _asynchronousRunner.compareAndSet(runner, null);
+
+ while (iterations != 0 && ((previousStateChangeCount != (stateChangeCount = _stateChangeCount.get())) || deliveryIncomplete) && _asynchronousRunner.compareAndSet(null, runner))
+ {
+ // we want to have one extra loop after every subscription has reached the point where it cannot move
+ // further, just in case the advance of one subscription in the last loop allows a different subscription to
+ // move forward in the next iteration
+
+ if (previousStateChangeCount != stateChangeCount)
+ {
+ extraLoops = 1;
+ }
+
+ previousStateChangeCount = stateChangeCount;
+ deliveryIncomplete = _subscriptionList.size() != 0;
+ boolean done = true;
+
+ SubscriptionList.SubscriptionNodeIterator subscriptionIter = _subscriptionList.iterator();
+ //iterate over the subscribers and try to advance their pointer
+ while (subscriptionIter.advance())
+ {
+ boolean closeConsumer = false;
+ Subscription sub = subscriptionIter.getNode().getSubscription();
+ sub.getSendLock();
+ try
+ {
+ if (sub != null)
+ {
+
+ QueueEntry node = moveSubscriptionToNextNode(sub);
+ if (node != null)
+ {
+ done = attemptDelivery(sub);
+ }
+ }
+ if (done)
+ {
+ if (extraLoops == 0)
+ {
+ deliveryIncomplete = false;
+ if (sub.isAutoClose())
+ {
+ unregisterSubscription(sub);
+
+ ProtocolOutputConverter converter = sub.getChannel().getProtocolSession().getProtocolOutputConverter();
+ converter.confirmConsumerAutoClose(sub.getChannel().getChannelId(), sub.getConsumerTag());
+ }
+ }
+ else
+ {
+ extraLoops--;
+ }
+ }
+ else
+ {
+ iterations--;
+ extraLoops = 1;
+ }
+ }
+ finally
+ {
+ sub.releaseSendLock();
+ }
+ }
+ _asynchronousRunner.set(null);
+ }
+
+ // If deliveries == 0 then the limitting factor was the time-slicing rather than available messages or credit
+ // therefore we should schedule this runner again (unless someone beats us to it :-) ).
+ if (iterations == 0 && _asynchronousRunner.compareAndSet(null, runner))
+ {
+ _asyncDelivery.execute(runner);
+ }
+ }
+
+ @Override
+ public void checkMessageStatus() throws AMQException
+ {
+
+ final StoreContext storeContext = new StoreContext();
+
+ QueueEntryIterator queueListIterator = _entries.iterator();
+
+ while (queueListIterator.advance())
+ {
+ QueueEntry node = queueListIterator.getNode();
+ if (!node.isDeleted() && node.expired() && node.acquire())
+ {
+ node.discard(storeContext);
+ }
+ else
+ {
+ _managedObject.checkForNotification(node.getMessage());
+ }
+ }
+
+ }
+
+ public long getMinimumAlertRepeatGap()
+ {
+ return _minimumAlertRepeatGap;
+ }
+
+ public void setMinimumAlertRepeatGap(long minimumAlertRepeatGap)
+ {
+ _minimumAlertRepeatGap = minimumAlertRepeatGap;
+ }
+
+ public long getMaximumMessageAge()
+ {
+ return _maximumMessageAge;
+ }
+
+ public void setMaximumMessageAge(long maximumMessageAge)
+ {
+ _maximumMessageAge = maximumMessageAge;
+ if (maximumMessageAge == 0L)
+ {
+ _notificationChecks.remove(NotificationCheck.MESSAGE_AGE_ALERT);
+ }
+ else
+ {
+ _notificationChecks.add(NotificationCheck.MESSAGE_AGE_ALERT);
+ }
+ }
+
+ public long getMaximumMessageCount()
+ {
+ return _maximumMessageCount;
+ }
+
+ public void setMaximumMessageCount(final long maximumMessageCount)
+ {
+ _maximumMessageCount = maximumMessageCount;
+ if (maximumMessageCount == 0L)
+ {
+ _notificationChecks.remove(NotificationCheck.MESSAGE_COUNT_ALERT);
+ }
+ else
+ {
+ _notificationChecks.add(NotificationCheck.MESSAGE_COUNT_ALERT);
+ }
+
+ }
+
+ public long getMaximumQueueDepth()
+ {
+ return _maximumQueueDepth;
+ }
+
+ // Sets the queue depth, the max queue size
+ public void setMaximumQueueDepth(final long maximumQueueDepth)
+ {
+ _maximumQueueDepth = maximumQueueDepth;
+ if (maximumQueueDepth == 0L)
+ {
+ _notificationChecks.remove(NotificationCheck.QUEUE_DEPTH_ALERT);
+ }
+ else
+ {
+ _notificationChecks.add(NotificationCheck.QUEUE_DEPTH_ALERT);
+ }
+
+ }
+
+ public long getMaximumMessageSize()
+ {
+ return _maximumMessageSize;
+ }
+
+ public void setMaximumMessageSize(final long maximumMessageSize)
+ {
+ _maximumMessageSize = maximumMessageSize;
+ if (maximumMessageSize == 0L)
+ {
+ _notificationChecks.remove(NotificationCheck.MESSAGE_SIZE_ALERT);
+ }
+ else
+ {
+ _notificationChecks.add(NotificationCheck.MESSAGE_SIZE_ALERT);
+ }
+ }
+
+ public Set<NotificationCheck> getNotificationChecks()
+ {
+ return _notificationChecks;
+ }
+
+ public ManagedObject getManagedObject()
+ {
+ return _managedObject;
+ }
+
+ private final class QueueEntryListener implements QueueEntry.StateChangeListener
+ {
+ private final QueueEntry _entry;
+ private final Subscription _sub;
+
+ public QueueEntryListener(final Subscription sub, final QueueEntry entry)
+ {
+ _entry = entry;
+ _sub = sub;
+ }
+
+ public boolean equals(Object o)
+ {
+ return _entry == ((QueueEntryListener) o)._entry && _sub == ((QueueEntryListener) o)._sub;
+ }
+
+ public int hashCode()
+ {
+ return System.identityHashCode(_entry) ^ System.identityHashCode(_sub);
+ }
+
+ public void stateChanged(QueueEntry entry, QueueEntry.State oldSate, QueueEntry.State newState)
+ {
+ entry.removeStateChangeListener(this);
+ deliverAsync(_sub);
+ }
+ }
+
+ public List<Long> getMessagesOnTheQueue(int num)
+ {
+ return getMessagesOnTheQueue(num, 0);
+ }
+
+ public List<Long> getMessagesOnTheQueue(int num, int offset)
+ {
+ ArrayList<Long> ids = new ArrayList<Long>(num);
+ QueueEntryIterator it = _entries.iterator();
+ for (int i = 0; i < offset; i++)
+ {
+ it.advance();
+ }
+
+ for (int i = 0; i < num && !it.atTail(); i++)
+ {
+ it.advance();
+ ids.add(it.getNode().getMessage().getMessageId());
+ }
+ return ids;
+ }
+
+ public void configure(QueueConfiguration config)
+ {
+ if (config != null)
+ {
+ setMaximumMessageAge(config.getMaximumMessageAge());
+ setMaximumQueueDepth(config.getMaximumQueueDepth());
+ setMaximumMessageSize(config.getMaximumMessageSize());
+ setMaximumMessageCount(config.getMaximumMessageCount());
+ setMinimumAlertRepeatGap(config.getMinimumAlertRepeatGap());
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java
new file mode 100644
index 0000000000..a46c5ae2e8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java
@@ -0,0 +1,178 @@
+package org.apache.qpid.server.queue;
+
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+public class SimpleQueueEntryList implements QueueEntryList
+{
+ private final QueueEntryImpl _head;
+
+ private volatile QueueEntryImpl _tail;
+
+ static final AtomicReferenceFieldUpdater<SimpleQueueEntryList, QueueEntryImpl>
+ _tailUpdater =
+ AtomicReferenceFieldUpdater.newUpdater
+ (SimpleQueueEntryList.class, QueueEntryImpl.class, "_tail");
+
+
+ private final AMQQueue _queue;
+
+ static final AtomicReferenceFieldUpdater<QueueEntryImpl, QueueEntryImpl>
+ _nextUpdater =
+ AtomicReferenceFieldUpdater.newUpdater
+ (QueueEntryImpl.class, QueueEntryImpl.class, "_next");
+
+
+
+
+
+ public SimpleQueueEntryList(AMQQueue queue)
+ {
+ _queue = queue;
+ _head = new QueueEntryImpl(this);
+ _tail = _head;
+ }
+
+ void advanceHead()
+ {
+ QueueEntryImpl head = _head.nextNode();
+ while(head._next != null && head.isDeleted())
+ {
+
+ final QueueEntryImpl newhead = head.nextNode();
+ if(newhead != null)
+ {
+ _nextUpdater.compareAndSet(_head,head, newhead);
+ }
+ head = _head.nextNode();
+ }
+ }
+
+
+ public AMQQueue getQueue()
+ {
+ return _queue;
+ }
+
+
+ public QueueEntry add(AMQMessage message)
+ {
+ QueueEntryImpl node = new QueueEntryImpl(this, message);
+ for (;;)
+ {
+ QueueEntryImpl tail = _tail;
+ QueueEntryImpl next = tail.nextNode();
+ if (tail == _tail)
+ {
+ if (next == null)
+ {
+ node.setEntryId(tail.getEntryId()+1);
+ if (_nextUpdater.compareAndSet(tail, null, node))
+ {
+ _tailUpdater.compareAndSet(this, tail, node);
+
+ return node;
+ }
+ }
+ else
+ {
+ _tailUpdater.compareAndSet(this,tail, next);
+ }
+ }
+ }
+ }
+
+ public QueueEntry next(QueueEntry node)
+ {
+ return ((QueueEntryImpl)node).getNext();
+ }
+
+
+ public class QueueEntryIteratorImpl implements QueueEntryIterator
+ {
+
+ private QueueEntryImpl _lastNode;
+
+ QueueEntryIteratorImpl(QueueEntryImpl startNode)
+ {
+ _lastNode = startNode;
+ }
+
+
+ public boolean atTail()
+ {
+ return _lastNode.nextNode() == null;
+ }
+
+ public QueueEntry getNode()
+ {
+
+ return _lastNode;
+
+ }
+
+ public boolean advance()
+ {
+
+ if(!atTail())
+ {
+ QueueEntryImpl nextNode = _lastNode.nextNode();
+ while(nextNode.isDeleted() && nextNode.nextNode() != null)
+ {
+ nextNode = nextNode.nextNode();
+ }
+ _lastNode = nextNode;
+ return true;
+
+ }
+ else
+ {
+ return false;
+ }
+
+ }
+
+ }
+
+
+ public QueueEntryIterator iterator()
+ {
+ return new QueueEntryIteratorImpl(_head);
+ }
+
+
+ public QueueEntry getHead()
+ {
+ return _head;
+ }
+
+ static class Factory implements QueueEntryListFactory
+ {
+
+ public QueueEntryList createQueueEntryList(AMQQueue queue)
+ {
+ return new SimpleQueueEntryList(queue);
+ }
+ }
+
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/TransientMessageData.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/TransientMessageData.java
new file mode 100644
index 0000000000..9b91c71a1d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/TransientMessageData.java
@@ -0,0 +1,127 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentHeaderBody;
+
+/**
+ * Contains data that is only used in AMQMessage transiently, e.g. while the content
+ * body fragments are arriving.
+ *
+ * Having this data stored in a separate class means that the AMQMessage class avoids
+ * the small overhead of numerous guaranteed-null references.
+ *
+ * @author Apache Software Foundation
+ */
+public class TransientMessageData
+{
+ /**
+ * Stored temporarily until the header has been received at which point it is used when
+ * constructing the handle
+ */
+ private MessagePublishInfo _messagePublishInfo;
+
+ /**
+ * Also stored temporarily.
+ */
+ private ContentHeaderBody _contentHeaderBody;
+
+ /**
+ * Keeps a track of how many bytes we have received in body frames
+ */
+ private long _bodyLengthReceived = 0;
+
+ /**
+ * 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;
+
+ public MessagePublishInfo getMessagePublishInfo()
+ {
+ return _messagePublishInfo;
+ }
+
+ public void setMessagePublishInfo(MessagePublishInfo messagePublishInfo)
+ {
+ _messagePublishInfo = messagePublishInfo;
+ }
+
+ public List<AMQQueue> getDestinationQueues()
+ {
+ return _destinationQueues == null ? (List<AMQQueue>) Collections.EMPTY_LIST : _destinationQueues;
+ }
+
+ public void setDestinationQueues(List<AMQQueue> destinationQueues)
+ {
+ _destinationQueues = destinationQueues;
+ }
+
+ public ContentHeaderBody getContentHeaderBody()
+ {
+ return _contentHeaderBody;
+ }
+
+ public void setContentHeaderBody(ContentHeaderBody contentHeaderBody)
+ {
+ _contentHeaderBody = contentHeaderBody;
+ }
+
+ public long getBodyLengthReceived()
+ {
+ return _bodyLengthReceived;
+ }
+
+ public void addBodyLength(int value)
+ {
+ _bodyLengthReceived += value;
+ }
+
+ public boolean isAllContentReceived() throws AMQException
+ {
+ return _bodyLengthReceived == _contentHeaderBody.bodySize;
+ }
+
+ public void addDestinationQueue(AMQQueue queue)
+ {
+ if(_destinationQueues == null)
+ {
+ _destinationQueues = new ArrayList<AMQQueue>();
+ }
+ _destinationQueues.add(queue);
+ }
+
+ public boolean isPersistent()
+ {
+ //todo remove literal values to a constant file such as AMQConstants in common
+ return _contentHeaderBody.properties instanceof BasicContentHeaderProperties &&
+ ((BasicContentHeaderProperties) _contentHeaderBody.properties).getDeliveryMode() == 2;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/UnauthorizedAccessException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/UnauthorizedAccessException.java
new file mode 100644
index 0000000000..295cb266b9
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/UnauthorizedAccessException.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.RequiredDeliveryException;
+
+/**
+ * UnauthorizedAccessException is a {@link RequiredDeliveryException} that represents the failure case where a message
+ * is published with a user id different from the one used when creating the connection .
+ * The AMQP status code, 403, is always used to report this condition.
+ *
+ */
+
+public class UnauthorizedAccessException extends RequiredDeliveryException
+{
+ public UnauthorizedAccessException(String msg, AMQMessage amqMessage)
+ {
+ super(msg, amqMessage);
+ }
+
+ public AMQConstant getReplyCode()
+ {
+ return AMQConstant.ACCESS_REFUSED;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java
new file mode 100644
index 0000000000..3ed8b0e55c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java
@@ -0,0 +1,219 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+
+/**
+ * @author Robert Greig (robert.j.greig@jpmorgan.com)
+ */
+public class WeakReferenceMessageHandle implements AMQMessageHandle
+{
+ private WeakReference<ContentHeaderBody> _contentHeaderBody;
+
+ private WeakReference<MessagePublishInfo> _messagePublishInfo;
+
+ private List<WeakReference<ContentChunk>> _contentBodies;
+
+ private boolean _redelivered;
+
+ private final MessageStore _messageStore;
+
+ private final Long _messageId;
+ private long _arrivalTime;
+
+ public WeakReferenceMessageHandle(final Long messageId, MessageStore messageStore)
+ {
+ _messageId = messageId;
+ _messageStore = messageStore;
+ }
+
+ public ContentHeaderBody getContentHeaderBody(StoreContext context) throws AMQException
+ {
+ ContentHeaderBody chb = (_contentHeaderBody != null ? _contentHeaderBody.get() : null);
+ if (chb == null)
+ {
+ MessageMetaData mmd = loadMessageMetaData(context);
+ chb = mmd.getContentHeaderBody();
+ }
+ return chb;
+ }
+
+ public Long getMessageId()
+ {
+ return _messageId;
+ }
+
+ private MessageMetaData loadMessageMetaData(StoreContext context)
+ throws AMQException
+ {
+ MessageMetaData mmd = _messageStore.getMessageMetaData(context, _messageId);
+ populateFromMessageMetaData(mmd);
+ return mmd;
+ }
+
+ private void populateFromMessageMetaData(MessageMetaData mmd)
+ {
+ _arrivalTime = mmd.getArrivalTime();
+ _contentHeaderBody = new WeakReference<ContentHeaderBody>(mmd.getContentHeaderBody());
+ _messagePublishInfo = new WeakReference<MessagePublishInfo>(mmd.getMessagePublishInfo());
+ }
+
+ public int getBodyCount(StoreContext context) throws AMQException
+ {
+ if (_contentBodies == null)
+ {
+ MessageMetaData mmd = _messageStore.getMessageMetaData(context, _messageId);
+ int chunkCount = mmd.getContentChunkCount();
+ _contentBodies = new ArrayList<WeakReference<ContentChunk>>(chunkCount);
+ for (int i = 0; i < chunkCount; i++)
+ {
+ _contentBodies.add(new WeakReference<ContentChunk>(null));
+ }
+ }
+ return _contentBodies.size();
+ }
+
+ public long getBodySize(StoreContext context) throws AMQException
+ {
+ return getContentHeaderBody(context).bodySize;
+ }
+
+ public ContentChunk getContentChunk(StoreContext context, 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<ContentChunk> wr = _contentBodies.get(index);
+ ContentChunk cb = wr.get();
+ if (cb == null)
+ {
+ cb = _messageStore.getContentBodyChunk(context, _messageId, index);
+ _contentBodies.set(index, new WeakReference<ContentChunk>(cb));
+ }
+ return cb;
+ }
+
+ /**
+ * Content bodies are set <i>before</i> the publish and header frames
+ *
+ * @param storeContext
+ * @param contentChunk
+ * @param isLastContentBody
+ * @throws AMQException
+ */
+ public void addContentBodyFrame(StoreContext storeContext, ContentChunk contentChunk, boolean isLastContentBody) throws AMQException
+ {
+ if (_contentBodies == null && isLastContentBody)
+ {
+ _contentBodies = new ArrayList<WeakReference<ContentChunk>>(1);
+ }
+ else
+ {
+ if (_contentBodies == null)
+ {
+ _contentBodies = new LinkedList<WeakReference<ContentChunk>>();
+ }
+ }
+ _contentBodies.add(new WeakReference<ContentChunk>(contentChunk));
+ _messageStore.storeContentBodyChunk(storeContext, _messageId, _contentBodies.size() - 1,
+ contentChunk, isLastContentBody);
+ }
+
+ public MessagePublishInfo getMessagePublishInfo(StoreContext context) throws AMQException
+ {
+ MessagePublishInfo bpb = (_messagePublishInfo != null ? _messagePublishInfo.get() : null);
+ if (bpb == null)
+ {
+ MessageMetaData mmd = loadMessageMetaData(context);
+
+ bpb = mmd.getMessagePublishInfo();
+ }
+ return bpb;
+ }
+
+ public boolean isRedelivered()
+ {
+ return _redelivered;
+ }
+
+ public void setRedelivered(boolean redelivered)
+ {
+ _redelivered = redelivered;
+ }
+
+ public boolean isPersistent()
+ {
+ return true;
+ }
+
+ /**
+ * This is called when all the content has been received.
+ *
+ * @param publishBody
+ * @param contentHeaderBody
+ * @throws AMQException
+ */
+ public void setPublishAndContentHeaderBody(StoreContext storeContext, MessagePublishInfo publishBody,
+ ContentHeaderBody contentHeaderBody)
+ throws AMQException
+ {
+ // if there are no content bodies the list will be null so we must
+ // create en empty list here
+ if (contentHeaderBody.bodySize == 0)
+ {
+ _contentBodies = new LinkedList<WeakReference<ContentChunk>>();
+ }
+
+ final long arrivalTime = System.currentTimeMillis();
+
+
+ MessageMetaData mmd = new MessageMetaData(publishBody, contentHeaderBody, _contentBodies.size(), arrivalTime);
+
+ _messageStore.storeMessageMetaData(storeContext, _messageId, mmd);
+
+
+ populateFromMessageMetaData(mmd);
+ }
+
+ public void removeMessage(StoreContext storeContext) throws AMQException
+ {
+ _messageStore.removeMessage(storeContext, _messageId);
+ }
+
+ public long getArrivalTime()
+ {
+ return _arrivalTime;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java
new file mode 100644
index 0000000000..22b4623ae1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java
@@ -0,0 +1,290 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.registry;
+
+import java.net.InetSocketAddress;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.log4j.Logger;
+import org.apache.mina.common.IoAcceptor;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.management.ManagedObjectRegistry;
+import org.apache.qpid.server.plugins.PluginManager;
+import org.apache.qpid.server.security.access.ACLManager;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager;
+import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+
+/**
+ * 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
+{
+ protected static final Logger _logger = Logger.getLogger(ApplicationRegistry.class);
+
+ private static Map<Integer, IApplicationRegistry> _instanceMap = new HashMap<Integer, IApplicationRegistry>();
+
+ private final Map<Class<?>, Object> _configuredObjects = new HashMap<Class<?>, Object>();
+
+ protected final ServerConfiguration _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;
+
+ protected final Map<InetSocketAddress, IoAcceptor> _acceptors = new HashMap<InetSocketAddress, IoAcceptor>();
+
+ protected ManagedObjectRegistry _managedObjectRegistry;
+
+ protected AuthenticationManager _authenticationManager;
+
+ protected VirtualHostRegistry _virtualHostRegistry;
+
+ protected ACLManager _accessManager;
+
+ protected PrincipalDatabaseManager _databaseManager;
+
+ protected PluginManager _pluginManager;
+
+ static
+ {
+ Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownService()));
+ }
+
+ private static class ShutdownService implements Runnable
+ {
+ public void run()
+ {
+ removeAll();
+ }
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * Method to cleanly shutdown specified registry running in this JVM
+ *
+ * @param instanceID the instance to shutdown
+ */
+
+ public static void remove(int instanceID)
+ {
+ try
+ {
+ IApplicationRegistry instance = _instanceMap.get(instanceID);
+ if (instance != null)
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Shuting down ApplicationRegistry(" + instanceID + "):" + instance);
+ }
+ instance.close();
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.error("Error shutting down Application Registry(" + instanceID + "): " + e, e);
+ }
+ finally
+ {
+ _instanceMap.remove(instanceID);
+ }
+ }
+
+ /** Method to cleanly shutdown all registries currently running in this JVM */
+ public static void removeAll()
+ {
+ Object[] keys = _instanceMap.keySet().toArray();
+ for (Object k : keys)
+ {
+ remove((Integer) k);
+ }
+ }
+
+ protected ApplicationRegistry(ServerConfiguration configuration)
+ {
+ _configuration = configuration;
+ }
+
+ public static IApplicationRegistry getInstance()
+ {
+ return getInstance(DEFAULT_INSTANCE);
+ }
+
+ public static IApplicationRegistry getInstance(int instanceID)
+ {
+ synchronized (IApplicationRegistry.class)
+ {
+ IApplicationRegistry instance = _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", e);
+ }
+ }
+ else
+ {
+ return instance;
+ }
+ }
+ }
+
+ public void close() throws Exception
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Shutting down ApplicationRegistry:"+this);
+ }
+
+ //Stop incomming connections
+ unbind();
+
+ //Shutdown virtualhosts
+ for (VirtualHost virtualHost : getVirtualHostRegistry().getVirtualHosts())
+ {
+ virtualHost.close();
+ }
+
+ // Replace above with this
+// _virtualHostRegistry.close();
+
+// _accessManager.close();
+
+// _databaseManager.close();
+
+ _authenticationManager.close();
+
+// _databaseManager.close();
+
+ // close the rmi registry(if any) started for management
+ if (_managedObjectRegistry != null)
+ {
+ _managedObjectRegistry.close();
+ }
+
+// _pluginManager.close();
+ }
+
+ private void unbind()
+ {
+ synchronized (_acceptors)
+ {
+ for (InetSocketAddress bindAddress : _acceptors.keySet())
+ {
+ IoAcceptor acceptor = _acceptors.get(bindAddress);
+ acceptor.unbind(bindAddress);
+ }
+ }
+ }
+
+ public ServerConfiguration getConfiguration()
+ {
+ return _configuration;
+ }
+
+ public void addAcceptor(InetSocketAddress bindAddress, IoAcceptor acceptor)
+ {
+ synchronized (_acceptors)
+ {
+ _acceptors.put(bindAddress, acceptor);
+ }
+ }
+
+ public static void setDefaultApplicationRegistry(String clazz)
+ {
+ _APPLICATION_REGISTRY = clazz;
+ }
+
+ public VirtualHostRegistry getVirtualHostRegistry()
+ {
+ return _virtualHostRegistry;
+ }
+
+ public ACLManager getAccessManager() throws ConfigurationException
+ {
+ return new ACLManager(_configuration.getSecurityConfiguration(), _pluginManager);
+ }
+
+ public ManagedObjectRegistry getManagedObjectRegistry()
+ {
+ return _managedObjectRegistry;
+ }
+
+ public PrincipalDatabaseManager getDatabaseManager()
+ {
+ return _databaseManager;
+ }
+
+ public AuthenticationManager getAuthenticationManager()
+ {
+ return _authenticationManager;
+ }
+
+ public PluginManager getPluginManager()
+ {
+ return _pluginManager;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java
new file mode 100644
index 0000000000..39164883f9
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java
@@ -0,0 +1,87 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.registry;
+
+import java.io.File;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.management.JMXManagedObjectRegistry;
+import org.apache.qpid.server.management.NoopManagedObjectRegistry;
+import org.apache.qpid.server.plugins.PluginManager;
+import org.apache.qpid.server.security.access.ACLManager;
+import org.apache.qpid.server.security.auth.database.ConfigurationFilePrincipalDatabaseManager;
+import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+
+public class ConfigurationFileApplicationRegistry extends ApplicationRegistry
+{
+
+ public ConfigurationFileApplicationRegistry(File configurationURL) throws ConfigurationException
+ {
+ super(new ServerConfiguration(configurationURL));
+ }
+
+ public void initialise() throws Exception
+ {
+ initialiseManagedObjectRegistry();
+
+ _virtualHostRegistry = new VirtualHostRegistry();
+
+ _pluginManager = new PluginManager(_configuration.getPluginDirectory());
+
+ _accessManager = new ACLManager(_configuration.getSecurityConfiguration(), _pluginManager);
+
+ _databaseManager = new ConfigurationFilePrincipalDatabaseManager(_configuration);
+
+ _authenticationManager = new PrincipalDatabaseAuthenticationManager(null, null);
+
+ _databaseManager.initialiseManagement(_configuration);
+
+ _managedObjectRegistry.start();
+
+ initialiseVirtualHosts();
+
+ }
+
+ private void initialiseVirtualHosts() throws Exception
+ {
+ for (String name : _configuration.getVirtualHosts())
+ {
+ _virtualHostRegistry.registerVirtualHost(new VirtualHost(_configuration.getVirtualHostConfig(name)));
+ }
+ getVirtualHostRegistry().setDefaultVirtualHostName(_configuration.getDefaultVirtualHost());
+ }
+
+ private void initialiseManagedObjectRegistry() throws AMQException
+ {
+ if (_configuration.getManagementEnabled())
+ {
+ _managedObjectRegistry = new JMXManagedObjectRegistry();
+ }
+ else
+ {
+ _managedObjectRegistry = new NoopManagedObjectRegistry();
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java
new file mode 100644
index 0000000000..bbfda3addc
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java
@@ -0,0 +1,79 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.registry;
+
+import java.util.Collection;
+import java.net.InetSocketAddress;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.management.ManagedObjectRegistry;
+import org.apache.qpid.server.plugins.PluginManager;
+import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager;
+import org.apache.qpid.server.security.access.ACLManager;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+import org.apache.mina.common.IoAcceptor;
+
+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;
+
+ /**
+ * Shutdown this Registry
+ * @throws Exception - //fixme needs to be made more specific
+ */
+ void close() throws Exception;
+
+ /**
+ * 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
+ */
+ ServerConfiguration getConfiguration();
+
+ ManagedObjectRegistry getManagedObjectRegistry();
+
+ PrincipalDatabaseManager getDatabaseManager();
+
+ AuthenticationManager getAuthenticationManager();
+
+ VirtualHostRegistry getVirtualHostRegistry();
+
+ ACLManager getAccessManager() throws ConfigurationException;
+
+ PluginManager getPluginManager();
+
+ /**
+ * Register any acceptors for this registry
+ * @param bindAddress The address that the acceptor has been bound with
+ * @param acceptor The acceptor in use
+ */
+ void addAcceptor(InetSocketAddress bindAddress, IoAcceptor acceptor);
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLManager.java
new file mode 100644
index 0000000000..6f7f66fad2
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLManager.java
@@ -0,0 +1,322 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.log4j.Logger;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.configuration.SecurityConfiguration;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.plugins.PluginManager;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.security.access.ACLPlugin.AuthzResult;
+import org.apache.qpid.server.security.access.plugins.SimpleXML;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class ACLManager
+{
+ private static final Logger _logger = Logger.getLogger(ACLManager.class);
+ private PluginManager _pluginManager;
+ private Map<String, ACLPluginFactory> _allSecurityPlugins = new HashMap<String, ACLPluginFactory>();
+ private Map<String, ACLPlugin> _globalPlugins = new HashMap<String, ACLPlugin>();
+ private Map<String, ACLPlugin> _hostPlugins = new HashMap<String, ACLPlugin>();
+
+ public ACLManager(SecurityConfiguration configuration, PluginManager manager) throws ConfigurationException
+ {
+ this(configuration, manager, null);
+ }
+
+ public ACLManager(SecurityConfiguration configuration, PluginManager manager, ACLPluginFactory securityPlugin) throws ConfigurationException
+ {
+ _pluginManager = manager;
+
+ if (manager == null) // No plugin manager, no plugins
+ {
+ return;
+ }
+
+ _allSecurityPlugins = _pluginManager.getSecurityPlugins();
+ if (securityPlugin != null)
+ {
+ _allSecurityPlugins.put(securityPlugin.getClass().getName(), securityPlugin);
+ }
+
+ _globalPlugins = configurePlugins(configuration);
+ }
+
+
+ public void configureHostPlugins(SecurityConfiguration hostConfig) throws ConfigurationException
+ {
+ _hostPlugins = configurePlugins(hostConfig);
+ }
+
+ public Map<String, ACLPlugin> configurePlugins(SecurityConfiguration hostConfig) throws ConfigurationException
+ {
+ Configuration securityConfig = hostConfig.getConfiguration();
+ Map<String, ACLPlugin> plugins = new HashMap<String, ACLPlugin>();
+ Iterator keys = securityConfig.getKeys();
+ Collection<String> handledTags = new HashSet();
+ while (keys.hasNext())
+ {
+ // Splitting the string is necessary here because of the way that getKeys() returns only
+ // bottom level children
+ String tag = ((String) keys.next()).split("\\.", 2)[0];
+ if (!handledTags.contains(tag))
+ {
+ for (ACLPluginFactory plugin : _allSecurityPlugins.values())
+ {
+ if (plugin.supportsTag(tag))
+ {
+ _logger.warn("Plugin handling security section "+tag+" is "+plugin.getClass().getSimpleName());
+ handledTags.add(tag);
+ plugins.put(plugin.getClass().getName(), plugin.newInstance(securityConfig));
+ }
+ }
+ }
+ if (!handledTags.contains(tag))
+ {
+ _logger.warn("No plugin handled security section "+tag);
+ }
+ }
+ return plugins;
+ }
+
+ public static Logger getLogger()
+ {
+ return _logger;
+ }
+
+ private abstract class AccessCheck
+ {
+ abstract AuthzResult allowed(ACLPlugin plugin);
+ }
+
+ private boolean checkAllPlugins(AccessCheck checker)
+ {
+ AuthzResult result = AuthzResult.ABSTAIN;
+ HashMap<String, ACLPlugin> remainingPlugins = new HashMap<String, ACLPlugin>();
+ remainingPlugins.putAll(_globalPlugins);
+ for (Entry<String, ACLPlugin> plugin : _hostPlugins.entrySet())
+ {
+ result = checker.allowed(plugin.getValue());
+ if (result == AuthzResult.DENIED)
+ {
+ // Something vetoed the access, we're done
+ return false;
+ }
+ else if (result == AuthzResult.ALLOWED)
+ {
+ // Remove plugin from global check list since
+ // host allow overrides global allow
+ remainingPlugins.remove(plugin.getKey());
+ }
+ }
+
+ for (ACLPlugin plugin : remainingPlugins.values())
+ {
+ result = checker.allowed(plugin);
+ if (result == AuthzResult.DENIED)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean authoriseBind(final AMQProtocolSession session, final Exchange exch, final AMQQueue queue,
+ final AMQShortString routingKey)
+ {
+ return checkAllPlugins(new AccessCheck()
+ {
+
+ @Override
+ AuthzResult allowed(ACLPlugin plugin)
+ {
+ return plugin.authoriseBind(session, exch, queue, routingKey);
+ }
+
+ });
+ }
+
+ public boolean authoriseConnect(final AMQProtocolSession session, final VirtualHost virtualHost)
+ {
+ return checkAllPlugins(new AccessCheck()
+ {
+
+ @Override
+ AuthzResult allowed(ACLPlugin plugin)
+ {
+ return plugin.authoriseConnect(session, virtualHost);
+ }
+
+ });
+ }
+
+ public boolean authoriseConsume(final AMQProtocolSession session, final boolean noAck, final AMQQueue queue)
+ {
+ return checkAllPlugins(new AccessCheck()
+ {
+
+ @Override
+ AuthzResult allowed(ACLPlugin plugin)
+ {
+ return plugin.authoriseConsume(session, noAck, queue);
+ }
+
+ });
+ }
+
+ public boolean authoriseConsume(final AMQProtocolSession session, final boolean exclusive, final boolean noAck,
+ final boolean noLocal, final boolean nowait, final AMQQueue queue)
+ {
+ return checkAllPlugins(new AccessCheck()
+ {
+
+ @Override
+ AuthzResult allowed(ACLPlugin plugin)
+ {
+ return plugin.authoriseConsume(session, exclusive, noAck, noLocal, nowait, queue);
+ }
+
+ });
+ }
+
+ public boolean authoriseCreateExchange(final AMQProtocolSession session, final boolean autoDelete,
+ final boolean durable, final AMQShortString exchangeName, final boolean internal, final boolean nowait,
+ final boolean passive, final AMQShortString exchangeType)
+ {
+ return checkAllPlugins(new AccessCheck()
+ {
+
+ @Override
+ AuthzResult allowed(ACLPlugin plugin)
+ {
+ return plugin.authoriseCreateExchange(session, autoDelete, durable, exchangeName, internal, nowait,
+ passive, exchangeType);
+ }
+
+ });
+ }
+
+ public boolean authoriseCreateQueue(final AMQProtocolSession session, final boolean autoDelete,
+ final boolean durable, final boolean exclusive, final boolean nowait, final boolean passive,
+ final AMQShortString queue)
+ {
+ return checkAllPlugins(new AccessCheck()
+ {
+
+ @Override
+ AuthzResult allowed(ACLPlugin plugin)
+ {
+ return plugin.authoriseCreateQueue(session, autoDelete, durable, exclusive, nowait, passive, queue);
+ }
+
+ });
+ }
+
+ public boolean authoriseDelete(final AMQProtocolSession session, final AMQQueue queue)
+ {
+ return checkAllPlugins(new AccessCheck()
+ {
+
+ @Override
+ AuthzResult allowed(ACLPlugin plugin)
+ {
+ return plugin.authoriseDelete(session, queue);
+ }
+
+ });
+ }
+
+ public boolean authoriseDelete(final AMQProtocolSession session, final Exchange exchange)
+ {
+ return checkAllPlugins(new AccessCheck()
+ {
+
+ @Override
+ AuthzResult allowed(ACLPlugin plugin)
+ {
+ return plugin.authoriseDelete(session, exchange);
+ }
+
+ });
+ }
+
+ public boolean authorisePublish(final AMQProtocolSession session, final boolean immediate, final boolean mandatory,
+ final AMQShortString routingKey, final Exchange e)
+ {
+ return checkAllPlugins(new AccessCheck()
+ {
+
+ @Override
+ AuthzResult allowed(ACLPlugin plugin)
+ {
+ return plugin.authorisePublish(session, immediate, mandatory, routingKey, e);
+ }
+
+ });
+ }
+
+ public boolean authorisePurge(final AMQProtocolSession session, final AMQQueue queue)
+ {
+ return checkAllPlugins(new AccessCheck()
+ {
+
+ @Override
+ AuthzResult allowed(ACLPlugin plugin)
+ {
+ return plugin.authorisePurge(session, queue);
+ }
+
+ });
+ }
+
+ public boolean authoriseUnbind(final AMQProtocolSession session, final Exchange exch,
+ final AMQShortString routingKey, final AMQQueue queue)
+ {
+ return checkAllPlugins(new AccessCheck()
+ {
+
+ @Override
+ AuthzResult allowed(ACLPlugin plugin)
+ {
+ return plugin.authoriseUnbind(session, exch, routingKey, queue);
+ }
+
+ });
+ }
+
+ public void addHostPlugin(ACLPlugin aclPlugin)
+ {
+ _hostPlugins.put(aclPlugin.getClass().getName(), aclPlugin);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPlugin.java
new file mode 100644
index 0000000000..032184ec39
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPlugin.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public interface ACLPlugin
+{
+ public enum AuthzResult
+ {
+ ALLOWED,
+ DENIED,
+ ABSTAIN
+ }
+
+ void setConfiguration(Configuration config) throws ConfigurationException;
+
+ // These return true if the plugin thinks the action should be allowed, and false if not.
+
+ AuthzResult authoriseBind(AMQProtocolSession session, Exchange exch, AMQQueue queue, AMQShortString routingKey);
+
+ AuthzResult authoriseCreateExchange(AMQProtocolSession session, boolean autoDelete, boolean durable,
+ AMQShortString exchangeName, boolean internal, boolean nowait, boolean passive, AMQShortString exchangeType);
+
+ AuthzResult authoriseCreateQueue(AMQProtocolSession session, boolean autoDelete, boolean durable, boolean exclusive,
+ boolean nowait, boolean passive, AMQShortString queue);
+
+ AuthzResult authoriseConnect(AMQProtocolSession session, VirtualHost virtualHost);
+
+ AuthzResult authoriseConsume(AMQProtocolSession session, boolean noAck, AMQQueue queue);
+
+ AuthzResult authoriseConsume(AMQProtocolSession session, boolean exclusive, boolean noAck, boolean noLocal,
+ boolean nowait, AMQQueue queue);
+
+ AuthzResult authoriseDelete(AMQProtocolSession session, AMQQueue queue);
+
+ AuthzResult authoriseDelete(AMQProtocolSession session, Exchange exchange);
+
+ AuthzResult authorisePublish(AMQProtocolSession session, boolean immediate, boolean mandatory,
+ AMQShortString routingKey, Exchange e);
+
+ AuthzResult authorisePurge(AMQProtocolSession session, AMQQueue queue);
+
+ AuthzResult authoriseUnbind(AMQProtocolSession session, Exchange exch, AMQShortString routingKey, AMQQueue queue);
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPluginFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPluginFactory.java
new file mode 100644
index 0000000000..256f093477
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPluginFactory.java
@@ -0,0 +1,33 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+
+public interface ACLPluginFactory
+{
+
+ public boolean supportsTag(String name);
+
+ public ACLPlugin newInstance(Configuration config) throws ConfigurationException;
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessResult.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessResult.java
new file mode 100644
index 0000000000..d722da4ae0
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessResult.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+public class AccessResult
+{
+ public enum AccessStatus
+ {
+ GRANTED, REFUSED
+ }
+
+ private String _authorizer;
+ private AccessStatus _status;
+
+ public AccessResult(ACLPlugin authorizer, AccessStatus status)
+ {
+ _status = status;
+ _authorizer = authorizer.getClass().getSimpleName();
+ }
+
+ public void setAuthorizer(ACLPlugin authorizer)
+ {
+ _authorizer += authorizer.getClass().getSimpleName();
+ }
+
+ public String getAuthorizer()
+ {
+ return _authorizer;
+ }
+
+ public void setStatus(AccessStatus status)
+ {
+ _status = status;
+ }
+
+ public AccessStatus getStatus()
+ {
+ return _status;
+ }
+
+ public void addAuthorizer(ACLPlugin accessManager)
+ {
+ _authorizer = accessManager.getClass().getSimpleName() + "->" + _authorizer;
+ }
+
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessRights.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessRights.java
new file mode 100644
index 0000000000..1b79a5a0e0
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessRights.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+public class AccessRights
+{
+ public enum Rights
+ {
+ ANY,
+ READ,
+ WRITE,
+ READWRITE
+ }
+
+ Rights _right;
+
+ public AccessRights(Rights right)
+ {
+ _right = right;
+ }
+
+ public boolean allows(Rights rights)
+ {
+ switch (_right)
+ {
+ case ANY:
+ return (rights.equals(Rights.WRITE)
+ || rights.equals(Rights.READ)
+ || rights.equals(Rights.READWRITE)
+ || rights.equals(Rights.ANY));
+ case READ:
+ return rights.equals(Rights.READ) || rights.equals(Rights.ANY);
+ case WRITE:
+ return rights.equals(Rights.WRITE) || rights.equals(Rights.ANY);
+ case READWRITE:
+ return true;
+ }
+ return false;
+ }
+
+ public Rights getRights()
+ {
+ return _right;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Accessable.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Accessable.java
new file mode 100644
index 0000000000..f51cf24caa
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Accessable.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+public interface Accessable
+{
+ void setAccessableName(String name);
+ String getAccessableName();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AuthorizationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AuthorizationManager.java
new file mode 100644
index 0000000000..9527120f30
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AuthorizationManager.java
@@ -0,0 +1,6 @@
+package org.apache.qpid.server.security.access;
+
+public class AuthorizationManager
+{
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.java
new file mode 100644
index 0000000000..b65b0cdc6c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.queue.AMQQueue;
+
+public enum Permission
+{
+ CONSUME,
+ PUBLISH,
+ CREATEQUEUE,
+ CREATEEXCHANGE,
+ ACCESS,
+ BIND,
+ UNBIND,
+ DELETE,
+ PURGE
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/PrincipalPermissions.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/PrincipalPermissions.java
new file mode 100755
index 0000000000..f852514444
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/PrincipalPermissions.java
@@ -0,0 +1,612 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.QueueBindBody;
+import org.apache.qpid.framing.QueueDeclareBody;
+import org.apache.qpid.framing.ExchangeDeclareBody;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.security.access.ACLPlugin.AuthzResult;
+import org.apache.qpid.server.exchange.Exchange;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PrincipalPermissions
+{
+
+ private static final Object CONSUME_QUEUES_KEY = new Object();
+ private static final Object CONSUME_TEMPORARY_KEY = new Object();
+ private static final Object CONSUME_OWN_QUEUES_ONLY_KEY = new Object();
+
+ private static final Object CREATE_QUEUES_KEY = new Object();
+ private static final Object CREATE_EXCHANGES_KEY = new Object();
+
+ private static final Object CREATE_QUEUE_TEMPORARY_KEY = new Object();
+ private static final Object CREATE_QUEUE_QUEUES_KEY = new Object();
+ private static final Object CREATE_QUEUE_EXCHANGES_KEY = new Object();
+
+ private static final Object CREATE_QUEUE_EXCHANGES_TEMPORARY_KEY = new Object();
+ private static final Object CREATE_QUEUE_EXCHANGES_ROUTINGKEYS_KEY = new Object();
+
+ private static final int PUBLISH_EXCHANGES_KEY = 0;
+
+ private Map _permissions;
+
+ private String _user;
+
+
+ public PrincipalPermissions(String user)
+ {
+ _user = user;
+ _permissions = new ConcurrentHashMap();
+ }
+
+ /**
+ *
+ * @param permission the type of permission to check
+ *
+ * @param parameters vararg depending on what permission was passed in
+ * ACCESS: none
+ * BIND: none
+ * CONSUME: AMQShortString queueName, Boolean temporary, Boolean ownQueueOnly
+ * CREATEQUEUE: Boolean temporary, AMQShortString queueName, AMQShortString exchangeName, AMQShortString routingKey
+ * CREATEEXCHANGE: AMQShortString exchangeName, AMQShortString Class
+ * DELETE: none
+ * PUBLISH: Exchange exchange, AMQShortString routingKey
+ * PURGE: none
+ * UNBIND: none
+ */
+ public void grant(Permission permission, Object... parameters)
+ {
+ switch (permission)
+ {
+ case ACCESS:
+ break; // This is a no-op as the existence of this PrincipalPermission object is scoped per VHost for ACCESS
+ case BIND:
+ break; // All the details are currently included in the create setup.
+ case CONSUME: // Parameters : AMQShortString queueName, Boolean Temporary, Boolean ownQueueOnly
+ Map consumeRights = (Map) _permissions.get(permission);
+
+ if (consumeRights == null)
+ {
+ consumeRights = new ConcurrentHashMap();
+ _permissions.put(permission, consumeRights);
+ }
+
+ //if we have parametsre
+ if (parameters.length > 0)
+ {
+ AMQShortString queueName = (AMQShortString) parameters[0];
+ Boolean temporary = (Boolean) parameters[1];
+ Boolean ownQueueOnly = (Boolean) parameters[2];
+
+ if (temporary)
+ {
+ consumeRights.put(CONSUME_TEMPORARY_KEY, true);
+ }
+ else
+ {
+ consumeRights.put(CONSUME_TEMPORARY_KEY, false);
+ }
+
+ if (ownQueueOnly)
+ {
+ consumeRights.put(CONSUME_OWN_QUEUES_ONLY_KEY, true);
+ }
+ else
+ {
+ consumeRights.put(CONSUME_OWN_QUEUES_ONLY_KEY, false);
+ }
+
+
+ LinkedList queues = (LinkedList) consumeRights.get(CONSUME_QUEUES_KEY);
+ if (queues == null)
+ {
+ queues = new LinkedList();
+ consumeRights.put(CONSUME_QUEUES_KEY, queues);
+ }
+
+ if (queueName != null)
+ {
+ queues.add(queueName);
+ }
+ }
+
+
+ break;
+ case CREATEQUEUE: // Parameters : Boolean temporary, AMQShortString queueName
+ // , AMQShortString exchangeName , AMQShortString routingKey
+
+ Map createRights = (Map) _permissions.get(permission);
+
+ if (createRights == null)
+ {
+ createRights = new ConcurrentHashMap();
+ _permissions.put(permission, createRights);
+
+ }
+
+ //The existence of the empty map mean permission to all.
+ if (parameters.length == 0)
+ {
+ return;
+ }
+
+ Boolean temporary = (Boolean) parameters[0];
+
+ AMQShortString queueName = parameters.length > 1 ? (AMQShortString) parameters[1] : null;
+ AMQShortString exchangeName = parameters.length > 2 ? (AMQShortString) parameters[2] : null;
+ //Set the routingkey to the specified value or the queueName if present
+ AMQShortString routingKey = (parameters.length > 3 && null != parameters[3]) ? (AMQShortString) parameters[3] : queueName;
+
+ // Get the queues map
+ Map create_queues = (Map) createRights.get(CREATE_QUEUES_KEY);
+
+ if (create_queues == null)
+ {
+ create_queues = new ConcurrentHashMap();
+ createRights.put(CREATE_QUEUES_KEY, create_queues);
+ }
+
+ //Allow all temp queues to be created
+ create_queues.put(CREATE_QUEUE_TEMPORARY_KEY, temporary);
+
+ //Create empty list of queues
+ Map create_queues_queues = (Map) create_queues.get(CREATE_QUEUE_QUEUES_KEY);
+
+ if (create_queues_queues == null)
+ {
+ create_queues_queues = new ConcurrentHashMap();
+ create_queues.put(CREATE_QUEUE_QUEUES_KEY, create_queues_queues);
+ }
+
+ // We are granting CREATE rights to all temporary queues only
+ if (parameters.length == 1)
+ {
+ return;
+ }
+
+ // if we have a queueName then we need to store any associated exchange / rk bindings
+ if (queueName != null)
+ {
+ Map queue = (Map) create_queues_queues.get(queueName);
+ if (queue == null)
+ {
+ queue = new ConcurrentHashMap();
+ create_queues_queues.put(queueName, queue);
+ }
+
+ if (exchangeName != null)
+ {
+ queue.put(exchangeName, routingKey);
+ }
+
+ //If no exchange is specified then the presence of the queueName in the map says any exchange is ok
+ }
+
+ // Store the exchange that we are being granted rights to. This will be used as part of binding
+
+ //Lookup the list of exchanges
+ Map create_queues_exchanges = (Map) create_queues.get(CREATE_QUEUE_EXCHANGES_KEY);
+
+ if (create_queues_exchanges == null)
+ {
+ create_queues_exchanges = new ConcurrentHashMap();
+ create_queues.put(CREATE_QUEUE_EXCHANGES_KEY, create_queues_exchanges);
+ }
+
+ //if we have an exchange
+ if (exchangeName != null)
+ {
+ //Retrieve the list of permitted exchanges.
+ Map exchanges = (Map) create_queues_exchanges.get(exchangeName);
+
+ if (exchanges == null)
+ {
+ exchanges = new ConcurrentHashMap();
+ create_queues_exchanges.put(exchangeName, exchanges);
+ }
+
+ //Store the temporary setting CREATE_QUEUE_EXCHANGES_ROUTINGKEYS_KEY
+ exchanges.put(CREATE_QUEUE_EXCHANGES_TEMPORARY_KEY, temporary);
+
+ //Store the binding details of queue/rk for this exchange.
+ if (queueName != null)
+ {
+ //Retrieve the list of permitted routingKeys.
+ Map rKeys = (Map) exchanges.get(exchangeName);
+
+ if (rKeys == null)
+ {
+ rKeys = new ConcurrentHashMap();
+ exchanges.put(CREATE_QUEUE_EXCHANGES_ROUTINGKEYS_KEY, rKeys);
+ }
+
+ rKeys.put(queueName, routingKey);
+ }
+ }
+ break;
+ case CREATEEXCHANGE:
+ // Parameters AMQShortString exchangeName , AMQShortString Class
+ Map rights = (Map) _permissions.get(permission);
+ if (rights == null)
+ {
+ rights = new ConcurrentHashMap();
+ _permissions.put(permission, rights);
+ }
+
+ Map create_exchanges = (Map) rights.get(CREATE_EXCHANGES_KEY);
+ if (create_exchanges == null)
+ {
+ create_exchanges = new ConcurrentHashMap();
+ rights.put(CREATE_EXCHANGES_KEY, create_exchanges);
+ }
+
+ //Should perhaps error if parameters[0] is null;
+ AMQShortString name = parameters.length > 0 ? (AMQShortString) parameters[0] : null;
+ AMQShortString className = parameters.length > 1 ? (AMQShortString) parameters[1] : new AMQShortString("direct");
+
+ //Store the exchangeName / class mapping if the mapping is null
+ rights.put(name, className);
+ break;
+ case DELETE:
+ break;
+
+ case PUBLISH: // Parameters : Exchange exchange, AMQShortString routingKey
+ Map publishRights = (Map) _permissions.get(permission);
+
+ if (publishRights == null)
+ {
+ publishRights = new ConcurrentHashMap();
+ _permissions.put(permission, publishRights);
+ }
+
+ if (parameters == null || parameters.length == 0)
+ {
+ //If we have no parameters then allow publish to all destinations
+ // this is signified by having a null value for publish_exchanges
+ }
+ else
+ {
+ Map publish_exchanges = (Map) publishRights.get(PUBLISH_EXCHANGES_KEY);
+
+ if (publish_exchanges == null)
+ {
+ publish_exchanges = new ConcurrentHashMap();
+ publishRights.put(PUBLISH_EXCHANGES_KEY, publish_exchanges);
+ }
+
+
+ HashSet routingKeys = (HashSet) publish_exchanges.get(parameters[0]);
+
+ // Check to see if we have a routing key
+ if (parameters.length == 2)
+ {
+ if (routingKeys == null)
+ {
+ routingKeys = new HashSet<AMQShortString>();
+ }
+ //Add routing key to permitted publish destinations
+ routingKeys.add(parameters[1]);
+ }
+
+ // Add the updated routingkey list or null if all values allowed
+ publish_exchanges.put(parameters[0], routingKeys);
+ }
+ break;
+ case PURGE:
+ break;
+ case UNBIND:
+ break;
+ }
+
+ }
+
+ /**
+ *
+ * @param permission the type of permission to check
+ *
+ * @param parameters vararg depending on what permission was passed in
+ * ACCESS: none
+ * BIND: QueueBindBody bindmethod, Exchange exchange, AMQQueue queue, AMQShortString routingKey
+ * CONSUME: AMQQueue queue
+ * CREATEQUEUE: Boolean autodelete, AMQShortString name
+ * CREATEEXCHANGE: AMQShortString exchangeName
+ * DELETE: none
+ * PUBLISH: Exchange exchange, AMQShortString routingKey
+ * PURGE: none
+ * UNBIND: none
+ */
+ public AuthzResult authorise(Permission permission, Object... parameters)
+ {
+
+ switch (permission)
+ {
+ case ACCESS:
+ return AuthzResult.ALLOWED; // This is here for completeness but the SimpleXML ACLManager never calls it.
+ // The existence of this user specific PP can be validated in the map SimpleXML maintains.
+ case BIND: // Parameters : QueueBindMethod , Exchange , AMQQueue, AMQShortString routingKey
+
+ Exchange exchange = (Exchange) parameters[1];
+
+ AMQQueue bind_queueName = (AMQQueue) parameters[2];
+ AMQShortString routingKey = (AMQShortString) parameters[3];
+
+ //Get all Create Rights for this user
+ Map bindCreateRights = (Map) _permissions.get(Permission.CREATEQUEUE);
+
+ //Look up the Queue Creation Rights
+ Map bind_create_queues = (Map) bindCreateRights.get(CREATE_QUEUES_KEY);
+
+ //Lookup the list of queues
+ Map bind_create_queues_queues = (Map) bindCreateRights.get(CREATE_QUEUE_QUEUES_KEY);
+
+ // Check and see if we have a queue white list to check
+ if (bind_create_queues_queues != null)
+ {
+ //There a white list for queues
+ Map exchangeDetails = (Map) bind_create_queues_queues.get(bind_queueName);
+
+ if (exchangeDetails == null) //Then all queue can be bound to all exchanges.
+ {
+ return AuthzResult.ALLOWED;
+ }
+
+ // Check to see if we have a white list of routingkeys to check
+ Map rkeys = (Map) exchangeDetails.get(exchange.getName());
+
+ // if keys is null then any rkey is allowed on this exchange
+ if (rkeys == null)
+ {
+ // There is no routingkey white list
+ return AuthzResult.ALLOWED;
+ }
+ else
+ {
+ // We have routingKeys so a match must be found to allowed binding
+ Iterator keys = rkeys.keySet().iterator();
+
+ boolean matched = false;
+ while (keys.hasNext() && !matched)
+ {
+ AMQShortString rkey = (AMQShortString) keys.next();
+ if (rkey.endsWith("*"))
+ {
+ matched = routingKey.startsWith(rkey.subSequence(0, rkey.length() - 1).toString());
+ }
+ else
+ {
+ matched = routingKey.equals(rkey);
+ }
+ }
+
+
+ return (matched) ? AuthzResult.ALLOWED : AuthzResult.DENIED;
+ }
+
+
+ }
+ else
+ {
+ //There a is no white list for queues
+
+ // So can allow all queues to be bound
+ // but we should first check and see if we have a temp queue and validate that we are allowed
+ // to bind temp queues.
+
+ //Check to see if we have a temporary queue
+ if (bind_queueName.isAutoDelete())
+ {
+ // Check and see if we have an exchange white list.
+ Map bind_exchanges = (Map) bind_create_queues.get(CREATE_QUEUE_EXCHANGES_KEY);
+
+ // If the exchange exists then we must check to see if temporary queues are allowed here
+ if (bind_exchanges != null)
+ {
+ // Check to see if the requested exchange is allowed.
+ Map exchangeDetails = (Map) bind_exchanges.get(exchange.getName());
+
+ return ((Boolean) exchangeDetails.get(CREATE_QUEUE_EXCHANGES_TEMPORARY_KEY)) ? AuthzResult.ALLOWED : AuthzResult.DENIED;
+ }
+
+ //no white list so all allowed, drop through to return true below.
+ }
+
+ // not a temporary queue and no white list so all allowed.
+ return AuthzResult.ALLOWED;
+ }
+
+ case CREATEQUEUE:// Parameters : boolean autodelete, AMQShortString name
+
+ Map createRights = (Map) _permissions.get(permission);
+
+ // If there are no create rights then deny request
+ if (createRights == null)
+ {
+ return AuthzResult.DENIED;
+ }
+
+ //Look up the Queue Creation Rights
+ Map create_queues = (Map) createRights.get(CREATE_QUEUES_KEY);
+
+ //Lookup the list of queues allowed to be created
+ Map create_queues_queues = (Map) create_queues.get(CREATE_QUEUE_QUEUES_KEY);
+
+
+ AMQShortString queueName = (AMQShortString) parameters[1];
+ Boolean autoDelete = (Boolean) parameters[0];
+
+ if (autoDelete)// we have a temporary queue
+ {
+ return ((Boolean) create_queues.get(CREATE_QUEUE_TEMPORARY_KEY)) ? AuthzResult.ALLOWED : AuthzResult.DENIED;
+ }
+ else
+ {
+ // If there is a white list then check
+ if (create_queues_queues == null || create_queues_queues.containsKey(queueName))
+ {
+ return AuthzResult.ALLOWED;
+ }
+ else
+ {
+ return AuthzResult.DENIED;
+ }
+
+ }
+ case CREATEEXCHANGE:
+ Map rights = (Map) _permissions.get(permission);
+
+ AMQShortString exchangeName = (AMQShortString) parameters[0];
+
+ // If the exchange list is doesn't exist then all is allowed else
+ // check the valid exchanges
+ if (rights == null || rights.containsKey(exchangeName))
+ {
+ return AuthzResult.ALLOWED;
+ }
+ else
+ {
+ return AuthzResult.DENIED;
+ }
+ case CONSUME: // Parameters : AMQQueue
+
+ if (parameters.length == 1 && parameters[0] instanceof AMQQueue)
+ {
+ AMQQueue queue = ((AMQQueue) parameters[0]);
+ Map queuePermissions = (Map) _permissions.get(permission);
+
+ List queues = (List) queuePermissions.get(CONSUME_QUEUES_KEY);
+
+ Boolean temporayQueues = (Boolean) queuePermissions.get(CONSUME_TEMPORARY_KEY);
+ Boolean ownQueuesOnly = (Boolean) queuePermissions.get(CONSUME_OWN_QUEUES_ONLY_KEY);
+
+ // If user is allowed to publish to temporary queues and this is a temp queue then allow it.
+ if (temporayQueues)
+ {
+ if (queue.isAutoDelete())
+ // This will allow consumption from any temporary queue including ones not owned by this user.
+ // Of course the exclusivity will not be broken.
+ {
+ // if not limited to ownQueuesOnly then ok else check queue Owner.
+ return (!ownQueuesOnly || queue.getOwner().equals(_user)) ? AuthzResult.ALLOWED : AuthzResult.DENIED;
+ }
+ else
+ {
+ return AuthzResult.DENIED;
+ }
+ }
+
+ // if queues are white listed then ensure it is ok
+ if (queues != null)
+ {
+ // if no queues are listed then ALL are ok othereise it must be specified.
+ if (ownQueuesOnly)
+ {
+ if (queue.getOwner().equals(_user))
+ {
+ return (queues.size() == 0 || queues.contains(queue.getName())) ? AuthzResult.ALLOWED : AuthzResult.DENIED;
+ }
+ else
+ {
+ return AuthzResult.DENIED;
+ }
+ }
+
+ // If we are
+ return (queues.size() == 0 || queues.contains(queue.getName())) ? AuthzResult.ALLOWED : AuthzResult.DENIED;
+ }
+ }
+
+ // Can't authenticate without the right parameters
+ return AuthzResult.DENIED;
+ case DELETE:
+ break;
+
+ case PUBLISH: // Parameters : Exchange exchange, AMQShortString routingKey
+ Map publishRights = (Map) _permissions.get(permission);
+
+ if (publishRights == null)
+ {
+ return AuthzResult.DENIED;
+ }
+
+ Map exchanges = (Map) publishRights.get(PUBLISH_EXCHANGES_KEY);
+
+ // Having no exchanges listed gives full publish rights to all exchanges
+ if (exchanges == null)
+ {
+ return AuthzResult.ALLOWED;
+ }
+ // Otherwise exchange must be listed in the white list
+
+ // If the map doesn't have the exchange then it isn't allowed
+ if (!exchanges.containsKey(((Exchange) parameters[0]).getName()))
+ {
+ return AuthzResult.DENIED;
+ }
+ else
+ {
+
+ // Get valid routing keys
+ HashSet routingKeys = (HashSet) exchanges.get(((Exchange)parameters[0]).getName());
+
+ // Having no routingKeys in the map then all are allowed.
+ if (routingKeys == null)
+ {
+ return AuthzResult.ALLOWED;
+ }
+ else
+ {
+ // We have routingKeys so a match must be found to allowed binding
+ Iterator keys = routingKeys.iterator();
+
+
+ AMQShortString publishRKey = (AMQShortString)parameters[1];
+
+ boolean matched = false;
+ while (keys.hasNext() && !matched)
+ {
+ AMQShortString rkey = (AMQShortString) keys.next();
+
+ if (rkey.endsWith("*"))
+ {
+ matched = publishRKey.startsWith(rkey.subSequence(0, rkey.length() - 1));
+ }
+ else
+ {
+ matched = publishRKey.equals(rkey);
+ }
+ }
+ return (matched) ? AuthzResult.ALLOWED : AuthzResult.DENIED;
+ }
+ }
+ case PURGE:
+ break;
+ case UNBIND:
+ break;
+
+ }
+
+ return AuthzResult.DENIED;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/VirtualHostAccess.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/VirtualHostAccess.java
new file mode 100644
index 0000000000..13151a66b8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/VirtualHostAccess.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+public class VirtualHostAccess
+{
+ private String _vhost;
+ private AccessRights _rights;
+
+ public VirtualHostAccess(String vhostaccess)
+ {
+ //format <vhost>(<rights>)
+ int hostend = vhostaccess.indexOf('(');
+
+ if (hostend == -1)
+ {
+ throw new IllegalArgumentException("VirtualHostAccess format string contains no access _rights");
+ }
+
+ _vhost = vhostaccess.substring(0, hostend);
+
+ String rights = vhostaccess.substring(hostend);
+
+ if (rights.indexOf('r') != -1)
+ {
+ if (rights.indexOf('w') != -1)
+ {
+ _rights = new AccessRights(AccessRights.Rights.READWRITE);
+ }
+ else
+ {
+ _rights = new AccessRights(AccessRights.Rights.READ);
+ }
+ }
+ else if (rights.indexOf('w') != -1)
+ {
+ _rights = new AccessRights(AccessRights.Rights.WRITE);
+ }
+ }
+
+ public AccessRights getAccessRights()
+ {
+ return _rights;
+ }
+
+ public String getVirtualHost()
+ {
+ return _vhost;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBean.java
new file mode 100644
index 0000000000..121f571abe
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBean.java
@@ -0,0 +1,501 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access.management;
+
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.management.AMQManagedObject;
+import org.apache.qpid.server.management.MBeanOperation;
+import org.apache.qpid.server.management.MBeanInvocationHandlerImpl;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
+import org.apache.qpid.server.security.access.management.UserManagement;
+import org.apache.log4j.Logger;
+import org.apache.commons.configuration.ConfigurationException;
+
+import javax.management.JMException;
+import javax.management.remote.JMXPrincipal;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.security.auth.login.AccountNotFoundException;
+import javax.security.auth.Subject;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.FileOutputStream;
+import java.util.Properties;
+import java.util.List;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+import java.security.Principal;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+
+/** MBean class for AMQUserManagementMBean. It implements all the management features exposed for managing users. */
+@MBeanDescription("User Management Interface")
+public class AMQUserManagementMBean extends AMQManagedObject implements UserManagement
+{
+
+ private static final Logger _logger = Logger.getLogger(AMQUserManagementMBean.class);
+
+ private PrincipalDatabase _principalDatabase;
+ private Properties _accessRights;
+ private File _accessFile;
+
+ private ReentrantLock _accessRightsUpdate = new ReentrantLock();
+
+ // Setup for the TabularType
+ static TabularType _userlistDataType; // Datatype for representing User Lists
+
+ static CompositeType _userDataType; // Composite type for representing User
+ static String[] _userItemNames = {"Username", "read", "write", "admin"};
+
+ static
+ {
+ String[] userItemDesc = {"Broker Login username", "Management Console Read Permission",
+ "Management Console Write Permission", "Management Console Admin Permission"};
+
+ OpenType[] userItemTypes = new OpenType[4]; // User item types.
+ userItemTypes[0] = SimpleType.STRING; // For Username
+ userItemTypes[1] = SimpleType.BOOLEAN; // For Rights - Read
+ userItemTypes[2] = SimpleType.BOOLEAN; // For Rights - Write
+ userItemTypes[3] = SimpleType.BOOLEAN; // For Rights - Admin
+ String[] userDataIndex = {_userItemNames[0]};
+
+ try
+ {
+ _userDataType =
+ new CompositeType("User", "User Data", _userItemNames, userItemDesc, userItemTypes);
+
+ _userlistDataType = new TabularType("Users", "List of users", _userDataType, userDataIndex);
+ }
+ catch (OpenDataException e)
+ {
+ _logger.error("Tabular data setup for viewing users incorrect.");
+ _userlistDataType = null;
+ }
+ }
+
+
+ public AMQUserManagementMBean() throws JMException
+ {
+ super(UserManagement.class, UserManagement.TYPE, UserManagement.VERSION);
+ }
+
+ public String getObjectInstanceName()
+ {
+ return UserManagement.TYPE;
+ }
+
+ public boolean setPassword(String username, char[] password)
+ {
+ try
+ {
+ //delegate password changes to the Principal Database
+ return _principalDatabase.updatePassword(new UsernamePrincipal(username), password);
+ }
+ catch (AccountNotFoundException e)
+ {
+ _logger.warn("Attempt to set password of non-existant user'" + username + "'");
+ return false;
+ }
+ }
+
+ public boolean setRights(String username, boolean read, boolean write, boolean admin)
+ {
+
+ Object oldRights = null;
+ if ((oldRights =_accessRights.get(username)) == null)
+ {
+ // If the user doesn't exist in the access rights file check that they at least have an account.
+ if (_principalDatabase.getUser(username) == null)
+ {
+ return false;
+ }
+ }
+
+ try
+ {
+ _accessRightsUpdate.lock();
+
+ // Update the access rights
+ if (admin)
+ {
+ _accessRights.put(username, MBeanInvocationHandlerImpl.ADMIN);
+ }
+ else
+ {
+ if (read | write)
+ {
+ if (read)
+ {
+ _accessRights.put(username, MBeanInvocationHandlerImpl.READONLY);
+ }
+ if (write)
+ {
+ _accessRights.put(username, MBeanInvocationHandlerImpl.READWRITE);
+ }
+ }
+ else
+ {
+ _accessRights.remove(username);
+ }
+ }
+
+ //save the rights file
+ try
+ {
+ saveAccessFile();
+ }
+ catch (IOException e)
+ {
+ _logger.warn("Problem occured saving '" + _accessFile + "', the access right changes will not be preserved: " + e);
+
+ //the rights file was not successfully saved, restore user rights to previous value
+ _logger.warn("Reverting attempted rights update for user'" + username + "'");
+ if (oldRights != null)
+ {
+ _accessRights.put(username, oldRights);
+ }
+ else
+ {
+ _accessRights.remove(username);
+ }
+
+ return false;
+ }
+ }
+ finally
+ {
+ if (_accessRightsUpdate.isHeldByCurrentThread())
+ {
+ _accessRightsUpdate.unlock();
+ }
+ }
+
+ return true;
+ }
+
+ public boolean createUser(String username, char[] password, boolean read, boolean write, boolean admin)
+ {
+ if (_principalDatabase.createPrincipal(new UsernamePrincipal(username), password))
+ {
+ if (!setRights(username, read, write, admin))
+ {
+ //unable to set rights for user, remove account
+ try
+ {
+ _principalDatabase.deletePrincipal(new UsernamePrincipal(username));
+ }
+ catch (AccountNotFoundException e)
+ {
+ //ignore
+ }
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean deleteUser(String username)
+ {
+ try
+ {
+ if (_principalDatabase.deletePrincipal(new UsernamePrincipal(username)))
+ {
+ try
+ {
+ _accessRightsUpdate.lock();
+
+ _accessRights.remove(username);
+
+ try
+ {
+ saveAccessFile();
+ }
+ catch (IOException e)
+ {
+ _logger.warn("Problem occured saving '" + _accessFile + "', the access right changes will not be preserved: " + e);
+ return false;
+ }
+ }
+ finally
+ {
+ if (_accessRightsUpdate.isHeldByCurrentThread())
+ {
+ _accessRightsUpdate.unlock();
+ }
+ }
+ }
+ }
+ catch (AccountNotFoundException e)
+ {
+ _logger.warn("Attempt to delete user (" + username + ") that doesn't exist");
+ return false;
+ }
+
+ return true;
+ }
+
+ public boolean reloadData()
+ {
+ try
+ {
+ loadAccessFile();
+ _principalDatabase.reload();
+ }
+ catch (ConfigurationException e)
+ {
+ _logger.warn("Reload failed due to:" + e);
+ return false;
+ }
+ catch (IOException e)
+ {
+ _logger.warn("Reload failed due to:" + e);
+ return false;
+ }
+ // Reload successful
+ return true;
+ }
+
+
+ @MBeanOperation(name = "viewUsers", description = "All users with access rights to the system.")
+ public TabularData viewUsers()
+ {
+ // Table of users
+ // Username(string), Access rights Read,Write,Admin(bool,bool,bool)
+
+ if (_userlistDataType == null)
+ {
+ _logger.warn("TabluarData not setup correctly");
+ return null;
+ }
+
+ List<Principal> users = _principalDatabase.getUsers();
+
+ TabularDataSupport userList = new TabularDataSupport(_userlistDataType);
+
+ try
+ {
+ // Create the tabular list of message header contents
+ for (Principal user : users)
+ {
+ // Create header attributes list
+
+ String rights = (String) _accessRights.get(user.getName());
+
+ Boolean read = false;
+ Boolean write = false;
+ Boolean admin = false;
+
+ if (rights != null)
+ {
+ read = rights.equals(MBeanInvocationHandlerImpl.READONLY)
+ || rights.equals(MBeanInvocationHandlerImpl.READWRITE);
+ write = rights.equals(MBeanInvocationHandlerImpl.READWRITE);
+ admin = rights.equals(MBeanInvocationHandlerImpl.ADMIN);
+ }
+
+ Object[] itemData = {user.getName(), read, write, admin};
+ CompositeData messageData = new CompositeDataSupport(_userDataType, _userItemNames, itemData);
+ userList.put(messageData);
+ }
+ }
+ catch (OpenDataException e)
+ {
+ _logger.warn("Unable to create user list due to :" + e);
+ return null;
+ }
+
+ return userList;
+ }
+
+ /*** Broker Methods **/
+
+ /**
+ * setPrincipalDatabase
+ *
+ * @param database set The Database to use for user lookup
+ */
+ public void setPrincipalDatabase(PrincipalDatabase database)
+ {
+ _principalDatabase = database;
+ }
+
+ /**
+ * setAccessFile
+ *
+ * @param accessFile the file to use for updating.
+ *
+ * @throws java.io.IOException If the file cannot be accessed
+ * @throws org.apache.commons.configuration.ConfigurationException
+ * if checks on the file fail.
+ */
+ public void setAccessFile(String accessFile) throws IOException, ConfigurationException
+ {
+ if (accessFile != null)
+ {
+ _accessFile = new File(accessFile);
+ if (!_accessFile.exists())
+ {
+ throw new ConfigurationException("'" + _accessFile + "' does not exist");
+ }
+
+ if (!_accessFile.canRead())
+ {
+ throw new ConfigurationException("Cannot read '" + _accessFile + "'.");
+ }
+
+ if (!_accessFile.canWrite())
+ {
+ _logger.warn("Unable to write to access rights file '" + _accessFile + "', changes will not be preserved.");
+ }
+
+ loadAccessFile();
+ }
+ else
+ {
+ _logger.warn("Access rights file specified is null. Access rights not changed.");
+ }
+ }
+
+ private void loadAccessFile() throws IOException, ConfigurationException
+ {
+ if(_accessFile == null)
+ {
+ _logger.error("No jmx access rights file has been specified.");
+ return;
+ }
+
+ if(_accessFile.exists())
+ {
+ try
+ {
+ _accessRightsUpdate.lock();
+
+ Properties accessRights = new Properties();
+ accessRights.load(new FileInputStream(_accessFile));
+ checkAccessRights(accessRights);
+ setAccessRights(accessRights);
+ }
+ finally
+ {
+ if (_accessRightsUpdate.isHeldByCurrentThread())
+ {
+ _accessRightsUpdate.unlock();
+ }
+ }
+ }
+ else
+ {
+ _logger.error("Specified jmxaccess rights file '" + _accessFile + "' does not exist.");
+ }
+ }
+
+ private void checkAccessRights(Properties accessRights)
+ {
+ Enumeration values = accessRights.propertyNames();
+
+ while (values.hasMoreElements())
+ {
+ String user = (String) values.nextElement();
+
+ if (_principalDatabase.getUser(user) == null)
+ {
+ _logger.warn("Access rights contains user '" + user + "' but there is no authentication data for that user");
+ }
+ }
+ }
+
+ private void saveAccessFile() throws IOException
+ {
+ try
+ {
+ _accessRightsUpdate.lock();
+
+ // Create temporary file
+ File tmp = File.createTempFile(_accessFile.getName(), ".tmp");
+
+ FileOutputStream output = new FileOutputStream(tmp);
+ _accessRights.store(output, "Generated by AMQUserManagementMBean Console : Last edited by user:" + getCurrentJMXUser());
+ output.close();
+
+ // Rename new file to main file
+ tmp.renameTo(_accessFile);
+
+ // delete tmp
+ tmp.delete();
+ }
+ finally
+ {
+ if (_accessRightsUpdate.isHeldByCurrentThread())
+ {
+ _accessRightsUpdate.unlock();
+ }
+ }
+
+ }
+
+ private String getCurrentJMXUser()
+ {
+ AccessControlContext acc = AccessController.getContext();
+
+ Subject subject = Subject.getSubject(acc);
+ if (subject == null)
+ {
+ return "Unknown user, authentication Subject was null";
+ }
+
+ // Retrieve JMXPrincipal from Subject
+ Set<JMXPrincipal> principals = subject.getPrincipals(JMXPrincipal.class);
+ if (principals == null || principals.isEmpty())
+ {
+ return "Unknown user principals were null";
+ }
+
+ Principal principal = principals.iterator().next();
+ return principal.getName();
+ }
+
+ /**
+ * user=read user=write user=readwrite user=admin
+ *
+ * @param accessRights The properties list of access rights to process
+ */
+ private void setAccessRights(Properties accessRights)
+ {
+ _logger.debug("Setting Access Rights:" + accessRights);
+ _accessRights = accessRights;
+ MBeanInvocationHandlerImpl.setAccessRights(_accessRights);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/UserManagement.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/UserManagement.java
new file mode 100644
index 0000000000..9fcdd4cd17
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/UserManagement.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access.management;
+
+import org.apache.qpid.server.management.MBeanOperation;
+import org.apache.qpid.server.management.MBeanOperationParameter;
+import org.apache.qpid.server.management.MBeanAttribute;
+import org.apache.qpid.AMQException;
+
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.CompositeData;
+import javax.management.JMException;
+import javax.management.MBeanOperationInfo;
+import java.io.IOException;
+
+public interface UserManagement
+{
+
+ String TYPE = "UserManagement";
+ int VERSION = 2;
+
+ //********** Operations *****************//
+ /**
+ * set password for user
+ *
+ * @param username The username to create
+ * @param password The password for the user
+ *
+ * @return The result of the operation
+ */
+ @MBeanOperation(name = "setPassword", description = "Set password for user.",
+ impact = MBeanOperationInfo.ACTION)
+ boolean setPassword(@MBeanOperationParameter(name = "username", description = "Username")String username,
+ @MBeanOperationParameter(name = "password", description = "Password")char[] password);
+
+ /**
+ * set rights for users with given details
+ *
+ * @param username The username to create
+ * @param read The set of permission to give the new user
+ * @param write The set of permission to give the new user
+ * @param admin The set of permission to give the new user
+ *
+ * @return The result of the operation
+ */
+ @MBeanOperation(name = "setRights", description = "Set access rights for user.",
+ impact = MBeanOperationInfo.ACTION)
+ boolean setRights(@MBeanOperationParameter(name = "username", description = "Username")String username,
+ @MBeanOperationParameter(name = "read", description = "Administration read")boolean read,
+ @MBeanOperationParameter(name = "readAndWrite", description = "Administration write")boolean write,
+ @MBeanOperationParameter(name = "admin", description = "Administration rights")boolean admin);
+
+ /**
+ * Create users with given details
+ *
+ * @param username The username to create
+ * @param password The password for the user
+ * @param read The set of permission to give the new user
+ * @param write The set of permission to give the new user
+ * @param admin The set of permission to give the new user
+ *
+ * @return The result of the operation
+ */
+ @MBeanOperation(name = "createUser", description = "Create new user from system.",
+ impact = MBeanOperationInfo.ACTION)
+ boolean createUser(@MBeanOperationParameter(name = "username", description = "Username")String username,
+ @MBeanOperationParameter(name = "password", description = "Password")char[] password,
+ @MBeanOperationParameter(name = "read", description = "Administration read")boolean read,
+ @MBeanOperationParameter(name = "readAndWrite", description = "Administration write")boolean write,
+ @MBeanOperationParameter(name = "admin", description = "Administration rights")boolean admin);
+
+ /**
+ * View users returns all the users that are currently available to the system.
+ *
+ * @param username The user to delete
+ *
+ * @return The result of the operation
+ */
+ @MBeanOperation(name = "deleteUser", description = "Delete user from system.",
+ impact = MBeanOperationInfo.ACTION)
+ boolean deleteUser(@MBeanOperationParameter(name = "username", description = "Username")String username);
+
+
+ /**
+ * Reload the date from disk
+ *
+ * @return The result of the operation
+ */
+ @MBeanOperation(name = "reloadData", description = "Reload the authentication file from disk.",
+ impact = MBeanOperationInfo.ACTION)
+ boolean reloadData();
+
+ /**
+ * View users returns all the users that are currently available to the system.
+ *
+ * @return a table of users data (Username, read, write, admin)
+ */
+ @MBeanOperation(name = "viewUsers", description = "All users with access rights to the system.",
+ impact = MBeanOperationInfo.INFO)
+ TabularData viewUsers();
+
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AbstractACLPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AbstractACLPlugin.java
new file mode 100644
index 0000000000..682135bc25
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AbstractACLPlugin.java
@@ -0,0 +1,99 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access.plugins;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+/**
+ * This ACLPlugin abstains from all votes. Useful if your plugin only cares about a few operations.
+ */
+public abstract class AbstractACLPlugin implements ACLPlugin
+{
+
+ private static final AuthzResult DEFAULT_ANSWER = AuthzResult.ABSTAIN;
+
+ public AuthzResult authoriseBind(AMQProtocolSession session, Exchange exch, AMQQueue queue,
+ AMQShortString routingKey)
+ {
+ return DEFAULT_ANSWER;
+ }
+
+ public AuthzResult authoriseConnect(AMQProtocolSession session, VirtualHost virtualHost)
+ {
+ return DEFAULT_ANSWER;
+ }
+
+ public AuthzResult authoriseConsume(AMQProtocolSession session, boolean noAck, AMQQueue queue)
+ {
+ return DEFAULT_ANSWER;
+ }
+
+ public AuthzResult authoriseConsume(AMQProtocolSession session, boolean exclusive, boolean noAck, boolean noLocal,
+ boolean nowait, AMQQueue queue)
+ {
+ return DEFAULT_ANSWER;
+ }
+
+ public AuthzResult authoriseCreateExchange(AMQProtocolSession session, boolean autoDelete, boolean durable,
+ AMQShortString exchangeName, boolean internal, boolean nowait, boolean passive, AMQShortString exchangeType)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public AuthzResult authoriseCreateQueue(AMQProtocolSession session, boolean autoDelete, boolean durable,
+ boolean exclusive, boolean nowait, boolean passive, AMQShortString queue)
+ {
+ return DEFAULT_ANSWER;
+ }
+
+ public AuthzResult authoriseDelete(AMQProtocolSession session, AMQQueue queue)
+ {
+ return DEFAULT_ANSWER;
+ }
+
+ public AuthzResult authoriseDelete(AMQProtocolSession session, Exchange exchange)
+ {
+ return DEFAULT_ANSWER;
+ }
+
+ public AuthzResult authorisePublish(AMQProtocolSession session, boolean immediate, boolean mandatory,
+ AMQShortString routingKey, Exchange e)
+ {
+ return DEFAULT_ANSWER;
+ }
+
+ public AuthzResult authorisePurge(AMQProtocolSession session, AMQQueue queue)
+ {
+ return DEFAULT_ANSWER;
+ }
+
+ public AuthzResult authoriseUnbind(AMQProtocolSession session, Exchange exch, AMQShortString routingKey,
+ AMQQueue queue)
+ {
+ return DEFAULT_ANSWER;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java
new file mode 100644
index 0000000000..4af178574b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access.plugins;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.ACLPluginFactory;
+
+public class AllowAll extends BasicACLPlugin
+{
+
+ public static final ACLPluginFactory FACTORY = new ACLPluginFactory()
+ {
+ public boolean supportsTag(String name)
+ {
+ return false;
+ }
+
+ public ACLPlugin newInstance(Configuration config)
+ {
+ return new AllowAll();
+ }
+ };
+
+ public String getPluginName()
+ {
+ return this.getClass().getSimpleName();
+ }
+
+ @Override
+ protected AuthzResult getResult()
+ {
+ // Always allow
+ return AuthzResult.ALLOWED;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/BasicACLPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/BasicACLPlugin.java
new file mode 100644
index 0000000000..f7e537b02b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/BasicACLPlugin.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access.plugins;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public abstract class BasicACLPlugin implements ACLPlugin
+{
+
+ // Returns true or false if the plugin should authorise or deny the request
+ protected abstract AuthzResult getResult();
+
+ @Override
+ public AuthzResult authoriseBind(AMQProtocolSession session, Exchange exch,
+ AMQQueue queue, AMQShortString routingKey)
+ {
+ return getResult();
+ }
+
+ @Override
+ public AuthzResult authoriseConnect(AMQProtocolSession session,
+ VirtualHost virtualHost)
+ {
+ return getResult();
+ }
+
+ @Override
+ public AuthzResult authoriseConsume(AMQProtocolSession session, boolean noAck,
+ AMQQueue queue)
+ {
+ return getResult();
+ }
+
+ @Override
+ public AuthzResult authoriseConsume(AMQProtocolSession session,
+ boolean exclusive, boolean noAck, boolean noLocal, boolean nowait,
+ AMQQueue queue)
+ {
+ return getResult();
+ }
+
+ @Override
+ public AuthzResult authoriseCreateExchange(AMQProtocolSession session,
+ boolean autoDelete, boolean durable, AMQShortString exchangeName,
+ boolean internal, boolean nowait, boolean passive,
+ AMQShortString exchangeType)
+ {
+ return getResult();
+ }
+
+ @Override
+ public AuthzResult authoriseCreateQueue(AMQProtocolSession session,
+ boolean autoDelete, boolean durable, boolean exclusive,
+ boolean nowait, boolean passive, AMQShortString queue)
+ {
+ return getResult();
+ }
+
+ @Override
+ public AuthzResult authoriseDelete(AMQProtocolSession session, AMQQueue queue)
+ {
+ return getResult();
+ }
+
+ @Override
+ public AuthzResult authoriseDelete(AMQProtocolSession session, Exchange exchange)
+ {
+ return getResult();
+ }
+
+ @Override
+ public AuthzResult authorisePublish(AMQProtocolSession session,
+ boolean immediate, boolean mandatory, AMQShortString routingKey,
+ Exchange e)
+ {
+ return getResult();
+ }
+
+ @Override
+ public AuthzResult authorisePurge(AMQProtocolSession session, AMQQueue queue)
+ {
+ return getResult();
+ }
+
+ @Override
+ public AuthzResult authoriseUnbind(AMQProtocolSession session, Exchange exch,
+ AMQShortString routingKey, AMQQueue queue)
+ {
+ return getResult();
+ }
+
+ @Override
+ public void setConfiguration(Configuration config)
+ {
+ // no-op
+ }
+
+ public boolean supportsTag(String name)
+ {
+ // This plugin doesn't support any tags
+ return false;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/DenyAll.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/DenyAll.java
new file mode 100644
index 0000000000..26a76c9af1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/DenyAll.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access.plugins;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.security.access.ACLManager;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.ACLPluginFactory;
+import org.apache.qpid.server.security.access.AccessResult;
+import org.apache.qpid.server.security.access.Permission;
+
+public class DenyAll extends BasicACLPlugin
+{
+ public static final ACLPluginFactory FACTORY = new ACLPluginFactory()
+ {
+ public boolean supportsTag(String name)
+ {
+ return false;
+ }
+
+ public ACLPlugin newInstance(Configuration config)
+ {
+ return new DenyAll();
+ }
+ };
+
+ public AccessResult authorise(AMQProtocolSession session,
+ Permission permission, AMQMethodBody body, Object... parameters)
+ throws AMQConnectionException
+ {
+
+ if (ACLManager.getLogger().isInfoEnabled())
+ {
+ ACLManager.getLogger().info(
+ "Denying user:" + session.getAuthorizedID());
+ }
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED,
+ "DenyAll Plugin");
+ }
+
+ public String getPluginName()
+ {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ protected AuthzResult getResult()
+ {
+ // Always deny
+ return AuthzResult.DENIED;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/LegacyAccessPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/LegacyAccessPlugin.java
new file mode 100644
index 0000000000..fc1bc048d4
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/LegacyAccessPlugin.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access.plugins;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.ACLPluginFactory;
+
+/**
+ *
+ * Used to suppress warnings in legacy config files that have things in <security> which aren't handled by a plugin directly.
+ *
+ */
+public class LegacyAccessPlugin extends BasicACLPlugin
+{
+ public static final ACLPluginFactory FACTORY = new ACLPluginFactory()
+ {
+ private Collection maskedTags = new HashSet<String>();
+ {
+ maskedTags.add("principal-databases");
+ maskedTags.add("access");
+ maskedTags.add("msg-auth");
+ maskedTags.add("false");
+ maskedTags.add("jmx");
+ }
+
+ public boolean supportsTag(String name)
+ {
+ return maskedTags .contains(name);
+ }
+
+ public ACLPlugin newInstance(Configuration config)
+ {
+ return new LegacyAccessPlugin();
+ }
+ };
+
+ public String getPluginName()
+ {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ protected AuthzResult getResult()
+ {
+ // Always abstain
+ return AuthzResult.ABSTAIN;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java
new file mode 100644
index 0000000000..2cc0c530de
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java
@@ -0,0 +1,432 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+
+package org.apache.qpid.server.security.access.plugins;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicConsumeBody;
+import org.apache.qpid.framing.BasicPublishBody;
+
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.security.access.ACLManager;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.ACLPluginFactory;
+import org.apache.qpid.server.security.access.AccessResult;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.server.security.access.PrincipalPermissions;
+import org.apache.qpid.server.security.access.ACLPlugin.AuthzResult;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This uses the default
+ */
+public class SimpleXML implements ACLPlugin
+{
+ public static final ACLPluginFactory FACTORY = new ACLPluginFactory()
+ {
+ public boolean supportsTag(String name)
+ {
+ return name.startsWith("access_control_list");
+ }
+
+ public ACLPlugin newInstance(Configuration config)
+ {
+ SimpleXML plugin = new SimpleXML();
+ plugin.setConfiguration(config);
+ return plugin;
+ }
+ };
+
+ private Map<String, PrincipalPermissions> _users;
+ private final AccessResult GRANTED = new AccessResult(this, AccessResult.AccessStatus.GRANTED);
+
+ public SimpleXML()
+ {
+ _users = new ConcurrentHashMap<String, PrincipalPermissions>();
+ }
+
+ public void setConfiguration(Configuration config)
+ {
+ processConfig(config);
+ }
+
+ private void processConfig(Configuration config)
+ {
+ processPublish(config);
+
+ processConsume(config);
+
+ processCreate(config);
+ }
+
+ /**
+ * Publish format takes Exchange + Routing Key Pairs
+ *
+ * @param config
+ * XML Configuration
+ */
+ private void processPublish(Configuration config)
+ {
+ Configuration publishConfig = config.subset("access_control_list.publish");
+
+ // Process users that have full publish permission
+ String[] users = publishConfig.getStringArray("users.user");
+
+ for (String user : users)
+ {
+ grant(Permission.PUBLISH, user);
+ }
+
+ // Process exchange limited users
+ int exchangeCount = 0;
+ Configuration exchangeConfig = publishConfig.subset("exchanges.exchange(" + exchangeCount + ")");
+
+ while (!exchangeConfig.isEmpty())
+ {
+ // Get Exchange Name
+ AMQShortString exchangeName = new AMQShortString(exchangeConfig.getString("name"));
+
+ // Get Routing Keys
+ int keyCount = 0;
+ Configuration routingkeyConfig = exchangeConfig.subset("routing_keys.routing_key(" + keyCount + ")");
+
+ while (!routingkeyConfig.isEmpty())
+ {
+ // Get RoutingKey Value
+ AMQShortString routingKeyValue = new AMQShortString(routingkeyConfig.getString("value"));
+
+ // Apply Exchange + RoutingKey permissions to Users
+ users = routingkeyConfig.getStringArray("users.user");
+ for (String user : users)
+ {
+ grant(Permission.PUBLISH, user, exchangeName, routingKeyValue);
+ }
+
+ // Apply permissions to Groups
+
+ // Check for more configs
+ keyCount++;
+ routingkeyConfig = exchangeConfig.subset("routing_keys.routing_key(" + keyCount + ")");
+ }
+
+ // Apply Exchange wide permissions to Users
+ users = exchangeConfig.getStringArray("exchange(" + exchangeCount + ").users.user");
+
+ for (String user : users)
+ {
+ grant(Permission.PUBLISH, user, exchangeName);
+ }
+
+ // Apply permissions to Groups
+ exchangeCount++;
+ exchangeConfig = publishConfig.subset("exchanges.exchange(" + exchangeCount + ")");
+ }
+ }
+
+ private void grant(Permission permission, String user, Object... parameters)
+ {
+ PrincipalPermissions permissions = _users.get(user);
+
+ if (permissions == null)
+ {
+ permissions = new PrincipalPermissions(user);
+ }
+
+ _users.put(user, permissions);
+ permissions.grant(permission, parameters);
+ }
+
+ private void processConsume(Configuration config)
+ {
+ Configuration consumeConfig = config.subset("access_control_list.consume");
+
+ // Process queue limited users
+ int queueCount = 0;
+ Configuration queueConfig = consumeConfig.subset("queues.queue(" + queueCount + ")");
+
+ while (!queueConfig.isEmpty())
+ {
+ // Get queue Name
+ AMQShortString queueName = new AMQShortString(queueConfig.getString("name"));
+ // if there is no name then there may be a temporary element
+ boolean temporary = queueConfig.containsKey("temporary");
+ boolean ownQueues = queueConfig.containsKey("own_queues");
+
+ // Process permissions for this queue
+ String[] users = queueConfig.getStringArray("users.user");
+ for (String user : users)
+ {
+ grant(Permission.CONSUME, user, queueName, temporary, ownQueues);
+ }
+
+ // See if we have another config
+ queueCount++;
+ queueConfig = consumeConfig.subset("queues.queue(" + queueCount + ")");
+ }
+
+ // Process users that have full consume permission
+ String[] users = consumeConfig.getStringArray("users.user");
+
+ for (String user : users)
+ {
+ grant(Permission.CONSUME, user);
+ }
+ }
+
+ private void processCreate(Configuration config)
+ {
+ Configuration createConfig = config.subset("access_control_list.create");
+
+ // Process create permissions for queue creation
+ int queueCount = 0;
+ Configuration queueConfig = createConfig.subset("queues.queue(" + queueCount + ")");
+
+ while (!queueConfig.isEmpty())
+ {
+ // Get queue Name
+ AMQShortString queueName = new AMQShortString(queueConfig.getString("name"));
+
+ // if there is no name then there may be a temporary element
+ boolean temporary = queueConfig.containsKey("temporary");
+
+ int exchangeCount = 0;
+ Configuration exchangeConfig = queueConfig.subset("exchanges.exchange(" + exchangeCount + ")");
+
+ while (!exchangeConfig.isEmpty())
+ {
+
+ AMQShortString exchange = new AMQShortString(exchangeConfig.getString("name"));
+ AMQShortString routingKey = new AMQShortString(exchangeConfig.getString("routing_key"));
+
+ // Process permissions for this queue
+ String[] users = exchangeConfig.getStringArray("users.user");
+ for (String user : users)
+ {
+ grant(Permission.CREATEEXCHANGE, user, exchange);
+ grant(Permission.CREATEQUEUE, user, temporary, (queueName.equals("") ? null : queueName), (exchange
+ .equals("") ? null : exchange), (routingKey.equals("") ? null : routingKey));
+ }
+
+ // See if we have another config
+ exchangeCount++;
+ exchangeConfig = queueConfig.subset("exchanges.exchange(" + exchangeCount + ")");
+ }
+
+ // Process users that are not bound to an exchange
+ String[] users = queueConfig.getStringArray("users.user");
+
+ for (String user : users)
+ {
+ grant(Permission.CREATEQUEUE, user, temporary, queueName);
+ }
+
+ // See if we have another config
+ queueCount++;
+ queueConfig = createConfig.subset("queues.queue(" + queueCount + ")");
+ }
+
+ // Process create permissions for exchange creation
+ int exchangeCount = 0;
+ Configuration exchangeConfig = createConfig.subset("exchanges.exchange(" + exchangeCount + ")");
+
+ while (!exchangeConfig.isEmpty())
+ {
+ AMQShortString exchange = new AMQShortString(exchangeConfig.getString("name"));
+ AMQShortString clazz = new AMQShortString(exchangeConfig.getString("class"));
+
+ // Process permissions for this queue
+ String[] users = exchangeConfig.getStringArray("users.user");
+ for (String user : users)
+ {
+ grant(Permission.CREATEEXCHANGE, user, exchange, clazz);
+ }
+
+ // See if we have another config
+ exchangeCount++;
+ exchangeConfig = queueConfig.subset("exchanges.exchange(" + exchangeCount + ")");
+ }
+
+ // Process users that have full create permission
+ String[] users = createConfig.getStringArray("users.user");
+
+ for (String user : users)
+ {
+ grant(Permission.CREATEEXCHANGE, user);
+ grant(Permission.CREATEQUEUE, user);
+ }
+
+ }
+
+ public String getPluginName()
+ {
+ return "Simple";
+ }
+
+ public AuthzResult authoriseBind(AMQProtocolSession session, Exchange exch, AMQQueue queue, AMQShortString routingKey)
+ {
+ PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName());
+ if (principalPermissions == null)
+ {
+ return AuthzResult.DENIED;
+ }
+ else
+ {
+ return principalPermissions.authorise(Permission.BIND, null, exch, queue, routingKey);
+ }
+ }
+
+ public AuthzResult authoriseConnect(AMQProtocolSession session, VirtualHost virtualHost)
+ {
+ PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName());
+ if (principalPermissions == null)
+ {
+ return AuthzResult.DENIED;
+ }
+ else
+ {
+ return principalPermissions.authorise(Permission.ACCESS);
+ }
+ }
+
+ public AuthzResult authoriseConsume(AMQProtocolSession session, boolean noAck, AMQQueue queue)
+ {
+ PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName());
+ if (principalPermissions == null)
+ {
+ return AuthzResult.DENIED;
+ }
+ else
+ {
+ return principalPermissions.authorise(Permission.CONSUME, queue);
+ }
+ }
+
+ public AuthzResult authoriseConsume(AMQProtocolSession session, boolean exclusive, boolean noAck, boolean noLocal,
+ boolean nowait, AMQQueue queue)
+ {
+ return authoriseConsume(session, noAck, queue);
+ }
+
+ public AuthzResult authoriseCreateExchange(AMQProtocolSession session, boolean autoDelete, boolean durable,
+ AMQShortString exchangeName, boolean internal, boolean nowait, boolean passive, AMQShortString exchangeType)
+ {
+ PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName());
+ if (principalPermissions == null)
+ {
+ return AuthzResult.DENIED;
+ }
+ else
+ {
+ return principalPermissions.authorise(Permission.CREATEEXCHANGE, exchangeName);
+ }
+ }
+
+ public AuthzResult authoriseCreateQueue(AMQProtocolSession session, boolean autoDelete, boolean durable, boolean exclusive,
+ boolean nowait, boolean passive, AMQShortString queue)
+ {
+ PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName());
+ if (principalPermissions == null)
+ {
+ return AuthzResult.DENIED;
+ }
+ else
+ {
+ return principalPermissions.authorise(Permission.CREATEQUEUE, autoDelete, queue);
+ }
+ }
+
+ public AuthzResult authoriseDelete(AMQProtocolSession session, AMQQueue queue)
+ {
+ PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName());
+ if (principalPermissions == null)
+ {
+ return AuthzResult.DENIED;
+ }
+ else
+ {
+ return principalPermissions.authorise(Permission.DELETE);
+ }
+ }
+
+ public AuthzResult authoriseDelete(AMQProtocolSession session, Exchange exchange)
+ {
+ PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName());
+ if (principalPermissions == null)
+ {
+ return AuthzResult.DENIED;
+ }
+ else
+ {
+ return principalPermissions.authorise(Permission.DELETE);
+ }
+ }
+
+ public AuthzResult authorisePublish(AMQProtocolSession session, boolean immediate, boolean mandatory,
+ AMQShortString routingKey, Exchange e)
+ {
+ PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName());
+ if (principalPermissions == null)
+ {
+ return AuthzResult.DENIED;
+ }
+ else
+ {
+ return principalPermissions.authorise(Permission.PUBLISH, e, routingKey);
+ }
+ }
+
+ public AuthzResult authorisePurge(AMQProtocolSession session, AMQQueue queue)
+ {
+ PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName());
+ if (principalPermissions == null)
+ {
+ return AuthzResult.DENIED;
+ }
+ else
+ {
+ return principalPermissions.authorise(Permission.PURGE);
+ }
+ }
+
+ public AuthzResult authoriseUnbind(AMQProtocolSession session, Exchange exch, AMQShortString routingKey, AMQQueue queue)
+ {
+ PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName());
+ if (principalPermissions == null)
+ {
+ return AuthzResult.DENIED;
+ }
+ else
+ {
+ return principalPermissions.authorise(Permission.UNBIND);
+ }
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallFactory.java
new file mode 100644
index 0000000000..a1a399e5bf
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallFactory.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access.plugins.network;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.ACLPluginFactory;
+
+public class FirewallFactory implements ACLPluginFactory
+{
+
+ @Override
+ public ACLPlugin newInstance(Configuration config) throws ConfigurationException
+ {
+ FirewallPlugin plugin = new FirewallPlugin();
+ plugin.setConfiguration(config);
+ return plugin;
+ }
+
+ @Override
+ public boolean supportsTag(String name)
+ {
+ return name.equals("firewall");
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallPlugin.java
new file mode 100644
index 0000000000..85026121ab
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallPlugin.java
@@ -0,0 +1,264 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access.plugins.network;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Pattern;
+
+import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.qpid.server.protocol.AMQMinaProtocolSession;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.ACLPluginFactory;
+import org.apache.qpid.server.security.access.plugins.AbstractACLPlugin;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.util.NetMatcher;
+
+public class FirewallPlugin extends AbstractACLPlugin
+{
+
+ public class FirewallPluginException extends Exception {}
+
+ public static final ACLPluginFactory FACTORY = new ACLPluginFactory()
+ {
+ public boolean supportsTag(String name)
+ {
+ return name.startsWith("firewall");
+ }
+
+ public ACLPlugin newInstance(Configuration config) throws ConfigurationException
+ {
+ FirewallPlugin plugin = new FirewallPlugin();
+ plugin.setConfiguration(config);
+ return plugin;
+ }
+ };
+
+ public class FirewallRule
+ {
+
+ private static final long DNS_TIMEOUT = 30000;
+ private AuthzResult _access;
+ private NetMatcher _network;
+ private Pattern[] _hostnamePatterns;
+
+ public FirewallRule(String access, List networks, List hostnames)
+ {
+ _access = (access.equals("allow")) ? AuthzResult.ALLOWED : AuthzResult.DENIED;
+
+ if (networks != null && networks.size() > 0)
+ {
+ String[] networkStrings = objListToStringArray(networks);
+ _network = new NetMatcher(networkStrings);
+ }
+
+ if (hostnames != null && hostnames.size() > 0)
+ {
+ int i = 0;
+ _hostnamePatterns = new Pattern[hostnames.size()];
+ for (String hostname : objListToStringArray(hostnames))
+ {
+ _hostnamePatterns[i++] = Pattern.compile(hostname);
+ }
+ }
+
+ }
+
+ private String[] objListToStringArray(List objList)
+ {
+ String[] networkStrings = new String[objList.size()];
+ int i = 0;
+ for (Object network : objList)
+ {
+ networkStrings[i++] = (String) network;
+ }
+ return networkStrings;
+ }
+
+ public boolean match(InetAddress remote) throws FirewallPluginException
+ {
+ if (_hostnamePatterns != null)
+ {
+ String hostname = getHostname(remote);
+ if (hostname == null)
+ {
+ throw new FirewallPluginException();
+ }
+ for (Pattern pattern : _hostnamePatterns)
+ {
+ if (pattern.matcher(hostname).matches())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ else
+ {
+ return _network.matchInetNetwork(remote);
+ }
+ }
+
+ /**
+ * @param remote the InetAddress to look up
+ * @return the hostname, null if not found or takes longer than 30s to find
+ */
+ private String getHostname(final InetAddress remote)
+ {
+ final String[] hostname = new String[]{null};
+ final AtomicBoolean done = new AtomicBoolean(false);
+ // Spawn thread
+ Thread thread = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ hostname[0] = remote.getCanonicalHostName();
+ done.getAndSet(true);
+ synchronized (done)
+ {
+ done.notifyAll();
+ }
+ }
+ });
+
+ thread.run();
+ long endTime = System.currentTimeMillis() + DNS_TIMEOUT;
+
+ while (System.currentTimeMillis() < endTime && !done.get())
+ {
+ try
+ {
+ synchronized (done)
+ {
+ done.wait(endTime - System.currentTimeMillis());
+ }
+ }
+ catch (InterruptedException e)
+ {
+ // Check the time and if necessary sleep for a bit longer
+ }
+ }
+ return hostname[0];
+ }
+
+ public AuthzResult getAccess()
+ {
+ return _access;
+ }
+
+ }
+
+ private AuthzResult _default = AuthzResult.ABSTAIN;
+ private FirewallRule[] _rules;
+
+ @Override
+ public AuthzResult authoriseConnect(AMQProtocolSession session, VirtualHost virtualHost)
+ {
+ if (!(session instanceof AMQMinaProtocolSession))
+ {
+ return AuthzResult.ABSTAIN; // We only deal with tcp sessions, which
+ // mean MINA right now
+ }
+
+ InetAddress addr = getInetAdressFromMinaSession((AMQMinaProtocolSession) session);
+
+ if (addr == null)
+ {
+ return AuthzResult.ABSTAIN; // Not an Inet socket on the other end
+ }
+
+ boolean match = false;
+ for (FirewallRule rule : _rules)
+ {
+ try
+ {
+ match = rule.match(addr);
+ }
+ catch (FirewallPluginException e)
+ {
+ return AuthzResult.DENIED;
+ }
+ if (match)
+ {
+ return rule.getAccess();
+ }
+ }
+ return _default;
+
+ }
+
+ private InetAddress getInetAdressFromMinaSession(AMQMinaProtocolSession session)
+ {
+ SocketAddress remote = session.getIOSession().getRemoteAddress();
+ if (remote instanceof InetSocketAddress)
+ {
+ return ((InetSocketAddress) remote).getAddress();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ @Override
+ public void setConfiguration(Configuration config) throws ConfigurationException
+ {
+ // Get default action
+ String defaultAction = config.getString("[@default-action]");
+ if (defaultAction == null)
+ {
+ _default = AuthzResult.ABSTAIN;
+ }
+ else if (defaultAction.toLowerCase().equals("allow"))
+ {
+ _default = AuthzResult.ALLOWED;
+ }
+ else
+ {
+ _default = AuthzResult.DENIED;
+ }
+ CompositeConfiguration finalConfig = new CompositeConfiguration(config);
+
+ List subFiles = config.getList("firewall.xml[@fileName]");
+ for (Object subFile : subFiles)
+ {
+ finalConfig.addConfiguration(new XMLConfiguration((String) subFile));
+ }
+
+ // all rules must have an access attribute
+ int numRules = finalConfig.getList("rule[@access]").size();
+ _rules = new FirewallRule[numRules];
+ for (int i = 0; i < numRules; i++)
+ {
+ FirewallRule rule = new FirewallRule(finalConfig.getString("rule(" + i + ")[@access]"), finalConfig.getList("rule("
+ + i + ")[@network]"), finalConfig.getList("rule(" + i + ")[@hostname]"));
+ _rules[i] = rule;
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java
new file mode 100644
index 0000000000..3f846b9dd0
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth;
+
+import javax.security.sasl.SaslException;
+
+public class AuthenticationResult
+{
+ public enum AuthenticationStatus
+ {
+ SUCCESS, CONTINUE, ERROR
+ }
+
+ public AuthenticationStatus status;
+ public byte[] challenge;
+
+ private Exception cause;
+
+ public AuthenticationResult(AuthenticationStatus status)
+ {
+ this(null, status, null);
+ }
+
+ public AuthenticationResult(byte[] challenge, AuthenticationStatus status)
+ {
+ this(challenge, status, null);
+ }
+
+ public AuthenticationResult(AuthenticationStatus error, Exception cause)
+ {
+ this(null, error, cause);
+ }
+
+ public AuthenticationResult(byte[] challenge, AuthenticationStatus status, Exception cause)
+ {
+ this.status = status;
+ this.challenge = challenge;
+ this.cause = cause;
+ }
+
+ public Exception getCause()
+ {
+ return cause;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java
new file mode 100644
index 0000000000..3c211746e3
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java
@@ -0,0 +1,541 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.server.security.access.management.AMQUserManagementMBean;
+import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser;
+import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
+import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HashedInitialiser;
+
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.AccountNotFoundException;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.security.Principal;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+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 Base64MD5PasswordFilePrincipalDatabase implements PrincipalDatabase
+{
+ private static final Logger _logger = Logger.getLogger(Base64MD5PasswordFilePrincipalDatabase.class);
+
+ private File _passwordFile;
+
+ private Pattern _regexp = Pattern.compile(":");
+
+ private Map<String, AuthenticationProviderInitialiser> _saslServers;
+
+ AMQUserManagementMBean _mbean;
+ public static final String DEFAULT_ENCODING = "utf-8";
+ private Map<String, HashedUser> _users = new HashMap<String, HashedUser>();
+ private ReentrantLock _userUpdate = new ReentrantLock();
+
+ public Base64MD5PasswordFilePrincipalDatabase()
+ {
+ _saslServers = new HashMap<String, AuthenticationProviderInitialiser>();
+
+ /**
+ * Create Authenticators for MD5 Password file.
+ */
+
+ // Accept Plain incomming and hash it for comparison to the file.
+ CRAMMD5HashedInitialiser cram = new CRAMMD5HashedInitialiser();
+ cram.initialise(this);
+ _saslServers.put(cram.getMechanismName(), cram);
+
+ //fixme The PDs should setup a PD Mangement MBean
+// try
+// {
+// _mbean = new AMQUserManagementMBean();
+// _mbean.setPrincipalDatabase(this);
+// }
+// catch (JMException e)
+// {
+// _logger.warn("User management disabled as unable to create MBean:" + e);
+// }
+ }
+
+ public void setPasswordFile(String passwordFile) throws IOException
+ {
+ 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.");
+ }
+
+ loadPasswordFile();
+ }
+
+ /**
+ * SASL Callback Mechanism - sets the Password in the PasswordCallback based on the value in the PasswordFile
+ * If you want to change the password for a user, use updatePassword instead.
+ *
+ * @param principal The Principal to set the password for
+ * @param callback The PasswordCallback to call setPassword on
+ *
+ * @throws AccountNotFoundException If the Principal cannont be found in this Database
+ */
+ public void setPassword(Principal principal, PasswordCallback callback) throws 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);
+ }
+ }
+
+ /**
+ * Used to verify that the presented Password is correct. Currently only used by Management Console
+ *
+ * @param principal The principal to authenticate
+ * @param password The password to check
+ *
+ * @return true if password is correct
+ *
+ * @throws AccountNotFoundException if the principal cannot be found
+ */
+ public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException
+ {
+ char[] pwd = lookupPassword(principal);
+
+ if (pwd == null)
+ {
+ throw new AccountNotFoundException("Unable to lookup the specfied users password");
+ }
+
+ byte[] byteArray = new byte[password.length];
+ int index = 0;
+ for (char c : password)
+ {
+ byteArray[index++] = (byte) c;
+ }
+
+ byte[] MD5byteArray;
+ try
+ {
+ MD5byteArray = HashedUser.getMD5(byteArray);
+ }
+ catch (Exception e1)
+ {
+ _logger.warn("Unable to hash password for user '" + principal + "' for comparison");
+ return false;
+ }
+
+ char[] hashedPassword = new char[MD5byteArray.length];
+
+ index = 0;
+ for (byte c : MD5byteArray)
+ {
+ hashedPassword[index++] = (char) c;
+ }
+
+ return compareCharArray(pwd, hashedPassword);
+ }
+
+ private boolean compareCharArray(char[] a, char[] b)
+ {
+ boolean equal = false;
+ if (a.length == b.length)
+ {
+ equal = true;
+ int index = 0;
+ while (equal && index < a.length)
+ {
+ equal = a[index] == b[index];
+ index++;
+ }
+ }
+ return equal;
+ }
+
+ /**
+ * Changes the password for the specified user
+ *
+ * @param principal to change the password for
+ * @param password plaintext password to set the password too
+ */
+ public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException
+ {
+ HashedUser user = _users.get(principal.getName());
+
+ if (user == null)
+ {
+ throw new AccountNotFoundException(principal.getName());
+ }
+
+ try
+ {
+ try
+ {
+ _userUpdate.lock();
+ char[] orig = user.getPassword();
+ user.setPassword(password,false);
+
+ try
+ {
+ savePasswordFile();
+ }
+ catch (IOException e)
+ {
+ _logger.error("Unable to save password file, password change for user'"
+ + principal + "' will revert at restart");
+ //revert the password change
+ user.setPassword(orig,true);
+ return false;
+ }
+ return true;
+ }
+ finally
+ {
+ if (_userUpdate.isHeldByCurrentThread())
+ {
+ _userUpdate.unlock();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ return false;
+ }
+ }
+
+ public boolean createPrincipal(Principal principal, char[] password)
+ {
+ if (_users.get(principal.getName()) != null)
+ {
+ return false;
+ }
+
+ HashedUser user;
+ try
+ {
+ user = new HashedUser(principal.getName(), password);
+ }
+ catch (Exception e1)
+ {
+ _logger.warn("Unable to create new user '" + principal.getName() + "'");
+ return false;
+ }
+
+
+ try
+ {
+ _userUpdate.lock();
+ _users.put(user.getName(), user);
+
+ try
+ {
+ savePasswordFile();
+ return true;
+ }
+ catch (IOException e)
+ {
+ //remove the use on failure.
+ _users.remove(user.getName());
+ return false;
+ }
+ }
+ finally
+ {
+ if (_userUpdate.isHeldByCurrentThread())
+ {
+ _userUpdate.unlock();
+ }
+ }
+ }
+
+ public boolean deletePrincipal(Principal principal) throws AccountNotFoundException
+ {
+ HashedUser user = _users.get(principal.getName());
+
+ if (user == null)
+ {
+ throw new AccountNotFoundException(principal.getName());
+ }
+
+ try
+ {
+ _userUpdate.lock();
+ user.delete();
+
+ try
+ {
+ savePasswordFile();
+ }
+ catch (IOException e)
+ {
+ _logger.warn("Unable to remove user '" + user.getName() + "' from password file.");
+ return false;
+ }
+
+ _users.remove(user.getName());
+ }
+ finally
+ {
+ if (_userUpdate.isHeldByCurrentThread())
+ {
+ _userUpdate.unlock();
+ }
+ }
+
+ return true;
+ }
+
+ public Map<String, AuthenticationProviderInitialiser> getMechanisms()
+ {
+ return _saslServers;
+ }
+
+ public List<Principal> getUsers()
+ {
+ return new LinkedList<Principal>(_users.values());
+ }
+
+ public Principal getUser(String username)
+ {
+ if (_users.containsKey(username))
+ {
+ return new UsernamePrincipal(username);
+ }
+ return null;
+ }
+
+ /**
+ * 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 The principal name to lookup
+ *
+ * @return a char[] for use in SASL.
+ */
+ private char[] lookupPassword(String name)
+ {
+ HashedUser user = _users.get(name);
+ if (user == null)
+ {
+ return null;
+ }
+ else
+ {
+ return user.getPassword();
+ }
+ }
+
+ private void loadPasswordFile() throws IOException
+ {
+ try
+ {
+ _userUpdate.lock();
+ _users.clear();
+
+ 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 || result[0].startsWith("#"))
+ {
+ continue;
+ }
+
+ HashedUser user = new HashedUser(result);
+ _logger.info("Created user:" + user);
+ _users.put(user.getName(), user);
+ }
+ }
+ finally
+ {
+ if (reader != null)
+ {
+ reader.close();
+ }
+ }
+ }
+ finally
+ {
+ if (_userUpdate.isHeldByCurrentThread())
+ {
+ _userUpdate.unlock();
+ }
+ }
+ }
+
+ private void savePasswordFile() throws IOException
+ {
+ try
+ {
+ _userUpdate.lock();
+
+ BufferedReader reader = null;
+ PrintStream writer = null;
+ File tmp = File.createTempFile(_passwordFile.getName(), ".tmp");
+
+ try
+ {
+ writer = new PrintStream(tmp);
+ reader = new BufferedReader(new FileReader(_passwordFile));
+ String line;
+
+ while ((line = reader.readLine()) != null)
+ {
+ String[] result = _regexp.split(line);
+ if (result == null || result.length < 2 || result[0].startsWith("#"))
+ {
+ writer.write(line.getBytes(DEFAULT_ENCODING));
+ writer.println();
+ continue;
+ }
+
+ HashedUser user = _users.get(result[0]);
+
+ if (user == null)
+ {
+ writer.write(line.getBytes(DEFAULT_ENCODING));
+ writer.println();
+ }
+ else if (!user.isDeleted())
+ {
+ if (!user.isModified())
+ {
+ writer.write(line.getBytes(DEFAULT_ENCODING));
+ writer.println();
+ }
+ else
+ {
+ try
+ {
+ byte[] encodedPassword = user.getEncodedPassword();
+
+ writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING));
+ writer.write(encodedPassword);
+ writer.println();
+
+ user.saved();
+ }
+ catch (Exception e)
+ {
+ _logger.warn("Unable to encode new password reverting to old password.");
+ writer.write(line.getBytes(DEFAULT_ENCODING));
+ writer.println();
+ }
+ }
+ }
+ }
+
+ for (HashedUser user : _users.values())
+ {
+ if (user.isModified())
+ {
+ byte[] encodedPassword;
+ try
+ {
+ encodedPassword = user.getEncodedPassword();
+ writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING));
+ writer.write(encodedPassword);
+ writer.println();
+ user.saved();
+ }
+ catch (Exception e)
+ {
+ _logger.warn("Unable to get Encoded password for user'" + user.getName() + "' password not saved");
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (reader != null)
+ {
+ reader.close();
+ }
+
+ if (writer != null)
+ {
+ writer.close();
+ }
+
+ // Swap temp file to main password file.
+ File old = new File(_passwordFile.getAbsoluteFile() + ".old");
+ if (old.exists())
+ {
+ old.delete();
+ }
+ _passwordFile.renameTo(old);
+ tmp.renameTo(_passwordFile);
+ tmp.delete();
+ }
+ }
+ finally
+ {
+ if (_userUpdate.isHeldByCurrentThread())
+ {
+ _userUpdate.unlock();
+ }
+ }
+ }
+
+ public void reload() throws IOException
+ {
+ loadPasswordFile();
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java
new file mode 100644
index 0000000000..e0d4c49af1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java
@@ -0,0 +1,232 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.configuration.PropertyUtils;
+import org.apache.qpid.configuration.PropertyException;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager;
+import org.apache.qpid.server.security.access.management.AMQUserManagementMBean;
+import org.apache.qpid.AMQException;
+
+import javax.management.JMException;
+
+public class ConfigurationFilePrincipalDatabaseManager implements PrincipalDatabaseManager
+{
+ private static final Logger _logger = Logger.getLogger(ConfigurationFilePrincipalDatabaseManager.class);
+
+ Map<String, PrincipalDatabase> _databases;
+
+ public ConfigurationFilePrincipalDatabaseManager(ServerConfiguration _configuration) throws Exception
+ {
+ _logger.info("Initialising PrincipleDatabase authentication manager");
+ _databases = initialisePrincipalDatabases(_configuration);
+ }
+
+ private Map<String, PrincipalDatabase> initialisePrincipalDatabases(ServerConfiguration _configuration) throws Exception
+ {
+ List<String> databaseNames = _configuration.getPrincipalDatabaseNames();
+ List<String> databaseClasses = _configuration.getPrincipalDatabaseClass();
+ Map<String, PrincipalDatabase> databases = new HashMap<String, PrincipalDatabase>();
+
+ if (databaseNames.size() == 0)
+ {
+ _logger.warn("No Principal databases specified. Broker running with NO AUTHENTICATION");
+ }
+
+ 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, _configuration, 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 not permitted");
+ }
+
+ _logger.info("Initialised principal database '" + name + "' successfully");
+ databases.put(name, (PrincipalDatabase) o);
+ }
+
+ return databases;
+ }
+
+ private void initialisePrincipalDatabase(PrincipalDatabase principalDatabase, ServerConfiguration _configuration, int index)
+ throws FileNotFoundException, ConfigurationException
+ {
+ List<String> argumentNames = _configuration.getPrincipalDatabaseAttributeNames(index);
+ List<String> argumentValues = _configuration.getPrincipalDatabaseAttributeValues(index);
+ for (int i = 0; i < argumentNames.size(); i++)
+ {
+ String argName = argumentNames.get(i);
+ if ((argName == null) || (argName.length() == 0))
+ {
+ throw new ConfigurationException("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 = null;
+ try
+ {
+ method = principalDatabase.getClass().getMethod(methodName, String.class);
+ }
+ catch (Exception e)
+ {
+ // do nothing.. as on error method will be null
+ }
+
+ if (method == null)
+ {
+ throw new ConfigurationException("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");
+ }
+
+ try
+ {
+ method.invoke(principalDatabase, PropertyUtils.replaceProperties(argumentValues.get(i)));
+ }
+ catch (Exception ite)
+ {
+ if (ite instanceof ConfigurationException)
+ {
+ throw(ConfigurationException) ite;
+ }
+ else
+ {
+ throw new ConfigurationException(ite.getMessage(), ite);
+ }
+ }
+ }
+ }
+
+ public Map<String, PrincipalDatabase> getDatabases()
+ {
+ return _databases;
+ }
+
+ public void initialiseManagement(ServerConfiguration config) throws ConfigurationException
+ {
+ try
+ {
+ AMQUserManagementMBean _mbean = new AMQUserManagementMBean();
+
+ List<String> principalDBs = config.getManagementPrincipalDBs();
+
+ if (principalDBs.size() == 0)
+ {
+ throw new ConfigurationException("No principal-database specified for jmx security");
+ }
+
+ String databaseName = principalDBs.get(0);
+
+ PrincipalDatabase database = getDatabases().get(databaseName);
+
+ if (database == null)
+ {
+ throw new ConfigurationException("Principal-database '" + databaseName + "' not found");
+ }
+
+ _mbean.setPrincipalDatabase(database);
+
+ List<String> jmxaccesslist = config.getManagementAccessList();
+
+ if (jmxaccesslist.size() == 0)
+ {
+ throw new ConfigurationException("No access control files specified for jmx security");
+ }
+
+ String jmxaccesssFile = null;
+
+ try
+ {
+ jmxaccesssFile = PropertyUtils.replaceProperties(jmxaccesslist.get(0));
+ }
+ catch (PropertyException e)
+ {
+ throw new ConfigurationException("Unable to parse access control filename '" + jmxaccesssFile + "'");
+ }
+
+ try
+ {
+ _mbean.setAccessFile(jmxaccesssFile);
+ }
+ catch (IOException e)
+ {
+ _logger.warn("Unable to load access file:" + jmxaccesssFile);
+ }
+
+ try
+ {
+ _mbean.register();
+ }
+ catch (AMQException e)
+ {
+ _logger.warn("Unable to register user management MBean");
+ }
+ }
+ catch (JMException e)
+ {
+ _logger.warn("User management disabled as unable to create MBean:" + e);
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java
new file mode 100644
index 0000000000..3690e7f92a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java
@@ -0,0 +1,169 @@
+/*
+*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.log4j.Logger;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+
+public class HashedUser implements Principal
+{
+ private static final Logger _logger = Logger.getLogger(HashedUser.class);
+
+ String _name;
+ char[] _password;
+ byte[] _encodedPassword = null;
+ private boolean _modified = false;
+ private boolean _deleted = false;
+
+ HashedUser(String[] data) throws UnsupportedEncodingException
+ {
+ if (data.length != 2)
+ {
+ throw new IllegalArgumentException("User Data should be length 2, username, password");
+ }
+
+ _name = data[0];
+
+ byte[] encoded_password = data[1].getBytes(Base64MD5PasswordFilePrincipalDatabase.DEFAULT_ENCODING);
+
+ Base64 b64 = new Base64();
+ byte[] decoded = b64.decode(encoded_password);
+
+ _encodedPassword = encoded_password;
+
+ _password = new char[decoded.length];
+
+ int index = 0;
+ for (byte c : decoded)
+ {
+ _password[index++] = (char) c;
+ }
+ }
+
+ public HashedUser(String name, char[] password) throws UnsupportedEncodingException, NoSuchAlgorithmException
+ {
+ _name = name;
+ setPassword(password,false);
+ }
+
+ public static byte[] getMD5(byte[] data) throws NoSuchAlgorithmException, UnsupportedEncodingException
+ {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+
+ for (byte b : data)
+ {
+ md.update(b);
+ }
+
+ return md.digest();
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public String toString()
+ {
+ return _name;
+ }
+
+ char[] getPassword()
+ {
+ return _password;
+ }
+
+ void setPassword(char[] password, boolean alreadyHashed) throws UnsupportedEncodingException, NoSuchAlgorithmException
+ {
+ if(alreadyHashed){
+ _password = password;
+ }
+ else
+ {
+ byte[] byteArray = new byte[password.length];
+ int index = 0;
+ for (char c : password)
+ {
+ byteArray[index++] = (byte) c;
+ }
+
+ byte[] MD5byteArray = getMD5(byteArray);
+
+ _password = new char[MD5byteArray.length];
+
+ index = 0;
+ for (byte c : MD5byteArray)
+ {
+ _password[index++] = (char) c;
+ }
+ }
+
+ _modified = true;
+ _encodedPassword = null;
+ }
+
+ byte[] getEncodedPassword() throws EncoderException, UnsupportedEncodingException, NoSuchAlgorithmException
+ {
+ if (_encodedPassword == null)
+ {
+ encodePassword();
+ }
+ return _encodedPassword;
+ }
+
+ private void encodePassword() throws EncoderException, UnsupportedEncodingException, NoSuchAlgorithmException
+ {
+ byte[] byteArray = new byte[_password.length];
+ int index = 0;
+ for (char c : _password)
+ {
+ byteArray[index++] = (byte) c;
+ }
+ _encodedPassword = (new Base64()).encode(byteArray);
+ }
+
+ public boolean isModified()
+ {
+ return _modified;
+ }
+
+ public boolean isDeleted()
+ {
+ return _deleted;
+ }
+
+ public void delete()
+ {
+ _deleted = true;
+ }
+
+ public void saved()
+ {
+ _modified = false;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java
new file mode 100644
index 0000000000..5e4678a63b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java
@@ -0,0 +1,491 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser;
+import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
+import org.apache.qpid.server.security.auth.sasl.amqplain.AmqPlainInitialiser;
+import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5Initialiser;
+import org.apache.qpid.server.security.auth.sasl.plain.PlainInitialiser;
+
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.AccountNotFoundException;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.security.Principal;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+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 PlainPasswordFilePrincipalDatabase implements PrincipalDatabase
+{
+ public static final String DEFAULT_ENCODING = "utf-8";
+
+ private static final Logger _logger = Logger.getLogger(PlainPasswordFilePrincipalDatabase.class);
+
+ private File _passwordFile;
+
+ private Pattern _regexp = Pattern.compile(":");
+
+ private Map<String, AuthenticationProviderInitialiser> _saslServers;
+
+ private Map<String, PlainUser> _users = new HashMap<String, PlainUser>();
+ private ReentrantLock _userUpdate = new ReentrantLock();
+
+ public PlainPasswordFilePrincipalDatabase()
+ {
+ _saslServers = new HashMap<String, AuthenticationProviderInitialiser>();
+
+ /**
+ * Create Authenticators for Plain Password file.
+ */
+
+ // Accept AMQPlain incomming and compare it to the file.
+ AmqPlainInitialiser amqplain = new AmqPlainInitialiser();
+ amqplain.initialise(this);
+
+ // Accept Plain incomming and compare it to the file.
+ PlainInitialiser plain = new PlainInitialiser();
+ plain.initialise(this);
+
+ // Accept MD5 incomming and Hash file value for comparison
+ CRAMMD5Initialiser cram = new CRAMMD5Initialiser();
+ cram.initialise(this);
+
+ _saslServers.put(amqplain.getMechanismName(), amqplain);
+ _saslServers.put(plain.getMechanismName(), plain);
+ _saslServers.put(cram.getMechanismName(), cram);
+ }
+
+ public void setPasswordFile(String passwordFile) throws IOException
+ {
+ File f = new File(passwordFile);
+ _logger.info("PlainPasswordFile 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.");
+ }
+
+ loadPasswordFile();
+ }
+
+ /**
+ * SASL Callback Mechanism - sets the Password in the PasswordCallback based on the value in the PasswordFile
+ * If you want to change the password for a user, use updatePassword instead.
+ *
+ * @param principal The Principal to set the password for
+ * @param callback The PasswordCallback to call setPassword on
+ *
+ * @throws AccountNotFoundException If the Principal cannot be found in this Database
+ */
+ public void setPassword(Principal principal, PasswordCallback callback) throws 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);
+ }
+ }
+
+ /**
+ * Used to verify that the presented Password is correct. Currently only used by Management Console
+ *
+ * @param principal The principal to authenticate
+ * @param password The plaintext password to check
+ *
+ * @return true if password is correct
+ *
+ * @throws AccountNotFoundException if the principal cannot be found
+ */
+ public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException
+ {
+
+ char[] pwd = lookupPassword(principal);
+
+ if (pwd == null)
+ {
+ throw new AccountNotFoundException("Unable to lookup the specfied users password");
+ }
+
+ return compareCharArray(pwd, password);
+
+ }
+
+ /**
+ * Changes the password for the specified user
+ *
+ * @param principal to change the password for
+ * @param password plaintext password to set the password too
+ */
+ public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException
+ {
+ PlainUser user = _users.get(principal.getName());
+
+ if (user == null)
+ {
+ throw new AccountNotFoundException(principal.getName());
+ }
+
+ try
+ {
+ try
+ {
+ _userUpdate.lock();
+ char[] orig = user.getPassword();
+ user.setPassword(password);
+
+ try
+ {
+ savePasswordFile();
+ }
+ catch (IOException e)
+ {
+ _logger.error("Unable to save password file, password change for user '" + principal + "' discarded");
+ //revert the password change
+ user.setPassword(orig);
+ return false;
+ }
+ return true;
+ }
+ finally
+ {
+ if (_userUpdate.isHeldByCurrentThread())
+ {
+ _userUpdate.unlock();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ return false;
+ }
+ }
+
+ public boolean createPrincipal(Principal principal, char[] password)
+ {
+ if (_users.get(principal.getName()) != null)
+ {
+ return false;
+ }
+
+ PlainUser user = new PlainUser(principal.getName(), password);
+
+ try
+ {
+ _userUpdate.lock();
+ _users.put(user.getName(), user);
+
+ try
+ {
+ savePasswordFile();
+ return true;
+ }
+ catch (IOException e)
+ {
+ //remove the use on failure.
+ _users.remove(user.getName());
+ _logger.warn("Unable to create user '" + user.getName());
+ return false;
+ }
+ }
+ finally
+ {
+ if (_userUpdate.isHeldByCurrentThread())
+ {
+ _userUpdate.unlock();
+ }
+ }
+ }
+
+ public boolean deletePrincipal(Principal principal) throws AccountNotFoundException
+ {
+ PlainUser user = _users.get(principal.getName());
+
+ if (user == null)
+ {
+ throw new AccountNotFoundException(principal.getName());
+ }
+
+ try
+ {
+ _userUpdate.lock();
+ user.delete();
+
+ try
+ {
+ savePasswordFile();
+ }
+ catch (IOException e)
+ {
+ _logger.error("Unable to remove user '" + user.getName() + "' from password file.");
+ return false;
+ }
+
+ _users.remove(user.getName());
+ }
+ finally
+ {
+ if (_userUpdate.isHeldByCurrentThread())
+ {
+ _userUpdate.unlock();
+ }
+ }
+
+ return true;
+ }
+
+ public Map<String, AuthenticationProviderInitialiser> getMechanisms()
+ {
+ return _saslServers;
+ }
+
+ public List<Principal> getUsers()
+ {
+ return new LinkedList<Principal>(_users.values());
+ }
+
+ public Principal getUser(String username)
+ {
+ if (_users.containsKey(username))
+ {
+ return new UsernamePrincipal(username);
+ }
+ return null;
+ }
+
+ private boolean compareCharArray(char[] a, char[] b)
+ {
+ boolean equal = false;
+ if (a.length == b.length)
+ {
+ equal = true;
+ int index = 0;
+ while (equal && index < a.length)
+ {
+ equal = a[index] == b[index];
+ index++;
+ }
+ }
+ return equal;
+ }
+
+
+ /**
+ * 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 The principal name to lookup
+ *
+ * @return a char[] for use in SASL.
+ */
+ private char[] lookupPassword(String name)
+ {
+ PlainUser user = _users.get(name);
+ if (user == null)
+ {
+ return null;
+ }
+ else
+ {
+ return user.getPassword();
+ }
+ }
+
+ private void loadPasswordFile() throws IOException
+ {
+ try
+ {
+ _userUpdate.lock();
+ _users.clear();
+
+ 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 || result[0].startsWith("#"))
+ {
+ continue;
+ }
+
+ PlainUser user = new PlainUser(result);
+ _logger.info("Created user:" + user);
+ _users.put(user.getName(), user);
+ }
+ }
+ finally
+ {
+ if (reader != null)
+ {
+ reader.close();
+ }
+ }
+ }
+ finally
+ {
+ if (_userUpdate.isHeldByCurrentThread())
+ {
+ _userUpdate.unlock();
+ }
+ }
+ }
+
+ private void savePasswordFile() throws IOException
+ {
+ try
+ {
+ _userUpdate.lock();
+
+ BufferedReader reader = null;
+ PrintStream writer = null;
+ File tmp = File.createTempFile(_passwordFile.getName(), ".tmp");
+
+ try
+ {
+ writer = new PrintStream(tmp);
+ reader = new BufferedReader(new FileReader(_passwordFile));
+ String line;
+
+ while ((line = reader.readLine()) != null)
+ {
+ String[] result = _regexp.split(line);
+ if (result == null || result.length < 2 || result[0].startsWith("#"))
+ {
+ writer.write(line.getBytes(DEFAULT_ENCODING));
+ writer.println();
+ continue;
+ }
+
+ PlainUser user = _users.get(result[0]);
+
+ if (user == null)
+ {
+ writer.write(line.getBytes(DEFAULT_ENCODING));
+ writer.println();
+ }
+ else if (!user.isDeleted())
+ {
+ if (!user.isModified())
+ {
+ writer.write(line.getBytes(DEFAULT_ENCODING));
+ writer.println();
+ }
+ else
+ {
+ byte[] password = user.getPasswordBytes();
+
+ writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING));
+ writer.write(password);
+ writer.println();
+
+ user.saved();
+ }
+ }
+ }
+
+ for (PlainUser user : _users.values())
+ {
+ if (user.isModified())
+ {
+ byte[] password;
+ password = user.getPasswordBytes();
+ writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING));
+ writer.write(password);
+ writer.println();
+ user.saved();
+ }
+ }
+ }
+ finally
+ {
+ if (reader != null)
+ {
+ reader.close();
+ }
+
+ if (writer != null)
+ {
+ writer.close();
+ }
+
+ // Swap temp file to main password file.
+ File old = new File(_passwordFile.getAbsoluteFile() + ".old");
+ if (old.exists())
+ {
+ old.delete();
+ }
+ _passwordFile.renameTo(old);
+ tmp.renameTo(_passwordFile);
+ tmp.delete();
+ }
+ }
+ finally
+ {
+ if (_userUpdate.isHeldByCurrentThread())
+ {
+ _userUpdate.unlock();
+ }
+ }
+ }
+
+ public void reload() throws IOException
+ {
+ loadPasswordFile();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java
new file mode 100644
index 0000000000..46a78a55aa
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java
@@ -0,0 +1,106 @@
+/*
+*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import org.apache.log4j.Logger;
+
+import java.security.Principal;
+
+public class PlainUser implements Principal
+{
+ private String _name;
+ private char[] _password;
+ private boolean _modified = false;
+ private boolean _deleted = false;
+
+ PlainUser(String[] data)
+ {
+ if (data.length != 2)
+ {
+ throw new IllegalArgumentException("User Data should be length 2, username, password");
+ }
+
+ _name = data[0];
+
+ _password = data[1].toCharArray();
+
+ }
+
+ public PlainUser(String name, char[] password)
+ {
+ _name = name;
+ _password = password;
+ _modified = true;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public String toString()
+ {
+ return _name;
+ }
+
+ char[] getPassword()
+ {
+ return _password;
+ }
+
+ byte[] getPasswordBytes()
+ {
+ byte[] byteArray = new byte[_password.length];
+ int index = 0;
+ for (char c : _password)
+ {
+ byteArray[index++] = (byte) c;
+ }
+ return byteArray;
+ }
+
+ void setPassword(char[] password)
+ {
+ _password = password;
+ _modified = true;
+ }
+
+ public boolean isModified()
+ {
+ return _modified;
+ }
+
+ public boolean isDeleted()
+ {
+ return _deleted;
+ }
+
+ public void delete()
+ {
+ _deleted = true;
+ }
+
+ public void saved()
+ {
+ _modified = false;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java
new file mode 100644
index 0000000000..ef37e043a6
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java
@@ -0,0 +1,105 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Map;
+import java.util.List;
+
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.AccountNotFoundException;
+
+/** 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;
+
+ /**
+ * Used to verify that the presented Password is correct. Currently only used by Management Console
+ * @param principal The principal to authenticate
+ * @param password The password to check
+ * @return true if password is correct
+ * @throws AccountNotFoundException if the principal cannot be found
+ */
+ boolean verifyPassword(String principal, char[] password)
+ throws AccountNotFoundException;
+
+ /**
+ * Update(Change) the password for the given principal
+ * @param principal Who's password is to be changed
+ * @param password The new password to use
+ * @return True if change was successful
+ * @throws AccountNotFoundException If the given principal doesn't exist in the Database
+ */
+ boolean updatePassword(Principal principal, char[] password)
+ throws AccountNotFoundException;
+
+ /**
+ * Create a new principal in the database
+ * @param principal The principal to create
+ * @param password The password to set for the principal
+ * @return True on a successful creation
+ */
+ boolean createPrincipal(Principal principal, char[] password);
+
+ /**
+ * Delete a principal
+ * @param principal The principal to delete
+ * @return True on a successful creation
+ * @throws AccountNotFoundException If the given principal doesn't exist in the Database
+ */
+ boolean deletePrincipal(Principal principal)
+ throws AccountNotFoundException;
+
+ /**
+ * Get the principal from the database with the given username
+ * @param username of the principal to lookup
+ * @return The Principal object for the given username or null if not found.
+ */
+ Principal getUser(String username);
+
+ /**
+ * Reload the database to its ensure contents are up to date
+ * @throws IOException If there was an error reloading the database
+ */
+ void reload() throws IOException;
+
+ public Map<String, AuthenticationProviderInitialiser> getMechanisms();
+
+
+ List<Principal> getUsers();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java
new file mode 100644
index 0000000000..f9882f8810
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+
+import java.util.Map;
+
+public interface PrincipalDatabaseManager
+{
+ public Map<String, PrincipalDatabase> getDatabases();
+
+ public void initialiseManagement(ServerConfiguration _configuration) throws ConfigurationException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java
new file mode 100644
index 0000000000..ff8851306f
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser;
+import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
+import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5Initialiser;
+import org.apache.qpid.server.security.auth.sasl.plain.PlainInitialiser;
+
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.AccountNotFoundException;
+import java.util.Properties;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.LinkedList;
+import java.security.Principal;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+public class PropertiesPrincipalDatabase implements PrincipalDatabase
+{
+ private Properties _users;
+
+ private Map<String, AuthenticationProviderInitialiser> _saslServers;
+
+ public PropertiesPrincipalDatabase(Properties users)
+ {
+ _users = users;
+
+ _saslServers = new HashMap<String, AuthenticationProviderInitialiser>();
+
+ /**
+ * Create Authenticators for Properties Principal Database.
+ */
+
+ // Accept MD5 incomming and use plain comparison with the file
+ PlainInitialiser cram = new PlainInitialiser();
+ cram.initialise(this);
+ // Accept Plain incomming and hash it for comparison to the file.
+ CRAMMD5Initialiser plain = new CRAMMD5Initialiser();
+ plain.initialise(this, CRAMMD5Initialiser.HashDirection.INCOMMING);
+
+ _saslServers.put(plain.getMechanismName(), cram);
+ _saslServers.put(cram.getMechanismName(), plain);
+ }
+
+ public void setPassword(Principal principal, PasswordCallback callback) throws IOException, AccountNotFoundException
+ {
+ if (principal == null)
+ {
+ throw new IllegalArgumentException("principal must not be null");
+ }
+
+
+
+ final String pwd = _users.getProperty(principal.getName());
+
+ if (pwd != null)
+ {
+ callback.setPassword(pwd.toCharArray());
+ }
+ else
+ {
+ throw new AccountNotFoundException("No account found for principal " + principal);
+ }
+ }
+
+ public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException
+ {
+ //fixme this is not correct as toCharArray is not safe based on the type of string.
+ char[] pwd = _users.getProperty(principal).toCharArray();
+
+ return compareCharArray(pwd, password);
+ }
+
+ public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException
+ {
+ return false; // updates denied
+ }
+
+ public boolean createPrincipal(Principal principal, char[] password)
+ {
+ return false; // updates denied
+ }
+
+ public boolean deletePrincipal(Principal principal) throws AccountNotFoundException
+ {
+ return false; // updates denied
+ }
+
+ private boolean compareCharArray(char[] a, char[] b)
+ {
+ boolean equal = false;
+ if (a.length == b.length)
+ {
+ equal = true;
+ int index = 0;
+ while (equal && index < a.length)
+ {
+ equal = a[index] == b[index];
+ index++;
+ }
+ }
+ return equal;
+ }
+
+ private char[] convertPassword(String password) throws UnsupportedEncodingException
+ {
+ byte[] passwdBytes = password.getBytes("utf-8");
+
+ char[] passwd = new char[passwdBytes.length];
+
+ int index = 0;
+
+ for (byte b : passwdBytes)
+ {
+ passwd[index++] = (char) b;
+ }
+
+ return passwd;
+ }
+
+
+ public Map<String, AuthenticationProviderInitialiser> getMechanisms()
+ {
+ return _saslServers;
+ }
+
+ public List<Principal> getUsers()
+ {
+ return new LinkedList<Principal>(); //todo
+ }
+
+ public Principal getUser(String username)
+ {
+ if (_users.getProperty(username) != null)
+ {
+ return new UsernamePrincipal(username);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public void reload() throws IOException
+ {
+ //No file to update from, so do nothing.
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java
new file mode 100644
index 0000000000..4efe381a8b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.HashMap;
+
+public class PropertiesPrincipalDatabaseManager implements PrincipalDatabaseManager
+{
+
+ Map<String, PrincipalDatabase> _databases = new HashMap<String, PrincipalDatabase>();
+
+ public PropertiesPrincipalDatabaseManager(String name, Properties users)
+ {
+ _databases.put(name, new PropertiesPrincipalDatabase(users));
+ }
+
+ public Map<String, PrincipalDatabase> getDatabases()
+ {
+ return _databases;
+ }
+
+ @Override
+ public void initialiseManagement(ServerConfiguration _configuration) throws ConfigurationException
+ {
+ //todo
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java
new file mode 100644
index 0000000000..d1803124a7
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.manager;
+
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+
+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);
+
+ void close();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java
new file mode 100644
index 0000000000..98c060599a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java
@@ -0,0 +1,237 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.auth.manager;
+
+import org.apache.log4j.Logger;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+import org.apache.qpid.server.security.auth.sasl.JCAProvider;
+import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.sasl.SaslServerFactory;
+import javax.security.sasl.SaslServer;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.Sasl;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.TreeMap;
+import java.security.Security;
+
+public class PrincipalDatabaseAuthenticationManager implements AuthenticationManager
+{
+ private static final Logger _logger = Logger.getLogger(PrincipalDatabaseAuthenticationManager.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, ?>>();
+
+ private AuthenticationManager _default = null;
+ /** The name for the required SASL Server mechanisms */
+ public static final String PROVIDER_NAME= "AMQSASLProvider-Server";
+
+ public PrincipalDatabaseAuthenticationManager(String name, VirtualHostConfiguration hostConfig) throws Exception
+ {
+ _logger.info("Initialising " + (name == null ? "Default" : "'" + name + "'")
+ + " PrincipleDatabase authentication manager.");
+
+ // Fixme This should be done per Vhost but allowing global hack isn't right but ...
+ // required as authentication is done before Vhost selection
+
+ Map<String, Class<? extends SaslServerFactory>> providerMap = new TreeMap<String, Class<? extends SaslServerFactory>>();
+
+
+ if (name == null || hostConfig == null)
+ {
+ initialiseAuthenticationMechanisms(providerMap, ApplicationRegistry.getInstance().getDatabaseManager().getDatabases());
+ }
+ else
+ {
+ String databaseName = hostConfig.getAuthenticationDatabase();
+
+ if (databaseName == null)
+ {
+
+ _default = ApplicationRegistry.getInstance().getAuthenticationManager();
+ return;
+ }
+ else
+ {
+ PrincipalDatabase database = ApplicationRegistry.getInstance().getDatabaseManager().getDatabases().get(databaseName);
+
+ if (database == null)
+ {
+ throw new ConfigurationException("Requested database:" + databaseName + " was not found");
+ }
+
+ initialiseAuthenticationMechanisms(providerMap, database);
+ }
+ }
+
+ if (providerMap.size() > 0)
+ {
+ // Ensure we are used before the defaults
+ if (Security.insertProviderAt(new JCAProvider(PROVIDER_NAME, providerMap), 1) == -1)
+ {
+ _logger.error("Unable to load custom SASL providers. Qpid custom SASL authenticators unavailable.");
+ }
+ else
+ {
+ _logger.info("Additional SASL providers successfully registered.");
+ }
+
+ }
+ else
+ {
+ _logger.warn("No additional SASL providers registered.");
+ }
+
+ }
+
+
+ private void initialiseAuthenticationMechanisms(Map<String, Class<? extends SaslServerFactory>> providerMap, Map<String, PrincipalDatabase> databases) throws Exception
+ {
+ if (databases.size() > 1)
+ {
+ _logger.warn("More than one principle database provided currently authentication mechanism will override each other.");
+ }
+
+ for (Map.Entry<String, PrincipalDatabase> entry : databases.entrySet())
+ {
+ // fixme As the database now provide the mechanisms they support, they will ...
+ // overwrite each other in the map. There should only be one database per vhost.
+ // But currently we must have authentication before vhost definition.
+ initialiseAuthenticationMechanisms(providerMap, entry.getValue());
+ }
+ }
+
+ private void initialiseAuthenticationMechanisms(Map<String, Class<? extends SaslServerFactory>> providerMap, PrincipalDatabase database) throws Exception
+ {
+ if (database == null || database.getMechanisms().size() == 0)
+ {
+ _logger.warn("No Database or no mechanisms to initialise authentication");
+ return;
+ }
+
+ for (Map.Entry<String, AuthenticationProviderInitialiser> mechanism : database.getMechanisms().entrySet())
+ {
+ initialiseAuthenticationMechanism(mechanism.getKey(), mechanism.getValue(), providerMap);
+ }
+ }
+
+ private void initialiseAuthenticationMechanism(String mechanism, AuthenticationProviderInitialiser initialiser,
+ Map<String, Class<? extends SaslServerFactory>> providerMap)
+ throws Exception
+ {
+ 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);
+ }
+ _logger.info("Initialised " + mechanism + " SASL provider successfully");
+ }
+
+ public String getMechanisms()
+ {
+ if (_default != null)
+ {
+ // Use the default AuthenticationManager if present
+ return _default.getMechanisms();
+ }
+ else
+ {
+ return _mechanisms;
+ }
+ }
+
+ public SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException
+ {
+ if (_default != null)
+ {
+ // Use the default AuthenticationManager if present
+ return _default.createSaslServer(mechanism, localFQDN);
+ }
+ else
+ {
+ return Sasl.createSaslServer(mechanism, "AMQP", localFQDN, _serverCreationProperties.get(mechanism),
+ _callbackHandlerMap.get(mechanism));
+ }
+
+ }
+
+ public AuthenticationResult authenticate(SaslServer server, byte[] response)
+ {
+ // Use the default AuthenticationManager if present
+ if (_default != null)
+ {
+ return _default.authenticate(server, 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, e);
+ }
+ }
+
+ public void close()
+ {
+ Security.removeProvider(PROVIDER_NAME);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java
new file mode 100644
index 0000000000..77040e896c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java
@@ -0,0 +1,119 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.rmi;
+
+import java.util.Collections;
+
+import javax.management.remote.JMXAuthenticator;
+import javax.management.remote.JMXPrincipal;
+import javax.security.auth.Subject;
+import javax.security.auth.login.AccountNotFoundException;
+
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+
+public class RMIPasswordAuthenticator implements JMXAuthenticator
+{
+ static final String UNABLE_TO_LOOKUP = "The broker was unable to lookup the user details";
+ static final String SHOULD_BE_STRING_ARRAY = "User details should be String[]";
+ static final String SHOULD_HAVE_2_ELEMENTS = "User details should have 2 elements, username, password";
+ static final String SHOULD_BE_NON_NULL = "Supplied username and password should be non-null";
+ static final String INVALID_CREDENTIALS = "Invalid user details supplied";
+ static final String CREDENTIALS_REQUIRED = "User details are required. " +
+ "Please ensure you are using an up to date management console to connect.";
+
+ private PrincipalDatabase _db = null;
+
+ public RMIPasswordAuthenticator()
+ {
+ }
+
+ public void setPrincipalDatabase(PrincipalDatabase pd)
+ {
+ this._db = pd;
+ }
+
+ public Subject authenticate(Object credentials) throws SecurityException
+ {
+ // Verify that credential's are of type String[].
+ if (!(credentials instanceof String[]))
+ {
+ if (credentials == null)
+ {
+ throw new SecurityException(CREDENTIALS_REQUIRED);
+ }
+ else
+ {
+ throw new SecurityException(SHOULD_BE_STRING_ARRAY);
+ }
+ }
+
+ // Verify that required number of credential's.
+ final String[] userCredentials = (String[]) credentials;
+ if (userCredentials.length != 2)
+ {
+ throw new SecurityException(SHOULD_HAVE_2_ELEMENTS);
+ }
+
+ String username = (String) userCredentials[0];
+ String password = (String) userCredentials[1];
+
+ // Verify that all required credential's are actually present.
+ if (username == null || password == null)
+ {
+ throw new SecurityException(SHOULD_BE_NON_NULL);
+ }
+
+ // Verify that a PD has been set.
+ if (_db == null)
+ {
+ throw new SecurityException(UNABLE_TO_LOOKUP);
+ }
+
+ boolean authenticated = false;
+
+ // Perform authentication
+ try
+ {
+ if (_db.verifyPassword(username, password.toCharArray()))
+ {
+ authenticated = true;
+ }
+ }
+ catch (AccountNotFoundException e)
+ {
+ throw new SecurityException(INVALID_CREDENTIALS);
+ }
+
+ if (authenticated)
+ {
+ //credential's check out, return the appropriate JAAS Subject
+ return new Subject(true,
+ Collections.singleton(new JMXPrincipal(username)),
+ Collections.EMPTY_SET,
+ Collections.EMPTY_SET);
+ }
+ else
+ {
+ throw new SecurityException(INVALID_CREDENTIALS);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java
new file mode 100644
index 0000000000..89e545d6f5
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java
@@ -0,0 +1,76 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl;
+
+import java.util.Map;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.sasl.SaslServerFactory;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+
+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
+ * @throws Exception needs refined Exception is too broad.
+ */
+ void initialise(String baseConfigPath, Configuration configuration,
+ Map<String, PrincipalDatabase> principalDatabases) throws Exception;
+
+ /**
+ * Initialise the authentication provider.
+ * @param db The principal database to initialise with
+ */
+ void initialise(PrincipalDatabase db);
+
+
+ /**
+ * @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/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/JCAProvider.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/JCAProvider.java
new file mode 100644
index 0000000000..d6a09d8217
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/JCAProvider.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl;
+
+import java.security.Provider;
+import java.security.Security;
+import java.util.Map;
+
+import javax.security.sasl.SaslServerFactory;
+
+public final class JCAProvider extends Provider
+{
+ public JCAProvider(String name, Map<String, Class<? extends SaslServerFactory>> providerMap)
+ {
+ super(name, 1.0, "A JCA provider that registers all " +
+ "AMQ SASL providers that want to be registered");
+ register(providerMap);
+ }
+
+ 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());
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java
new file mode 100644
index 0000000000..dd0bd096c3
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java
@@ -0,0 +1,123 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Map;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.AccountNotFoundException;
+import javax.security.sasl.AuthorizeCallback;
+
+import org.apache.commons.configuration.Configuration;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser;
+import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
+
+public abstract class UsernamePasswordInitialiser implements AuthenticationProviderInitialiser
+{
+ protected static final Logger _logger = Logger.getLogger(UsernamePasswordInitialiser.class);
+
+ 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
+ IOException ioe = new IOException("Error looking up user " + e);
+ ioe.initCause(e);
+ throw ioe;
+ }
+ }
+ 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);
+
+ initialise(db);
+ }
+
+ public void initialise(PrincipalDatabase db)
+ {
+ if (db == null)
+ {
+ throw new NullPointerException("Cannot initialise with a null Principal database.");
+ }
+
+ _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/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipal.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipal.java
new file mode 100644
index 0000000000..d7c8383690
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipal.java
@@ -0,0 +1,44 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl;
+
+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;
+ }
+
+ public String toString()
+ {
+ return _name;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java
new file mode 100644
index 0000000000..7acc6322d1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl.amqplain;
+
+import javax.security.sasl.SaslServerFactory;
+
+import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser;
+
+public class AmqPlainInitialiser extends UsernamePasswordInitialiser
+{
+ public String getMechanismName()
+ {
+ return "AMQPLAIN";
+ }
+
+ public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration()
+ {
+ return AmqPlainSaslServerFactory.class;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java
new file mode 100644
index 0000000000..9f56b8521a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java
@@ -0,0 +1,132 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl.amqplain;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.AuthorizeCallback;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.framing.AMQFrameDecodingException;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.FieldTableFactory;
+
+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 = FieldTableFactory.newFieldTable(ByteBuffer.wrap(response), response.length);
+ String username = (String) ft.getString("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.getString("PASSWORD");
+ AuthorizeCallback authzCb = new AuthorizeCallback(username, username);
+ Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb};
+ _cbh.handle(callbacks);
+ String storedPwd = new String(passwordCb.getPassword());
+ if (storedPwd.equals(pwd))
+ {
+ _complete = true;
+ }
+ if (authzCb.isAuthorized() && _complete)
+ {
+ _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/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java
new file mode 100644
index 0000000000..67d20136bf
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java
@@ -0,0 +1,60 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl.amqplain;
+
+import java.util.Map;
+
+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;
+
+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/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java
new file mode 100644
index 0000000000..97f9a4e91a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java
@@ -0,0 +1,50 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl.crammd5;
+
+import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+
+import javax.security.sasl.SaslServerFactory;
+import java.util.Map;
+
+public class CRAMMD5HashedInitialiser extends UsernamePasswordInitialiser
+{
+ public String getMechanismName()
+ {
+ return CRAMMD5HashedSaslServer.MECHANISM;
+ }
+
+ public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration()
+ {
+ return CRAMMD5HashedServerFactory.class;
+ }
+
+ public void initialise(PrincipalDatabase passwordFile)
+ {
+ super.initialise(passwordFile);
+ }
+
+ public Map<String, ?> getProperties()
+ {
+ return null;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java
new file mode 100644
index 0000000000..f6cab084ea
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl.crammd5;
+
+import javax.security.sasl.SaslServer;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslServerFactory;
+import javax.security.auth.callback.CallbackHandler;
+import java.util.Enumeration;
+import java.util.Map;
+
+public class CRAMMD5HashedSaslServer implements SaslServer
+{
+ public static final String MECHANISM = "CRAM-MD5-HASHED";
+
+ private SaslServer _realServer;
+
+ public CRAMMD5HashedSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props,
+ CallbackHandler cbh) throws SaslException
+ {
+ Enumeration factories = Sasl.getSaslServerFactories();
+
+ while (factories.hasMoreElements())
+ {
+ SaslServerFactory factory = (SaslServerFactory) factories.nextElement();
+
+ if (factory instanceof CRAMMD5HashedServerFactory)
+ {
+ continue;
+ }
+
+ String[] mechs = factory.getMechanismNames(props);
+
+ for (String mech : mechs)
+ {
+ if (mech.equals("CRAM-MD5"))
+ {
+ _realServer = factory.createSaslServer("CRAM-MD5", protocol, serverName, props, cbh);
+ return;
+ }
+ }
+ }
+
+ throw new RuntimeException("No default SaslServer found for mechanism:" + "CRAM-MD5");
+ }
+
+ public String getMechanismName()
+ {
+ return MECHANISM;
+ }
+
+ public byte[] evaluateResponse(byte[] response) throws SaslException
+ {
+ return _realServer.evaluateResponse(response);
+ }
+
+ public boolean isComplete()
+ {
+ return _realServer.isComplete();
+ }
+
+ public String getAuthorizationID()
+ {
+ return _realServer.getAuthorizationID();
+ }
+
+ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+ {
+ return _realServer.unwrap(incoming, offset, len);
+ }
+
+ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+ {
+ return _realServer.wrap(outgoing, offset, len);
+ }
+
+ public Object getNegotiatedProperty(String propName)
+ {
+ return _realServer.getNegotiatedProperty(propName);
+ }
+
+ public void dispose() throws SaslException
+ {
+ _realServer.dispose();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java
new file mode 100644
index 0000000000..5298b5cc63
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl.crammd5;
+
+import java.util.Map;
+
+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;
+
+public class CRAMMD5HashedServerFactory implements SaslServerFactory
+{
+ public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props,
+ CallbackHandler cbh) throws SaslException
+ {
+ if (mechanism.equals(CRAMMD5HashedSaslServer.MECHANISM))
+ {
+ return new CRAMMD5HashedSaslServer(mechanism, protocol, serverName, props, cbh);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public String[] getMechanismNames(Map props)
+ {
+ if (props != null)
+ {
+ 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];
+ }
+ }
+
+ return new String[]{CRAMMD5HashedSaslServer.MECHANISM};
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java
new file mode 100644
index 0000000000..264832888d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java
@@ -0,0 +1,71 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl.crammd5;
+
+import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+
+import javax.security.sasl.SaslServerFactory;
+
+public class CRAMMD5Initialiser extends UsernamePasswordInitialiser
+{
+ private HashDirection _hashDirection;
+
+ public enum HashDirection
+ {
+ INCOMMING, PASSWORD_FILE
+ }
+
+
+ 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.
+ if (_hashDirection == HashDirection.PASSWORD_FILE)
+ {
+ return null;
+ }
+ else
+ {
+ //fixme we need a server that will correctly has the incomming plain text for comparison to file.
+ _logger.warn("we need a server that will correctly convert the incomming plain text for comparison to file.");
+ return null;
+ }
+ }
+
+ public void initialise(PrincipalDatabase passwordFile)
+ {
+ initialise(passwordFile, HashDirection.PASSWORD_FILE);
+ }
+
+ public void initialise(PrincipalDatabase passwordFile, HashDirection direction)
+ {
+ super.initialise(passwordFile);
+
+ _hashDirection = direction;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java
new file mode 100644
index 0000000000..1d16cd8755
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl.plain;
+
+import javax.security.sasl.SaslServerFactory;
+
+import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser;
+
+public class PlainInitialiser extends UsernamePasswordInitialiser
+{
+ public String getMechanismName()
+ {
+ return "PLAIN";
+ }
+
+ public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration()
+ {
+ return PlainSaslServerFactory.class;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java
new file mode 100644
index 0000000000..45fb9a4e42
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java
@@ -0,0 +1,151 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl.plain;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.AuthorizeCallback;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+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);
+ 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");
+ AuthorizeCallback authzCb = new AuthorizeCallback(authzid, authzid);
+ Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb};
+ _cbh.handle(callbacks);
+ String storedPwd = new String(passwordCb.getPassword());
+ if (storedPwd.equals(pwd))
+ {
+ _complete = true;
+ }
+ if (authzCb.isAuthorized() && _complete)
+ {
+ _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/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java
new file mode 100644
index 0000000000..f0dd9eeb6d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java
@@ -0,0 +1,60 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.sasl.plain;
+
+import java.util.Map;
+
+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;
+
+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/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java
new file mode 100644
index 0000000000..f427cc7206
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java
@@ -0,0 +1,36 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.state;
+
+/**
+ * States used in the AMQ protocol. Used by the finite state machine to determine
+ * valid responses.
+ */
+public enum AMQState
+{
+ CONNECTION_NOT_STARTED,
+ CONNECTION_NOT_AUTH,
+ CONNECTION_NOT_TUNED,
+ CONNECTION_NOT_OPENED,
+ CONNECTION_OPEN,
+ CONNECTION_CLOSING,
+ CONNECTION_CLOSED
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java
new file mode 100644
index 0000000000..c5b3099f58
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java
@@ -0,0 +1,263 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.state;
+
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.protocol.AMQMethodListener;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.handler.BasicAckMethodHandler;
+import org.apache.qpid.server.handler.BasicCancelMethodHandler;
+import org.apache.qpid.server.handler.BasicConsumeMethodHandler;
+import org.apache.qpid.server.handler.BasicGetMethodHandler;
+import org.apache.qpid.server.handler.BasicPublishMethodHandler;
+import org.apache.qpid.server.handler.BasicQosHandler;
+import org.apache.qpid.server.handler.BasicRecoverMethodHandler;
+import org.apache.qpid.server.handler.BasicRejectMethodHandler;
+import org.apache.qpid.server.handler.ChannelCloseHandler;
+import org.apache.qpid.server.handler.ChannelCloseOkHandler;
+import org.apache.qpid.server.handler.ChannelFlowHandler;
+import org.apache.qpid.server.handler.ChannelOpenHandler;
+import org.apache.qpid.server.handler.ConnectionCloseMethodHandler;
+import org.apache.qpid.server.handler.ConnectionCloseOkMethodHandler;
+import org.apache.qpid.server.handler.ConnectionOpenMethodHandler;
+import org.apache.qpid.server.handler.ConnectionSecureOkMethodHandler;
+import org.apache.qpid.server.handler.ConnectionStartOkMethodHandler;
+import org.apache.qpid.server.handler.ConnectionTuneOkMethodHandler;
+import org.apache.qpid.server.handler.ExchangeBoundHandler;
+import org.apache.qpid.server.handler.ExchangeDeclareHandler;
+import org.apache.qpid.server.handler.ExchangeDeleteHandler;
+import org.apache.qpid.server.handler.QueueBindHandler;
+import org.apache.qpid.server.handler.QueueDeclareHandler;
+import org.apache.qpid.server.handler.QueueDeleteHandler;
+import org.apache.qpid.server.handler.QueuePurgeHandler;
+import org.apache.qpid.server.handler.TxCommitHandler;
+import org.apache.qpid.server.handler.TxRollbackHandler;
+import org.apache.qpid.server.handler.TxSelectHandler;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+
+/**
+ * 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);
+
+ private final VirtualHostRegistry _virtualHostRegistry;
+ private final AMQProtocolSession _protocolSession;
+ /** 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 EnumMap<AMQState, Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>> _state2HandlersMap =
+ new EnumMap<AMQState, Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>>(
+ AMQState.class);
+ */
+
+
+ private CopyOnWriteArraySet<StateListener> _stateListeners = new CopyOnWriteArraySet<StateListener>();
+
+ public AMQStateManager(VirtualHostRegistry virtualHostRegistry, AMQProtocolSession protocolSession)
+ {
+
+ _virtualHostRegistry = virtualHostRegistry;
+ _protocolSession = protocolSession;
+ _currentState = AMQState.CONNECTION_NOT_STARTED;
+
+ }
+
+ /*
+ protected void registerListeners()
+ {
+ Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>> frame2handlerMap;
+
+ frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>();
+ _state2HandlersMap.put(AMQState.CONNECTION_NOT_STARTED, frame2handlerMap);
+
+ frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>();
+ _state2HandlersMap.put(AMQState.CONNECTION_NOT_AUTH, frame2handlerMap);
+
+ frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>();
+ _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>>();
+ ChannelOpenHandler.getInstance();
+ ChannelCloseHandler.getInstance();
+ ChannelCloseOkHandler.getInstance();
+ ConnectionCloseMethodHandler.getInstance();
+ ConnectionCloseOkMethodHandler.getInstance();
+ ConnectionTuneOkMethodHandler.getInstance();
+ ConnectionSecureOkMethodHandler.getInstance();
+ ConnectionStartOkMethodHandler.getInstance();
+ ExchangeDeclareHandler.getInstance();
+ ExchangeDeleteHandler.getInstance();
+ ExchangeBoundHandler.getInstance();
+ BasicAckMethodHandler.getInstance();
+ BasicRecoverMethodHandler.getInstance();
+ BasicConsumeMethodHandler.getInstance();
+ BasicGetMethodHandler.getInstance();
+ BasicCancelMethodHandler.getInstance();
+ BasicPublishMethodHandler.getInstance();
+ BasicQosHandler.getInstance();
+ QueueBindHandler.getInstance();
+ QueueDeclareHandler.getInstance();
+ QueueDeleteHandler.getInstance();
+ QueuePurgeHandler.getInstance();
+ ChannelFlowHandler.getInstance();
+ TxSelectHandler.getInstance();
+ TxCommitHandler.getInstance();
+ TxRollbackHandler.getInstance();
+ BasicRejectMethodHandler.getInstance();
+
+ _state2HandlersMap.put(AMQState.CONNECTION_OPEN, frame2handlerMap);
+
+ frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>();
+
+ _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(Exception e)
+ {
+ _logger.error("State manager received error notification[Current State:" + _currentState + "]: " + e, e);
+ for (StateListener l : _stateListeners)
+ {
+ l.error(e);
+ }
+ }
+
+ public <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt) throws AMQException
+ {
+ MethodDispatcher dispatcher = _protocolSession.getMethodDispatcher();
+
+ final int channelId = evt.getChannelId();
+ B body = evt.getMethod();
+
+ if(channelId != 0 && _protocolSession.getChannel(channelId)== null)
+ {
+
+ if(! ((body instanceof ChannelOpenBody)
+ || (body instanceof ChannelCloseOkBody)
+ || (body instanceof ChannelCloseBody)))
+ {
+ throw body.getConnectionException(AMQConstant.CHANNEL_ERROR, "channel is closed");
+ }
+
+ }
+
+ return body.execute(dispatcher, channelId);
+
+ }
+
+ private <B extends AMQMethodBody> void checkChannel(AMQMethodEvent<B> evt, AMQProtocolSession protocolSession)
+ throws AMQException
+ {
+ if ((evt.getChannelId() != 0) && !(evt.getMethod() instanceof ChannelOpenBody)
+ && (protocolSession.getChannel(evt.getChannelId()) == null)
+ && !protocolSession.channelAwaitingClosure(evt.getChannelId()))
+ {
+ throw evt.getMethod().getChannelNotFoundException(evt.getChannelId());
+ }
+ }
+
+/*
+ protected <B extends AMQMethodBody> StateAwareMethodListener<B> findStateTransitionHandler(AMQState currentState,
+ B frame)
+ // throws IllegalStateTransitionException
+ {
+ final Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>> classToHandlerMap =
+ _state2HandlersMap.get(currentState);
+
+ final StateAwareMethodListener<B> handler =
+ (classToHandlerMap == null) ? null : (StateAwareMethodListener<B>) classToHandlerMap.get(frame.getClass());
+
+ if (handler == null)
+ {
+ _logger.debug("No state transition handler defined for receiving frame " + frame);
+
+ return null;
+ }
+ else
+ {
+ return handler;
+ }
+ }
+*/
+
+ public void addStateListener(StateListener listener)
+ {
+ _logger.debug("Adding state listener");
+ _stateListeners.add(listener);
+ }
+
+ public void removeStateListener(StateListener listener)
+ {
+ _stateListeners.remove(listener);
+ }
+
+ public VirtualHostRegistry getVirtualHostRegistry()
+ {
+ return _virtualHostRegistry;
+ }
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _protocolSession;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java
new file mode 100644
index 0000000000..cec67a8a6d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.state;
+
+import org.apache.qpid.AMQException;
+
+/**
+ * @todo Not an AMQP exception as no status code.
+ *
+ * @todo Not used! Delete.
+ */
+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/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java
new file mode 100644
index 0000000000..3c11bb8a9c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java
@@ -0,0 +1,35 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.state;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+
+/**
+ * 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, B evt, int channelId) throws AMQException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java
new file mode 100644
index 0000000000..00fc09867b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.state;
+
+import org.apache.qpid.AMQException;
+
+public interface StateListener
+{
+ void stateChanged(AMQState oldState, AMQState newState) throws AMQException;
+
+ void error(Throwable t);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/DerbyMessageStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/DerbyMessageStore.java
new file mode 100644
index 0000000000..e7f9c777c9
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/DerbyMessageStore.java
@@ -0,0 +1,1464 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.store;
+
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.AMQQueueFactory;
+import org.apache.qpid.server.queue.MessageMetaData;
+import org.apache.qpid.server.queue.QueueRegistry;
+
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.MessageHandleFactory;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.commons.configuration.Configuration;
+import org.apache.log4j.Logger;
+import org.apache.mina.common.ByteBuffer;
+
+import java.io.File;
+import java.io.ByteArrayInputStream;
+import java.sql.DriverManager;
+import java.sql.Driver;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Blob;
+import java.sql.Types;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+
+public class DerbyMessageStore implements MessageStore
+{
+
+ private static final Logger _logger = Logger.getLogger(DerbyMessageStore.class);
+
+ private static final String ENVIRONMENT_PATH_PROPERTY = "environment-path";
+
+
+ private static final String SQL_DRIVER_NAME = "org.apache.derby.jdbc.EmbeddedDriver";
+
+ private static final String DB_VERSION_TABLE_NAME = "QPID_DB_VERSION";
+
+ private static final String EXCHANGE_TABLE_NAME = "QPID_EXCHANGE";
+ private static final String QUEUE_TABLE_NAME = "QPID_QUEUE";
+ private static final String BINDINGS_TABLE_NAME = "QPID_BINDINGS";
+ private static final String QUEUE_ENTRY_TABLE_NAME = "QPID_QUEUE_ENTRY";
+ private static final String MESSAGE_META_DATA_TABLE_NAME = "QPID_MESSAGE_META_DATA";
+ private static final String MESSAGE_CONTENT_TABLE_NAME = "QPID_MESSAGE_CONTENT";
+
+ private static final int DB_VERSION = 1;
+
+
+
+ private VirtualHost _virtualHost;
+ private static Class<Driver> DRIVER_CLASS;
+
+ private final AtomicLong _messageId = new AtomicLong(1);
+ private AtomicBoolean _closed = new AtomicBoolean(false);
+
+ private String _connectionURL;
+
+
+
+ private static final String CREATE_DB_VERSION_TABLE = "CREATE TABLE "+DB_VERSION_TABLE_NAME+" ( version int not null )";
+ private static final String INSERT_INTO_DB_VERSION = "INSERT INTO "+DB_VERSION_TABLE_NAME+" ( version ) VALUES ( ? )";
+ private static final String CREATE_EXCHANGE_TABLE = "CREATE TABLE "+EXCHANGE_TABLE_NAME+" ( name varchar(255) not null, type varchar(255) not null, autodelete SMALLINT not null, PRIMARY KEY ( name ) )";
+ private static final String CREATE_QUEUE_TABLE = "CREATE TABLE "+QUEUE_TABLE_NAME+" ( name varchar(255) not null, owner varchar(255), PRIMARY KEY ( name ) )";
+ private static final String CREATE_BINDINGS_TABLE = "CREATE TABLE "+BINDINGS_TABLE_NAME+" ( exchange_name varchar(255) not null, queue_name varchar(255) not null, binding_key varchar(255) not null, arguments blob , PRIMARY KEY ( exchange_name, queue_name, binding_key ) )";
+ private static final String CREATE_QUEUE_ENTRY_TABLE = "CREATE TABLE "+QUEUE_ENTRY_TABLE_NAME+" ( queue_name varchar(255) not null, message_id bigint not null, PRIMARY KEY (queue_name, message_id) )";
+ private static final String CREATE_MESSAGE_META_DATA_TABLE = "CREATE TABLE "+MESSAGE_META_DATA_TABLE_NAME+" ( message_id bigint not null, exchange_name varchar(255) not null, routing_key varchar(255), flag_mandatory smallint not null, flag_immediate smallint not null, content_header blob, chunk_count int not null, PRIMARY KEY ( message_id ) )";
+ private static final String CREATE_MESSAGE_CONTENT_TABLE = "CREATE TABLE "+MESSAGE_CONTENT_TABLE_NAME+" ( message_id bigint not null, chunk_id int not null, content_chunk blob , PRIMARY KEY (message_id, chunk_id) )";
+ private static final String SELECT_FROM_QUEUE = "SELECT name, owner FROM " + QUEUE_TABLE_NAME;
+ private static final String SELECT_FROM_EXCHANGE = "SELECT name, type, autodelete FROM " + EXCHANGE_TABLE_NAME;
+ private static final String SELECT_FROM_BINDINGS =
+ "SELECT queue_name, binding_key, arguments FROM " + BINDINGS_TABLE_NAME + " WHERE exchange_name = ?";
+ private static final String DELETE_FROM_MESSAGE_META_DATA = "DELETE FROM " + MESSAGE_META_DATA_TABLE_NAME + " WHERE message_id = ?";
+ private static final String DELETE_FROM_MESSAGE_CONTENT = "DELETE FROM " + MESSAGE_CONTENT_TABLE_NAME + " WHERE message_id = ?";
+ private static final String INSERT_INTO_EXCHANGE = "INSERT INTO " + EXCHANGE_TABLE_NAME + " ( name, type, autodelete ) VALUES ( ?, ?, ? )";
+ private static final String DELETE_FROM_EXCHANGE = "DELETE FROM " + EXCHANGE_TABLE_NAME + " WHERE name = ?";
+ private static final String INSERT_INTO_BINDINGS = "INSERT INTO " + BINDINGS_TABLE_NAME + " ( exchange_name, queue_name, binding_key, arguments ) values ( ?, ?, ?, ? )";
+ private static final String DELETE_FROM_BINDINGS = "DELETE FROM " + BINDINGS_TABLE_NAME + " WHERE exchange_name = ? AND queue_name = ? AND binding_key = ?";
+ private static final String INSERT_INTO_QUEUE = "INSERT INTO " + QUEUE_TABLE_NAME + " (name, owner) VALUES (?, ?)";
+ private static final String DELETE_FROM_QUEUE = "DELETE FROM " + QUEUE_TABLE_NAME + " WHERE name = ?";
+ private static final String INSERT_INTO_QUEUE_ENTRY = "INSERT INTO " + QUEUE_ENTRY_TABLE_NAME + " (queue_name, message_id) values (?,?)";
+ private static final String DELETE_FROM_QUEUE_ENTRY = "DELETE FROM " + QUEUE_ENTRY_TABLE_NAME + " WHERE queue_name = ? AND message_id =?";
+ private static final String INSERT_INTO_MESSAGE_CONTENT = "INSERT INTO " + MESSAGE_CONTENT_TABLE_NAME + "( message_id, chunk_id, content_chunk ) values (?, ?, ?)";
+ private static final String INSERT_INTO_MESSAGE_META_DATA = "INSERT INTO " + MESSAGE_META_DATA_TABLE_NAME + "( message_id , exchange_name , routing_key , flag_mandatory , flag_immediate , content_header , chunk_count ) values (?, ?, ?, ?, ?, ?, ?)";
+ private static final String SELECT_FROM_MESSAGE_META_DATA =
+ "SELECT exchange_name , routing_key , flag_mandatory , flag_immediate , content_header , chunk_count FROM " + MESSAGE_META_DATA_TABLE_NAME + " WHERE message_id = ?";
+ private static final String SELECT_FROM_MESSAGE_CONTENT =
+ "SELECT content_chunk FROM " + MESSAGE_CONTENT_TABLE_NAME + " WHERE message_id = ? and chunk_id = ?";
+ private static final String SELECT_FROM_QUEUE_ENTRY = "SELECT queue_name, message_id FROM " + QUEUE_ENTRY_TABLE_NAME;
+ private static final String TABLE_EXISTANCE_QUERY = "SELECT 1 FROM SYS.SYSTABLES WHERE TABLENAME = ?";
+
+
+ private enum State
+ {
+ INITIAL,
+ CONFIGURING,
+ RECOVERING,
+ STARTED,
+ CLOSING,
+ CLOSED
+ }
+
+ private State _state = State.INITIAL;
+
+
+ public void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception
+ {
+ stateTransition(State.INITIAL, State.CONFIGURING);
+
+ initialiseDriver();
+
+ _virtualHost = virtualHost;
+
+ _logger.info("Configuring Derby message store for virtual host " + virtualHost.getName());
+ QueueRegistry queueRegistry = virtualHost.getQueueRegistry();
+
+ final String databasePath = config.getStoreConfiguration().getString(base + "." + ENVIRONMENT_PATH_PROPERTY, "derbyDB");
+
+ File environmentPath = new File(databasePath);
+ if (!environmentPath.exists())
+ {
+ if (!environmentPath.mkdirs())
+ {
+ throw new IllegalArgumentException("Environment path " + environmentPath + " could not be read or created. "
+ + "Ensure the path is correct and that the permissions are correct.");
+ }
+ }
+
+ createOrOpenDatabase(databasePath);
+
+ // this recovers durable queues and persistent messages
+
+ recover();
+
+ stateTransition(State.RECOVERING, State.STARTED);
+
+ }
+
+ private static synchronized void initialiseDriver() throws ClassNotFoundException
+ {
+ if(DRIVER_CLASS == null)
+ {
+ DRIVER_CLASS = (Class<Driver>) Class.forName(SQL_DRIVER_NAME);
+ }
+ }
+
+ private void createOrOpenDatabase(final String environmentPath) throws SQLException
+ {
+ _connectionURL = "jdbc:derby:" + environmentPath + "/" + _virtualHost.getName() + ";create=true";
+
+ Connection conn = newConnection();
+
+ createVersionTable(conn);
+ createExchangeTable(conn);
+ createQueueTable(conn);
+ createBindingsTable(conn);
+ createQueueEntryTable(conn);
+ createMessageMetaDataTable(conn);
+ createMessageContentTable(conn);
+
+ conn.close();
+ }
+
+
+
+ private void createVersionTable(final Connection conn) throws SQLException
+ {
+ if(!tableExists(DB_VERSION_TABLE_NAME, conn))
+ {
+ Statement stmt = conn.createStatement();
+
+ stmt.execute(CREATE_DB_VERSION_TABLE);
+ stmt.close();
+
+ PreparedStatement pstmt = conn.prepareStatement(INSERT_INTO_DB_VERSION);
+ pstmt.setInt(1, DB_VERSION);
+ pstmt.execute();
+ pstmt.close();
+ }
+
+ }
+
+
+ private void createExchangeTable(final Connection conn) throws SQLException
+ {
+ if(!tableExists(EXCHANGE_TABLE_NAME, conn))
+ {
+ Statement stmt = conn.createStatement();
+
+ stmt.execute(CREATE_EXCHANGE_TABLE);
+ stmt.close();
+ }
+ }
+
+ private void createQueueTable(final Connection conn) throws SQLException
+ {
+ if(!tableExists(QUEUE_TABLE_NAME, conn))
+ {
+ Statement stmt = conn.createStatement();
+ stmt.execute(CREATE_QUEUE_TABLE);
+ stmt.close();
+ }
+ }
+
+ private void createBindingsTable(final Connection conn) throws SQLException
+ {
+ if(!tableExists(BINDINGS_TABLE_NAME, conn))
+ {
+ Statement stmt = conn.createStatement();
+ stmt.execute(CREATE_BINDINGS_TABLE);
+
+ stmt.close();
+ }
+
+ }
+
+ private void createQueueEntryTable(final Connection conn) throws SQLException
+ {
+ if(!tableExists(QUEUE_ENTRY_TABLE_NAME, conn))
+ {
+ Statement stmt = conn.createStatement();
+ stmt.execute(CREATE_QUEUE_ENTRY_TABLE);
+
+ stmt.close();
+ }
+
+ }
+
+ private void createMessageMetaDataTable(final Connection conn) throws SQLException
+ {
+ if(!tableExists(MESSAGE_META_DATA_TABLE_NAME, conn))
+ {
+ Statement stmt = conn.createStatement();
+ stmt.execute(CREATE_MESSAGE_META_DATA_TABLE);
+
+ stmt.close();
+ }
+
+ }
+
+
+ private void createMessageContentTable(final Connection conn) throws SQLException
+ {
+ if(!tableExists(MESSAGE_CONTENT_TABLE_NAME, conn))
+ {
+ Statement stmt = conn.createStatement();
+ stmt.execute(CREATE_MESSAGE_CONTENT_TABLE);
+
+ stmt.close();
+ }
+
+ }
+
+
+
+ private boolean tableExists(final String tableName, final Connection conn) throws SQLException
+ {
+ PreparedStatement stmt = conn.prepareStatement(TABLE_EXISTANCE_QUERY);
+ stmt.setString(1, tableName);
+ ResultSet rs = stmt.executeQuery();
+ boolean exists = rs.next();
+ rs.close();
+ stmt.close();
+ return exists;
+ }
+
+ public void recover() throws AMQException
+ {
+ stateTransition(State.CONFIGURING, State.RECOVERING);
+
+ _logger.info("Recovering persistent state...");
+ StoreContext context = new StoreContext();
+
+ try
+ {
+ Map<AMQShortString, AMQQueue> queues = loadQueues();
+
+ recoverExchanges();
+
+ try
+ {
+
+ beginTran(context);
+
+ deliverMessages(context, queues);
+ _logger.info("Persistent state recovered successfully");
+ commitTran(context);
+
+ }
+ finally
+ {
+ if(inTran(context))
+ {
+ abortTran(context);
+ }
+ }
+ }
+ catch (SQLException e)
+ {
+
+ throw new AMQException("Error recovering persistent state: " + e, e);
+ }
+
+ }
+
+ private Map<AMQShortString, AMQQueue> loadQueues() throws SQLException, AMQException
+ {
+ Connection conn = newConnection();
+
+
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(SELECT_FROM_QUEUE);
+ Map<AMQShortString, AMQQueue> queueMap = new HashMap<AMQShortString, AMQQueue>();
+ while(rs.next())
+ {
+ String queueName = rs.getString(1);
+ String owner = rs.getString(2);
+ AMQShortString queueNameShortString = new AMQShortString(queueName);
+ AMQQueue q = AMQQueueFactory.createAMQQueueImpl(queueNameShortString, true, owner == null ? null : new AMQShortString(owner), false, _virtualHost,
+ null);
+ _virtualHost.getQueueRegistry().registerQueue(q);
+ queueMap.put(queueNameShortString,q);
+
+ }
+ return queueMap;
+ }
+
+ private void recoverExchanges() throws AMQException, SQLException
+ {
+ for (Exchange exchange : loadExchanges())
+ {
+ recoverExchange(exchange);
+ }
+ }
+
+
+ private List<Exchange> loadExchanges() throws AMQException, SQLException
+ {
+
+ List<Exchange> exchanges = new ArrayList<Exchange>();
+ Connection conn = null;
+ try
+ {
+ conn = newConnection();
+
+
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(SELECT_FROM_EXCHANGE);
+
+ Exchange exchange;
+ while(rs.next())
+ {
+ String exchangeName = rs.getString(1);
+ String type = rs.getString(2);
+ boolean autoDelete = rs.getShort(3) != 0;
+
+ exchange = _virtualHost.getExchangeFactory().createExchange(new AMQShortString(exchangeName), new AMQShortString(type), true, autoDelete, 0);
+ _virtualHost.getExchangeRegistry().registerExchange(exchange);
+ exchanges.add(exchange);
+
+ }
+ return exchanges;
+
+ }
+ finally
+ {
+ if(conn != null)
+ {
+ conn.close();
+ }
+ }
+
+ }
+
+ private void recoverExchange(Exchange exchange) throws AMQException, SQLException
+ {
+ _logger.info("Recovering durable exchange " + exchange.getName() + " of type " + exchange.getType() + "...");
+
+ QueueRegistry queueRegistry = _virtualHost.getQueueRegistry();
+
+ Connection conn = null;
+ try
+ {
+ conn = newConnection();
+
+ PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_BINDINGS);
+ stmt.setString(1, exchange.getName().toString());
+
+ ResultSet rs = stmt.executeQuery();
+
+
+ while(rs.next())
+ {
+ String queueName = rs.getString(1);
+ String bindingKey = rs.getString(2);
+ Blob arguments = rs.getBlob(3);
+
+
+ AMQQueue queue = queueRegistry.getQueue(new AMQShortString(queueName));
+ if (queue == null)
+ {
+ _logger.error("Unkown queue: " + queueName + " cannot be bound to exchange: "
+ + exchange.getName());
+ }
+ else
+ {
+ _logger.info("Restoring binding: (Exchange: " + exchange.getName() + ", Queue: " + queueName
+ + ", Routing Key: " + bindingKey + ", Arguments: " + arguments
+ + ")");
+
+ FieldTable argumentsFT = null;
+ if(arguments != null)
+ {
+ byte[] argumentBytes = arguments.getBytes(0, (int) arguments.length());
+ ByteBuffer buf = ByteBuffer.wrap(argumentBytes);
+ argumentsFT = new FieldTable(buf,arguments.length());
+ }
+
+ queue.bind(exchange, bindingKey == null ? null : new AMQShortString(bindingKey), argumentsFT);
+
+ }
+ }
+ }
+ finally
+ {
+ if(conn != null)
+ {
+ conn.close();
+ }
+ }
+ }
+
+ public void close() throws Exception
+ {
+ _closed.getAndSet(true);
+ }
+
+ public void removeMessage(StoreContext storeContext, Long messageId) throws AMQException
+ {
+
+ boolean localTx = getOrCreateTransaction(storeContext);
+
+ Connection conn = getConnection(storeContext);
+ ConnectionWrapper wrapper = (ConnectionWrapper) storeContext.getPayload();
+
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Message Id: " + messageId + " Removing");
+ }
+
+ // first we need to look up the header to get the chunk count
+ MessageMetaData mmd = getMessageMetaData(storeContext, messageId);
+ try
+ {
+ PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_MESSAGE_META_DATA);
+ stmt.setLong(1,messageId);
+ wrapper.setRequiresCommit();
+ int results = stmt.executeUpdate();
+
+ if (results == 0)
+ {
+ if (localTx)
+ {
+ abortTran(storeContext);
+ }
+
+ throw new AMQException("Message metadata not found for message id " + messageId);
+ }
+ stmt.close();
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Deleted metadata for message " + messageId);
+ }
+
+ stmt = conn.prepareStatement(DELETE_FROM_MESSAGE_CONTENT);
+ stmt.setLong(1,messageId);
+ results = stmt.executeUpdate();
+
+ if(results != mmd.getContentChunkCount())
+ {
+ if (localTx)
+ {
+ abortTran(storeContext);
+ }
+ throw new AMQException("Unexpected number of content chunks when deleting message. Expected " + mmd.getContentChunkCount() + " but found " + results);
+
+ }
+
+ if (localTx)
+ {
+ commitTran(storeContext);
+ }
+ }
+ catch (SQLException e)
+ {
+ if ((conn != null) && localTx)
+ {
+ abortTran(storeContext);
+ }
+
+ throw new AMQException("Error writing AMQMessage with id " + messageId + " to database: " + e, e);
+ }
+
+ }
+
+ public void createExchange(Exchange exchange) throws AMQException
+ {
+ if (_state != State.RECOVERING)
+ {
+ try
+ {
+ Connection conn = null;
+
+ try
+ {
+ conn = newConnection();
+
+ PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_EXCHANGE);
+ stmt.setString(1, exchange.getName().toString());
+ stmt.setString(2, exchange.getType().toString());
+ stmt.setShort(3, exchange.isAutoDelete() ? (short) 1 : (short) 0);
+ stmt.execute();
+ stmt.close();
+ conn.commit();
+
+ }
+ finally
+ {
+ if(conn != null)
+ {
+ conn.close();
+ }
+ }
+ }
+ catch (SQLException e)
+ {
+ throw new AMQException("Error writing Exchange with name " + exchange.getName() + " to database: " + e, e);
+ }
+ }
+
+ }
+
+ public void removeExchange(Exchange exchange) throws AMQException
+ {
+ Connection conn = null;
+
+ try
+ {
+ conn = newConnection();
+ PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_EXCHANGE);
+ stmt.setString(1, exchange.getName().toString());
+ int results = stmt.executeUpdate();
+ if(results == 0)
+ {
+ throw new AMQException("Exchange " + exchange.getName() + " not found");
+ }
+ else
+ {
+ conn.commit();
+ stmt.close();
+ }
+ }
+ catch (SQLException e)
+ {
+ throw new AMQException("Error writing deleting with name " + exchange.getName() + " from database: " + e, e);
+ }
+ finally
+ {
+ if(conn != null)
+ {
+ try
+ {
+ conn.close();
+ }
+ catch (SQLException e)
+ {
+ _logger.error(e);
+ }
+ }
+
+ }
+ }
+
+ public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args)
+ throws AMQException
+ {
+ if (_state != State.RECOVERING)
+ {
+ Connection conn = null;
+
+
+ try
+ {
+ conn = newConnection();
+ PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_BINDINGS);
+ stmt.setString(1, exchange.getName().toString() );
+ stmt.setString(2, queue.getName().toString());
+ stmt.setString(3, routingKey == null ? null : routingKey.toString());
+ if(args != null)
+ {
+ /* This would be the Java 6 way of setting a Blob
+ Blob blobArgs = conn.createBlob();
+ blobArgs.setBytes(0, args.getDataAsBytes());
+ stmt.setBlob(4, blobArgs);
+ */
+ byte[] bytes = args.getDataAsBytes();
+ ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ stmt.setBinaryStream(4, bis, bytes.length);
+ }
+ else
+ {
+ stmt.setNull(4, Types.BLOB);
+ }
+
+ stmt.executeUpdate();
+ conn.commit();
+ stmt.close();
+ }
+ catch (SQLException e)
+ {
+ throw new AMQException("Error writing binding for AMQQueue with name " + queue.getName() + " to exchange "
+ + exchange.getName() + " to database: " + e, e);
+ }
+ finally
+ {
+ if(conn != null)
+ {
+ try
+ {
+ conn.close();
+ }
+ catch (SQLException e)
+ {
+ _logger.error(e);
+ }
+ }
+
+ }
+
+ }
+
+
+ }
+
+ public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args)
+ throws AMQException
+ {
+ Connection conn = null;
+
+
+ try
+ {
+ conn = newConnection();
+ // exchange_name varchar(255) not null, queue_name varchar(255) not null, binding_key varchar(255), arguments blob
+ PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_BINDINGS);
+ stmt.setString(1, exchange.getName().toString() );
+ stmt.setString(2, queue.getName().toString());
+ stmt.setString(3, routingKey == null ? null : routingKey.toString());
+
+
+ if(stmt.executeUpdate() != 1)
+ {
+ throw new AMQException("Queue binding for queue with name " + queue.getName() + " to exchange "
+ + exchange.getName() + " not found");
+ }
+ conn.commit();
+ stmt.close();
+ }
+ catch (SQLException e)
+ {
+ throw new AMQException("Error removing binding for AMQQueue with name " + queue.getName() + " to exchange "
+ + exchange.getName() + " in database: " + e, e);
+ }
+ finally
+ {
+ if(conn != null)
+ {
+ try
+ {
+ conn.close();
+ }
+ catch (SQLException e)
+ {
+ _logger.error(e);
+ }
+ }
+
+ }
+
+
+ }
+
+ public void createQueue(AMQQueue queue) throws AMQException
+ {
+ createQueue(queue, null);
+ }
+
+ public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQException
+ {
+ _logger.debug("public void createQueue(AMQQueue queue = " + queue + "): called");
+
+ if (_state != State.RECOVERING)
+ {
+ try
+ {
+ Connection conn = newConnection();
+
+ PreparedStatement stmt =
+ conn.prepareStatement(INSERT_INTO_QUEUE);
+
+ stmt.setString(1, queue.getName().toString());
+ stmt.setString(2, queue.getOwner() == null ? null : queue.getOwner().toString());
+
+ stmt.execute();
+
+ stmt.close();
+
+ conn.commit();
+
+ conn.close();
+ }
+ catch (SQLException e)
+ {
+ throw new AMQException("Error writing AMQQueue with name " + queue.getName() + " to database: " + e, e);
+ }
+ }
+ }
+
+ private Connection newConnection() throws SQLException
+ {
+ final Connection connection = DriverManager.getConnection(_connectionURL);
+ return connection;
+ }
+
+ public void removeQueue(final AMQQueue queue) throws AMQException
+ {
+ AMQShortString name = queue.getName();
+ _logger.debug("public void removeQueue(AMQShortString name = " + name + "): called");
+ Connection conn = null;
+
+
+ try
+ {
+ conn = newConnection();
+ PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_QUEUE);
+ stmt.setString(1, name.toString());
+ int results = stmt.executeUpdate();
+
+
+ if (results == 0)
+ {
+ throw new AMQException("Queue " + name + " not found");
+ }
+
+ conn.commit();
+ stmt.close();
+ }
+ catch (SQLException e)
+ {
+ throw new AMQException("Error writing deleting with name " + name + " from database: " + e, e);
+ }
+ finally
+ {
+ if(conn != null)
+ {
+ try
+ {
+ conn.close();
+ }
+ catch (SQLException e)
+ {
+ _logger.error(e);
+ }
+ }
+
+ }
+
+
+ }
+
+ public void enqueueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException
+ {
+ AMQShortString name = queue.getName();
+
+ boolean localTx = getOrCreateTransaction(context);
+ Connection conn = getConnection(context);
+ ConnectionWrapper connWrapper = (ConnectionWrapper) context.getPayload();
+
+ try
+ {
+ PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_QUEUE_ENTRY);
+ stmt.setString(1,name.toString());
+ stmt.setLong(2,messageId);
+ stmt.executeUpdate();
+ connWrapper.requiresCommit();
+
+ if(localTx)
+ {
+ commitTran(context);
+ }
+
+
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Enqueuing message " + messageId + " on queue " + name + "[Connection" + conn + "]");
+ }
+ }
+ catch (SQLException e)
+ {
+ if(localTx)
+ {
+ abortTran(context);
+ }
+ _logger.error("Failed to enqueue: " + e, e);
+ throw new AMQException("Error writing enqueued message with id " + messageId + " for queue " + name
+ + " to database", e);
+ }
+
+ }
+
+ public void dequeueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException
+ {
+ AMQShortString name = queue.getName();
+
+ boolean localTx = getOrCreateTransaction(context);
+ Connection conn = getConnection(context);
+ ConnectionWrapper connWrapper = (ConnectionWrapper) context.getPayload();
+
+ try
+ {
+ PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_QUEUE_ENTRY);
+ stmt.setString(1,name.toString());
+ stmt.setLong(2,messageId);
+ int results = stmt.executeUpdate();
+
+ connWrapper.requiresCommit();
+
+ if(results != 1)
+ {
+ throw new AMQException("Unable to find message with id " + messageId + " on queue " + name);
+ }
+
+ if(localTx)
+ {
+ commitTran(context);
+ }
+
+
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Dequeuing message " + messageId + " on queue " + name + "[Connection" + conn + "]");
+ }
+ }
+ catch (SQLException e)
+ {
+ if(localTx)
+ {
+ abortTran(context);
+ }
+ _logger.error("Failed to dequeue: " + e, e);
+ throw new AMQException("Error deleting enqueued message with id " + messageId + " for queue " + name
+ + " from database", e);
+ }
+
+ }
+
+ private static final class ConnectionWrapper
+ {
+ private final Connection _connection;
+ private boolean _requiresCommit;
+
+ public ConnectionWrapper(Connection conn)
+ {
+ _connection = conn;
+ }
+
+ public void setRequiresCommit()
+ {
+ _requiresCommit = true;
+ }
+
+ public boolean requiresCommit()
+ {
+ return _requiresCommit;
+ }
+
+ public Connection getConnection()
+ {
+ return _connection;
+ }
+ }
+
+ public void beginTran(StoreContext context) throws AMQException
+ {
+ if (context.getPayload() != null)
+ {
+ throw new AMQException("Fatal internal error: transactional context is not empty at beginTran: "
+ + context.getPayload());
+ }
+ else
+ {
+ try
+ {
+ Connection conn = newConnection();
+
+
+ context.setPayload(new ConnectionWrapper(conn));
+ }
+ catch (SQLException e)
+ {
+ throw new AMQException("Error starting transaction: " + e, e);
+ }
+ }
+ }
+
+ public void commitTran(StoreContext context) throws AMQException
+ {
+ ConnectionWrapper connWrapper = (ConnectionWrapper) context.getPayload();
+
+ if (connWrapper == null)
+ {
+ throw new AMQException("Fatal internal error: transactional context is empty at commitTran");
+ }
+
+ try
+ {
+ Connection conn = connWrapper.getConnection();
+ if(connWrapper.requiresCommit())
+ {
+ conn.commit();
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("commit tran completed");
+ }
+
+ }
+ conn.close();
+ }
+ catch (SQLException e)
+ {
+ throw new AMQException("Error commit tx: " + e, e);
+ }
+ finally
+ {
+ context.setPayload(null);
+ }
+ }
+
+ public void abortTran(StoreContext context) throws AMQException
+ {
+ ConnectionWrapper connWrapper = (ConnectionWrapper) context.getPayload();
+
+ if (connWrapper == null)
+ {
+ throw new AMQException("Fatal internal error: transactional context is empty at abortTran");
+ }
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("abort tran called: " + connWrapper.getConnection());
+ }
+
+ try
+ {
+ Connection conn = connWrapper.getConnection();
+ if(connWrapper.requiresCommit())
+ {
+ conn.rollback();
+ }
+
+ conn.close();
+ }
+ catch (SQLException e)
+ {
+ throw new AMQException("Error aborting transaction: " + e, e);
+ }
+ finally
+ {
+ context.setPayload(null);
+ }
+ }
+
+ public boolean inTran(StoreContext context)
+ {
+ return context.getPayload() != null;
+ }
+
+ public Long getNewMessageId()
+ {
+ return _messageId.getAndIncrement();
+ }
+
+ public void storeContentBodyChunk(StoreContext context,
+ Long messageId,
+ int index,
+ ContentChunk contentBody,
+ boolean lastContentBody) throws AMQException
+ {
+ boolean localTx = getOrCreateTransaction(context);
+ Connection conn = getConnection(context);
+ ConnectionWrapper connWrapper = (ConnectionWrapper) context.getPayload();
+
+ try
+ {
+ PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_MESSAGE_CONTENT);
+ stmt.setLong(1,messageId);
+ stmt.setInt(2, index);
+ byte[] chunkData = new byte[contentBody.getSize()];
+ contentBody.getData().duplicate().get(chunkData);
+ /* this would be the Java 6 way of doing things
+ Blob dataAsBlob = conn.createBlob();
+ dataAsBlob.setBytes(1L, chunkData);
+ stmt.setBlob(3, dataAsBlob);
+ */
+ ByteArrayInputStream bis = new ByteArrayInputStream(chunkData);
+ stmt.setBinaryStream(3, bis, chunkData.length);
+ stmt.executeUpdate();
+ connWrapper.requiresCommit();
+
+ if(localTx)
+ {
+ commitTran(context);
+ }
+ }
+ catch (SQLException e)
+ {
+ if(localTx)
+ {
+ abortTran(context);
+ }
+
+ throw new AMQException("Error writing AMQMessage with id " + messageId + " to database: " + e, e);
+ }
+
+ }
+
+ public void storeMessageMetaData(StoreContext context, Long messageId, MessageMetaData mmd)
+ throws AMQException
+ {
+
+ boolean localTx = getOrCreateTransaction(context);
+ Connection conn = getConnection(context);
+ ConnectionWrapper connWrapper = (ConnectionWrapper) context.getPayload();
+
+ try
+ {
+
+ PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_MESSAGE_META_DATA);
+ stmt.setLong(1,messageId);
+ stmt.setString(2, mmd.getMessagePublishInfo().getExchange().toString());
+ stmt.setString(3, mmd.getMessagePublishInfo().getRoutingKey().toString());
+ stmt.setShort(4, mmd.getMessagePublishInfo().isMandatory() ? (short) 1 : (short) 0);
+ stmt.setShort(5, mmd.getMessagePublishInfo().isImmediate() ? (short) 1 : (short) 0);
+
+ ContentHeaderBody headerBody = mmd.getContentHeaderBody();
+ final int bodySize = headerBody.getSize();
+ byte[] underlying = new byte[bodySize];
+ ByteBuffer buf = ByteBuffer.wrap(underlying);
+ headerBody.writePayload(buf);
+/*
+ Blob dataAsBlob = conn.createBlob();
+ dataAsBlob.setBytes(1L, underlying);
+ stmt.setBlob(6, dataAsBlob);
+*/
+ ByteArrayInputStream bis = new ByteArrayInputStream(underlying);
+ stmt.setBinaryStream(6,bis,underlying.length);
+
+ stmt.setInt(7, mmd.getContentChunkCount());
+
+ stmt.executeUpdate();
+ connWrapper.requiresCommit();
+
+ if(localTx)
+ {
+ commitTran(context);
+ }
+ }
+ catch (SQLException e)
+ {
+ if(localTx)
+ {
+ abortTran(context);
+ }
+
+ throw new AMQException("Error writing AMQMessage with id " + messageId + " to database: " + e, e);
+ }
+
+
+ }
+
+ public MessageMetaData getMessageMetaData(StoreContext context, Long messageId) throws AMQException
+ {
+ boolean localTx = getOrCreateTransaction(context);
+ Connection conn = getConnection(context);
+
+
+ try
+ {
+
+ PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_MESSAGE_META_DATA);
+ stmt.setLong(1,messageId);
+ ResultSet rs = stmt.executeQuery();
+
+ if(rs.next())
+ {
+ final AMQShortString exchange = new AMQShortString(rs.getString(1));
+ final AMQShortString routingKey = rs.getString(2) == null ? null : new AMQShortString(rs.getString(2));
+ final boolean mandatory = (rs.getShort(3) != (short)0);
+ final boolean immediate = (rs.getShort(4) != (short)0);
+ MessagePublishInfo info = new MessagePublishInfo()
+ {
+
+ public AMQShortString getExchange()
+ {
+ return exchange;
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+
+ }
+
+ public boolean isImmediate()
+ {
+ return immediate;
+ }
+
+ public boolean isMandatory()
+ {
+ return mandatory;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return routingKey;
+ }
+ } ;
+
+ Blob dataAsBlob = rs.getBlob(5);
+
+ byte[] dataAsBytes = dataAsBlob.getBytes(1,(int) dataAsBlob.length());
+ ByteBuffer buf = ByteBuffer.wrap(dataAsBytes);
+
+ ContentHeaderBody chb = ContentHeaderBody.createFromBuffer(buf, dataAsBytes.length);
+
+ if(localTx)
+ {
+ commitTran(context);
+ }
+
+ return new MessageMetaData(info, chb, rs.getInt(6));
+
+ }
+ else
+ {
+ if(localTx)
+ {
+ abortTran(context);
+ }
+ throw new AMQException("Metadata not found for message with id " + messageId);
+ }
+ }
+ catch (SQLException e)
+ {
+ if(localTx)
+ {
+ abortTran(context);
+ }
+
+ throw new AMQException("Error reading AMQMessage with id " + messageId + " from database: " + e, e);
+ }
+
+
+ }
+
+ public ContentChunk getContentBodyChunk(StoreContext context, Long messageId, int index) throws AMQException
+ {
+ boolean localTx = getOrCreateTransaction(context);
+ Connection conn = getConnection(context);
+
+
+ try
+ {
+
+ PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_MESSAGE_CONTENT);
+ stmt.setLong(1,messageId);
+ stmt.setInt(2, index);
+ ResultSet rs = stmt.executeQuery();
+
+ if(rs.next())
+ {
+ Blob dataAsBlob = rs.getBlob(1);
+
+ final int size = (int) dataAsBlob.length();
+ byte[] dataAsBytes = dataAsBlob.getBytes(1, size);
+ final ByteBuffer buf = ByteBuffer.wrap(dataAsBytes);
+
+ ContentChunk cb = new ContentChunk()
+ {
+
+ public int getSize()
+ {
+ return size;
+ }
+
+ public ByteBuffer getData()
+ {
+ return buf;
+ }
+
+ public void reduceToFit()
+ {
+
+ }
+ };
+
+ if(localTx)
+ {
+ commitTran(context);
+ }
+
+ return cb;
+
+ }
+ else
+ {
+ if(localTx)
+ {
+ abortTran(context);
+ }
+ throw new AMQException("Message not found for message with id " + messageId);
+ }
+ }
+ catch (SQLException e)
+ {
+ if(localTx)
+ {
+ abortTran(context);
+ }
+
+ throw new AMQException("Error reading AMQMessage with id " + messageId + " from database: " + e, e);
+ }
+
+
+
+ }
+
+ public boolean isPersistent()
+ {
+ return true;
+ }
+
+ private void checkNotClosed() throws MessageStoreClosedException
+ {
+ if (_closed.get())
+ {
+ throw new MessageStoreClosedException();
+ }
+ }
+
+
+ private static final class ProcessAction
+ {
+ private final AMQQueue _queue;
+ private final StoreContext _context;
+ private final AMQMessage _message;
+
+ public ProcessAction(AMQQueue queue, StoreContext context, AMQMessage message)
+ {
+ _queue = queue;
+ _context = context;
+ _message = message;
+ }
+
+ public void process() throws AMQException
+ {
+ _queue.enqueue(_context, _message);
+
+ }
+
+ }
+
+
+ private void deliverMessages(final StoreContext context, Map<AMQShortString, AMQQueue> queues)
+ throws SQLException, AMQException
+ {
+ Map<Long, AMQMessage> msgMap = new HashMap<Long,AMQMessage>();
+ List<ProcessAction> actions = new ArrayList<ProcessAction>();
+
+ Map<AMQShortString, Integer> queueRecoveries = new TreeMap<AMQShortString, Integer>();
+
+ final boolean inLocaltran = inTran(context);
+ Connection conn = null;
+ try
+ {
+
+ if(inLocaltran)
+ {
+ conn = getConnection(context);
+ }
+ else
+ {
+ conn = newConnection();
+ }
+
+
+ MessageHandleFactory messageHandleFactory = new MessageHandleFactory();
+ long maxId = 1;
+
+ TransactionalContext txnContext = new NonTransactionalContext(this, new StoreContext(), null, null);
+
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(SELECT_FROM_QUEUE_ENTRY);
+
+
+ while (rs.next())
+ {
+
+
+
+ AMQShortString queueName = new AMQShortString(rs.getString(1));
+
+
+ AMQQueue queue = queues.get(queueName);
+ if (queue == null)
+ {
+ queue = AMQQueueFactory.createAMQQueueImpl(queueName, false, null, false, _virtualHost, null);
+
+ _virtualHost.getQueueRegistry().registerQueue(queue);
+ queues.put(queueName, queue);
+ }
+
+ long messageId = rs.getLong(2);
+ maxId = Math.max(maxId, messageId);
+ AMQMessage message = msgMap.get(messageId);
+
+ if(message != null)
+ {
+ message.incrementReference();
+ }
+ else
+ {
+ message = new AMQMessage(messageId, this, messageHandleFactory, txnContext);
+ msgMap.put(messageId,message);
+ }
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("On recovery, delivering " + message.getMessageId() + " to " + queue.getName());
+ }
+
+ if (_logger.isInfoEnabled())
+ {
+ Integer count = queueRecoveries.get(queueName);
+ if (count == null)
+ {
+ count = 0;
+ }
+
+ queueRecoveries.put(queueName, ++count);
+
+ }
+
+ actions.add(new ProcessAction(queue, context, message));
+
+ }
+
+ for(ProcessAction action : actions)
+ {
+ action.process();
+ }
+
+ _messageId.set(maxId + 1);
+ }
+ catch (SQLException e)
+ {
+ _logger.error("Error: " + e, e);
+ throw e;
+ }
+ finally
+ {
+ if (inLocaltran && conn != null)
+ {
+ conn.close();
+ }
+ }
+
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Recovered message counts: " + queueRecoveries);
+ }
+ }
+
+ private Connection getConnection(final StoreContext context)
+ {
+ return ((ConnectionWrapper)context.getPayload()).getConnection();
+ }
+
+ private boolean getOrCreateTransaction(StoreContext context) throws AMQException
+ {
+
+ ConnectionWrapper tx = (ConnectionWrapper) context.getPayload();
+ if (tx == null)
+ {
+ beginTran(context);
+ return true;
+ }
+
+ return false;
+ }
+
+ private synchronized void stateTransition(State requiredState, State newState) throws AMQException
+ {
+ if (_state != requiredState)
+ {
+ throw new AMQException("Cannot transition to the state: " + newState + "; need to be in state: " + requiredState
+ + "; currently in state: " + _state);
+ }
+
+ _state = newState;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java
new file mode 100644
index 0000000000..4691f02952
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java
@@ -0,0 +1,235 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.store;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.server.queue.MessageMetaData;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+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<ContentChunk>> _contentBodyMap;
+
+ private final AtomicLong _messageId = new AtomicLong(1);
+ private AtomicBoolean _closed = new AtomicBoolean(false);
+
+ 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<ContentChunk>>(DEFAULT_HASHTABLE_CAPACITY);
+ }
+
+ public void configure(String base, VirtualHostConfiguration config)
+ {
+ int hashtableCapacity = config.getStoreConfiguration().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<ContentChunk>>(hashtableCapacity);
+ }
+
+ public void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception
+ {
+ configure(base, config);
+ }
+
+ public void close() throws Exception
+ {
+ _closed.getAndSet(true);
+ if (_metaDataMap != null)
+ {
+ _metaDataMap.clear();
+ _metaDataMap = null;
+ }
+ if (_contentBodyMap != null)
+ {
+ _contentBodyMap.clear();
+ _contentBodyMap = null;
+ }
+ }
+
+ public void removeMessage(StoreContext context, Long messageId) throws AMQException
+ {
+ checkNotClosed();
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Removing message with id " + messageId);
+ }
+ _metaDataMap.remove(messageId);
+ _contentBodyMap.remove(messageId);
+ }
+
+ public void createExchange(Exchange exchange) throws AMQException
+ {
+
+ }
+
+ public void removeExchange(Exchange exchange) throws AMQException
+ {
+
+ }
+
+ public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+
+ }
+
+ public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+
+ }
+
+
+ public void createQueue(AMQQueue queue) throws AMQException
+ {
+ // Not requred to do anything
+ }
+
+ public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void removeQueue(final AMQQueue queue) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void enqueueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void dequeueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void beginTran(StoreContext context) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void commitTran(StoreContext context) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void abortTran(StoreContext context) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public boolean inTran(StoreContext context)
+ {
+ return false;
+ }
+
+ public List<AMQQueue> createQueues() throws AMQException
+ {
+ return null;
+ }
+
+ public Long getNewMessageId()
+ {
+ return _messageId.getAndIncrement();
+ }
+
+ public void storeContentBodyChunk(StoreContext context, Long messageId, int index, ContentChunk contentBody, boolean lastContentBody)
+ throws AMQException
+ {
+ checkNotClosed();
+ List<ContentChunk> bodyList = _contentBodyMap.get(messageId);
+
+ if (bodyList == null && lastContentBody)
+ {
+ _contentBodyMap.put(messageId, Collections.singletonList(contentBody));
+ }
+ else
+ {
+ if (bodyList == null)
+ {
+ bodyList = new ArrayList<ContentChunk>();
+ _contentBodyMap.put(messageId, bodyList);
+ }
+
+ bodyList.add(index, contentBody);
+ }
+ }
+
+ public void storeMessageMetaData(StoreContext context, Long messageId, MessageMetaData messageMetaData)
+ throws AMQException
+ {
+ checkNotClosed();
+ _metaDataMap.put(messageId, messageMetaData);
+ }
+
+ public MessageMetaData getMessageMetaData(StoreContext context, Long messageId) throws AMQException
+ {
+ checkNotClosed();
+ return _metaDataMap.get(messageId);
+ }
+
+ public ContentChunk getContentBodyChunk(StoreContext context, Long messageId, int index) throws AMQException
+ {
+ checkNotClosed();
+ List<ContentChunk> bodyList = _contentBodyMap.get(messageId);
+ return bodyList.get(index);
+ }
+
+ public boolean isPersistent()
+ {
+ return false;
+ }
+
+ private void checkNotClosed() throws MessageStoreClosedException
+ {
+ if (_closed.get())
+ {
+ throw new MessageStoreClosedException();
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java
new file mode 100644
index 0000000000..5a1b54b298
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java
@@ -0,0 +1,277 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.store;
+
+import org.apache.commons.configuration.Configuration;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.queue.MessageMetaData;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+/**
+ * MessageStore defines the interface to a storage area, which can be used to preserve the state of messages, queues
+ * and exchanges in a transactional manner.
+ *
+ * <p/>All message store, remove, enqueue and dequeue operations are carried out against a {@link StoreContext} which
+ * encapsulates the transactional context they are performed in. Many such operations can be carried out in a single
+ * transaction.
+ *
+ * <p/>The storage and removal of queues and exchanges, are not carried out in a transactional context.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Accept transaction boundary demarcations: Begin, Commit, Abort.
+ * <tr><td> Store and remove queues.
+ * <tr><td> Store and remove exchanges.
+ * <tr><td> Store and remove messages.
+ * <tr><td> Bind and unbind queues to exchanges.
+ * <tr><td> Enqueue and dequeue messages to queues.
+ * <tr><td> Generate message identifiers.
+ * </table>
+ */
+public interface MessageStore
+{
+ /**
+ * Called after instantiation in order to configure the message store. A particular implementation can define
+ * whatever parameters it wants.
+ *
+ * @param virtualHost The virtual host using 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 hostConfig The apache commons configuration object.
+ *
+ * @throws Exception If any error occurs that means the store is unable to configure itself.
+ */
+ void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration hostConfig) throws Exception;
+
+ /**
+ * Called to close and cleanup any resources used by the message store.
+ *
+ * @throws Exception If the close fails.
+ */
+ void close() throws Exception;
+
+ /**
+ * Removes the specified message from the store in the given transactional store context.
+ *
+ * @param storeContext The transactional context to remove the message in.
+ * @param messageId Identifies the message to remove.
+ *
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void removeMessage(StoreContext storeContext, Long messageId) throws AMQException;
+
+ /**
+ * Makes the specified exchange persistent.
+ *
+ * @param exchange The exchange to persist.
+ *
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void createExchange(Exchange exchange) throws AMQException;
+
+ /**
+ * Removes the specified persistent exchange.
+ *
+ * @param exchange The exchange to remove.
+ *
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void removeExchange(Exchange exchange) throws AMQException;
+
+ /**
+ * Binds the specified queue to an exchange with a routing key.
+ *
+ * @param exchange The exchange to bind to.
+ * @param routingKey The routing key to bind by.
+ * @param queue The queue to bind.
+ * @param args Additional parameters.
+ *
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException;
+
+ /**
+ * Unbinds the specified from an exchange under a particular routing key.
+ *
+ * @param exchange The exchange to unbind from.
+ * @param routingKey The routing key to unbind.
+ * @param queue The queue to unbind.
+ * @param args Additonal parameters.
+ *
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException;
+
+ /**
+ * Makes the specified queue persistent.
+ *
+ * @param queue The queue to store.
+ *
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void createQueue(AMQQueue queue) throws AMQException;
+
+ /**
+ * Makes the specified queue persistent.
+ *
+ * @param queue The queue to store.
+ *
+ * @param arguments The additional arguments to the binding
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void createQueue(AMQQueue queue, FieldTable arguments) throws AMQException;
+
+ /**
+ * Removes the specified queue from the persistent store.
+ *
+ * @param queue The queue to remove.
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void removeQueue(final AMQQueue queue) throws AMQException;
+
+ /**
+ * Places a message onto a specified queue, in a given transactional context.
+ *
+ * @param context The transactional context for the operation.
+ * @param queue The queue to place the message on.
+ * @param messageId The message to enqueue.
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void enqueueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException;
+
+ /**
+ * Extracts a message from a specified queue, in a given transactional context.
+ *
+ * @param context The transactional context for the operation.
+ * @param queue The queue to place the message on.
+ * @param messageId The message to dequeue.
+ * @throws AMQException If the operation fails for any reason, or if the specified message does not exist.
+ */
+ void dequeueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException;
+
+ /**
+ * Begins a transactional context.
+ *
+ * @param context The transactional context to begin.
+ *
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void beginTran(StoreContext context) throws AMQException;
+
+ /**
+ * Commits all operations performed within a given transactional context.
+ *
+ * @param context The transactional context to commit all operations for.
+ *
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void commitTran(StoreContext context) throws AMQException;
+
+ /**
+ * Abandons all operations performed within a given transactional context.
+ *
+ * @param context The transactional context to abandon.
+ *
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void abortTran(StoreContext context) throws AMQException;
+
+ /**
+ * Tests a transactional context to see if it has been begun but not yet committed or aborted.
+ *
+ * @param context The transactional context to test.
+ *
+ * @return <tt>true</tt> if the transactional context is live, <tt>false</tt> otherwise.
+ */
+ boolean inTran(StoreContext context);
+
+ /**
+ * Return a valid, currently unused message id.
+ *
+ * @return A fresh message id.
+ */
+ Long getNewMessageId();
+
+ /**
+ * Stores a chunk of message data.
+ *
+ * @param context The transactional context for the operation.
+ * @param messageId The message to store the data for.
+ * @param index The index of the data chunk.
+ * @param contentBody The content of the data chunk.
+ * @param lastContentBody Flag to indicate that this is the last such chunk for the message.
+ *
+ * @throws AMQException If the operation fails for any reason, or if the specified message does not exist.
+ */
+ void storeContentBodyChunk(StoreContext context, Long messageId, int index, ContentChunk contentBody,
+ boolean lastContentBody) throws AMQException;
+
+ /**
+ * Stores message meta-data.
+ *
+ * @param context The transactional context for the operation.
+ * @param messageId The message to store the data for.
+ * @param messageMetaData The message meta data to store.
+ *
+ * @throws AMQException If the operation fails for any reason, or if the specified message does not exist.
+ */
+ void storeMessageMetaData(StoreContext context, Long messageId, MessageMetaData messageMetaData) throws AMQException;
+
+ /**
+ * Retrieves message meta-data.
+ *
+ * @param context The transactional context for the operation.
+ * @param messageId The message to get the meta-data for.
+ *
+ * @return The message meta data.
+ *
+ * @throws AMQException If the operation fails for any reason, or if the specified message does not exist.
+ */
+ MessageMetaData getMessageMetaData(StoreContext context, Long messageId) throws AMQException;
+
+ /**
+ * Retrieves a chunk of message data.
+ *
+ * @param context The transactional context for the operation.
+ * @param messageId The message to get the data chunk for.
+ * @param index The offset index of the data chunk within the message.
+ *
+ * @return A chunk of message data.
+ *
+ * @throws AMQException If the operation fails for any reason, or if the specified message does not exist.
+ */
+ ContentChunk getContentBodyChunk(StoreContext context, Long messageId, int index) throws AMQException;
+
+ /**
+ * Is this store capable of persisting the data
+ *
+ * @return true if this store is capable of persisting data
+ */
+ boolean isPersistent();
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java
new file mode 100644
index 0000000000..3d1538c7eb
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java
@@ -0,0 +1,36 @@
+package org.apache.qpid.server.store;
+
+import org.apache.qpid.AMQException;/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * NOTE: this class currently extends AMQException but
+ * we should be using AMQExceptions internally in the code base for Protocol errors hence
+ * the message store interface should throw a different super class which this should be
+ * moved to reflect
+ */
+public class MessageStoreClosedException extends AMQException
+{
+ public MessageStoreClosedException()
+ {
+ super("Message store closed");
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreContext.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreContext.java
new file mode 100644
index 0000000000..fdb56a1a55
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreContext.java
@@ -0,0 +1,71 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.store;
+
+import org.apache.log4j.Logger;
+
+/**
+ * A context that the store can use to associate with a transactional context. For example, it could store
+ * some kind of txn id.
+ *
+ * @author Apache Software Foundation
+ */
+public class StoreContext
+{
+ private static final Logger _logger = Logger.getLogger(StoreContext.class);
+
+ private String _name;
+ private Object _payload;
+
+ public StoreContext()
+ {
+ _name = "StoreContext";
+ }
+
+ public StoreContext(String name)
+ {
+ _name = name;
+ }
+
+ public Object getPayload()
+ {
+ return _payload;
+ }
+
+ public void setPayload(Object payload)
+ {
+ if(_logger.isDebugEnabled())
+ {
+ _logger.debug("public void setPayload(Object payload = " + payload + "): called");
+ }
+ _payload = payload;
+ }
+
+ /**
+ * Prints out the transactional context as a string, mainly for debugging purposes.
+ *
+ * @return The transactional context as a string.
+ */
+ public String toString()
+ {
+ return "<_name = " + _name + ", _payload = " + _payload + ">";
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java
new file mode 100644
index 0000000000..fbc8b3af7d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java
@@ -0,0 +1,29 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.subscription;
+
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.AMQException;
+
+public interface ClientDeliveryMethod
+{
+ void deliverToClient(final Subscription sub, final QueueEntry entry, final long deliveryTag) throws AMQException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java
new file mode 100644
index 0000000000..e2ed4104de
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java
@@ -0,0 +1,28 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.subscription;
+
+import org.apache.qpid.server.queue.QueueEntry;
+
+public interface RecordDeliveryMethod
+{
+ void recordMessageDelivery(final Subscription sub, final QueueEntry entry, final long deliveryTag);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription.java
new file mode 100644
index 0000000000..9419572399
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription.java
@@ -0,0 +1,96 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.subscription;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueEntry;
+
+public interface Subscription
+{
+
+
+ public static enum State
+ {
+ ACTIVE,
+ SUSPENDED,
+ CLOSED
+ }
+
+ public static interface StateListener
+ {
+ public void stateChange(Subscription sub, State oldState, State newState);
+ }
+
+ AMQQueue getQueue();
+
+ QueueEntry.SubscriptionAcquiredState getOwningState();
+
+ void setQueue(AMQQueue queue);
+
+ AMQChannel getChannel();
+
+ AMQShortString getConsumerTag();
+
+ boolean isSuspended();
+
+ boolean hasInterest(QueueEntry msg);
+
+ boolean isAutoClose();
+
+ boolean isClosed();
+
+ boolean isBrowser();
+
+ void close();
+
+ boolean filtersMessages();
+
+ void send(QueueEntry msg) throws AMQException;
+
+ void queueDeleted(AMQQueue queue);
+
+
+ boolean wouldSuspend(QueueEntry msg);
+
+ void getSendLock();
+ void releaseSendLock();
+
+ void resend(final QueueEntry entry) throws AMQException;
+
+ void restoreCredit(final QueueEntry queueEntry);
+
+ void setStateListener(final StateListener listener);
+
+ public State getState();
+
+ QueueEntry getLastSeenEntry();
+
+ boolean setLastSeenEntry(QueueEntry expected, QueueEntry newValue);
+
+
+ boolean isActive();
+
+
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactory.java
new file mode 100644
index 0000000000..ce0362d73f
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactory.java
@@ -0,0 +1,59 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.subscription;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.flow.FlowCreditManager;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.AMQChannel;
+
+/**
+ * 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,
+ AMQShortString consumerTag,
+ boolean acks,
+ FieldTable filters,
+ boolean noLocal, FlowCreditManager creditManager) throws AMQException;
+
+
+ Subscription createSubscription(AMQChannel channel,
+ AMQProtocolSession protocolSession,
+ AMQShortString consumerTag,
+ boolean acks,
+ FieldTable filters,
+ boolean noLocal,
+ FlowCreditManager creditManager,
+ ClientDeliveryMethod clientMethod,
+ RecordDeliveryMethod recordMethod
+ )
+ throws AMQException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactoryImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactoryImpl.java
new file mode 100644
index 0000000000..5badbad642
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactoryImpl.java
@@ -0,0 +1,103 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.subscription;
+
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.flow.FlowCreditManager;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.subscription.SubscriptionFactory;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.common.AMQPFilterTypes;
+
+public class SubscriptionFactoryImpl implements SubscriptionFactory
+{
+
+ /* private SubscriptionFactoryImpl()
+ {
+
+ }*/
+
+ public Subscription createSubscription(int channelId, AMQProtocolSession protocolSession,
+ AMQShortString consumerTag, boolean acks, FieldTable filters,
+ boolean noLocal, FlowCreditManager creditManager) throws AMQException
+ {
+ AMQChannel channel = protocolSession.getChannel(channelId);
+ if (channel == null)
+ {
+ throw new AMQException(AMQConstant.NOT_FOUND, "channel :" + channelId + " not found in protocol session");
+ }
+ ClientDeliveryMethod clientMethod = channel.getClientDeliveryMethod();
+ RecordDeliveryMethod recordMethod = channel.getRecordDeliveryMethod();
+
+
+ return createSubscription(channel, protocolSession, consumerTag, acks, filters,
+ noLocal,
+ creditManager,
+ clientMethod,
+ recordMethod
+ );
+ }
+
+ public Subscription createSubscription(final AMQChannel channel,
+ final AMQProtocolSession protocolSession,
+ final AMQShortString consumerTag,
+ final boolean acks,
+ final FieldTable filters,
+ final boolean noLocal,
+ final FlowCreditManager creditManager,
+ final ClientDeliveryMethod clientMethod,
+ final RecordDeliveryMethod recordMethod
+ )
+ throws AMQException
+ {
+ boolean isBrowser;
+
+ if (filters != null)
+ {
+ Boolean isBrowserObj = (Boolean) filters.get(AMQPFilterTypes.NO_CONSUME.getValue());
+ isBrowser = (isBrowserObj != null) && isBrowserObj.booleanValue();
+ }
+ else
+ {
+ isBrowser = false;
+ }
+
+ if(isBrowser)
+ {
+ return new SubscriptionImpl.BrowserSubscription(channel, protocolSession, consumerTag, filters, noLocal, creditManager, clientMethod, recordMethod);
+ }
+ else if(acks)
+ {
+ return new SubscriptionImpl.AckSubscription(channel, protocolSession, consumerTag, filters, noLocal, creditManager, clientMethod, recordMethod);
+ }
+ else
+ {
+ return new SubscriptionImpl.NoAckSubscription(channel, protocolSession, consumerTag, filters, noLocal, creditManager, clientMethod, recordMethod);
+ }
+ }
+
+
+ public static final SubscriptionFactoryImpl INSTANCE = new SubscriptionFactoryImpl();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java
new file mode 100644
index 0000000000..a616c2ea35
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java
@@ -0,0 +1,617 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.subscription;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.common.AMQPFilterTypes;
+import org.apache.qpid.common.ClientProperties;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.flow.FlowCreditManager;
+import org.apache.qpid.server.filter.FilterManager;
+import org.apache.qpid.server.filter.FilterManagerFactory;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.store.StoreContext;
+
+/**
+ * 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 abstract class SubscriptionImpl implements Subscription, FlowCreditManager.FlowCreditManagerListener
+{
+
+ private StateListener _stateListener = new StateListener()
+ {
+
+ public void stateChange(Subscription sub, State oldState, State newState)
+ {
+
+ }
+ };
+
+
+ private final AtomicReference<State> _state = new AtomicReference<State>(State.ACTIVE);
+ private final AtomicReference<QueueEntry> _queueContext = new AtomicReference<QueueEntry>(null);
+ private final ClientDeliveryMethod _deliveryMethod;
+ private final RecordDeliveryMethod _recordMethod;
+
+ private QueueEntry.SubscriptionAcquiredState _owningState = new QueueEntry.SubscriptionAcquiredState(this);
+ private final Lock _stateChangeLock;
+
+ static final class BrowserSubscription extends SubscriptionImpl
+ {
+ public BrowserSubscription(AMQChannel channel, AMQProtocolSession protocolSession,
+ AMQShortString consumerTag, FieldTable filters,
+ boolean noLocal, FlowCreditManager creditManager,
+ ClientDeliveryMethod deliveryMethod,
+ RecordDeliveryMethod recordMethod)
+ throws AMQException
+ {
+ super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod);
+ }
+
+
+ public boolean isBrowser()
+ {
+ return true;
+ }
+
+ /**
+ * 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 The message to send
+ * @throws AMQException
+ */
+ @Override
+ public void send(QueueEntry msg) throws AMQException
+ {
+ // We don't decrement the reference here as we don't want to consume the message
+ // but we do want to send it to the client.
+
+ synchronized (getChannel())
+ {
+ long deliveryTag = getChannel().getNextDeliveryTag();
+ sendToClient(msg, deliveryTag);
+ }
+
+ }
+
+ @Override
+ public boolean wouldSuspend(QueueEntry msg)
+ {
+ return false;
+ }
+
+ }
+
+ public static class NoAckSubscription extends SubscriptionImpl
+ {
+ public NoAckSubscription(AMQChannel channel, AMQProtocolSession protocolSession,
+ AMQShortString consumerTag, FieldTable filters,
+ boolean noLocal, FlowCreditManager creditManager,
+ ClientDeliveryMethod deliveryMethod,
+ RecordDeliveryMethod recordMethod)
+ throws AMQException
+ {
+ super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod);
+ }
+
+
+ public boolean isBrowser()
+ {
+ return false;
+ }
+
+ /**
+ * 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 entry The message to send
+ * @throws AMQException
+ */
+ @Override
+ public void send(QueueEntry entry) throws AMQException
+ {
+
+ StoreContext storeContext = getChannel().getStoreContext();
+ try
+ { // 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.
+ entry.dequeue(storeContext);
+
+
+ synchronized (getChannel())
+ {
+ long deliveryTag = getChannel().getNextDeliveryTag();
+
+ sendToClient(entry, deliveryTag);
+
+ }
+ entry.dispose(storeContext);
+ }
+ finally
+ {
+ //Only set delivered if it actually was writen successfully..
+ // using a try->finally would set it even if an error occured.
+ // Is this what we want?
+
+ entry.setDeliveredToSubscription();
+ }
+ }
+
+ @Override
+ public boolean wouldSuspend(QueueEntry msg)
+ {
+ return false;
+ }
+
+ }
+
+ static final class AckSubscription extends SubscriptionImpl
+ {
+ public AckSubscription(AMQChannel channel, AMQProtocolSession protocolSession,
+ AMQShortString consumerTag, FieldTable filters,
+ boolean noLocal, FlowCreditManager creditManager,
+ ClientDeliveryMethod deliveryMethod,
+ RecordDeliveryMethod recordMethod)
+ throws AMQException
+ {
+ super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod);
+ }
+
+
+ public boolean isBrowser()
+ {
+ return false;
+ }
+
+
+ /**
+ * 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 entry The message to send
+ * @throws AMQException
+ */
+ @Override
+ public void send(QueueEntry entry) throws AMQException
+ {
+
+ try
+ { // 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.
+
+ synchronized (getChannel())
+ {
+ long deliveryTag = getChannel().getNextDeliveryTag();
+
+
+ recordMessageDelivery(entry, deliveryTag);
+ sendToClient(entry, deliveryTag);
+
+
+ }
+ }
+ finally
+ {
+ //Only set delivered if it actually was writen successfully..
+ // using a try->finally would set it even if an error occured.
+ // Is this what we want?
+
+ entry.setDeliveredToSubscription();
+ }
+ }
+
+
+ }
+
+
+ private static final Logger _logger = Logger.getLogger(SubscriptionImpl.class);
+
+ private final AMQChannel _channel;
+
+ private final AMQShortString _consumerTag;
+
+
+ private final boolean _noLocal;
+
+ private final FlowCreditManager _creditManager;
+
+ private FilterManager _filters;
+
+ private final Boolean _autoClose;
+
+
+ private static final String CLIENT_PROPERTIES_INSTANCE = ClientProperties.instance.toString();
+
+ private AMQQueue _queue;
+ private final AtomicBoolean _deleted = new AtomicBoolean(false);
+
+
+
+
+ public SubscriptionImpl(AMQChannel channel , AMQProtocolSession protocolSession,
+ AMQShortString consumerTag, FieldTable arguments,
+ boolean noLocal, FlowCreditManager creditManager,
+ ClientDeliveryMethod deliveryMethod,
+ RecordDeliveryMethod recordMethod)
+ throws AMQException
+ {
+
+ _channel = channel;
+ _consumerTag = consumerTag;
+
+ _creditManager = creditManager;
+ creditManager.addStateListener(this);
+
+ _noLocal = noLocal;
+
+
+ _filters = FilterManagerFactory.createManager(arguments);
+
+ _deliveryMethod = deliveryMethod;
+ _recordMethod = recordMethod;
+
+
+ _stateChangeLock = new ReentrantLock();
+
+
+ if (arguments != null)
+ {
+ Object autoClose = arguments.get(AMQPFilterTypes.AUTO_CLOSE.getValue());
+ if (autoClose != null)
+ {
+ _autoClose = (Boolean) autoClose;
+ }
+ else
+ {
+ _autoClose = false;
+ }
+ }
+ else
+ {
+ _autoClose = false;
+ }
+
+
+ }
+
+
+
+ public synchronized void setQueue(AMQQueue queue)
+ {
+ if(getQueue() != null)
+ {
+ throw new IllegalStateException("Attempt to set queue for subscription " + this + " to " + queue + "when already set to " + getQueue());
+ }
+ _queue = queue;
+ }
+
+ public String toString()
+ {
+ String subscriber = "[channel=" + _channel +
+ ", consumerTag=" + _consumerTag +
+ ", session=" + getProtocolSession().getKey() ;
+
+ return subscriber + "]";
+ }
+
+ /**
+ * 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 The message to send
+ * @throws AMQException
+ */
+ abstract public void send(QueueEntry msg) throws AMQException;
+
+
+ public boolean isSuspended()
+ {
+ return !isActive() || _channel.isSuspended() || _deleted.get();
+ }
+
+ /**
+ * Callback indicating that a queue has been deleted.
+ *
+ * @param queue The queue to delete
+ */
+ public void queueDeleted(AMQQueue queue)
+ {
+ _deleted.set(true);
+// _channel.queueDeleted(queue);
+ }
+
+ public boolean filtersMessages()
+ {
+ return _filters != null || _noLocal;
+ }
+
+ public boolean hasInterest(QueueEntry entry)
+ {
+ //check that the message hasn't been rejected
+ if (entry.isRejectedBy(this))
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Subscription:" + debugIdentity() + " rejected message:" + entry.debugIdentity());
+ }
+// return false;
+ }
+
+
+
+ //todo - client id should be recoreded and this test removed but handled below
+ if (_noLocal)
+ {
+ final Object publisherId = entry.getMessage().getPublisherClientInstance();
+
+ // We don't want local messages so check to see if message is one we sent
+ Object localInstance;
+
+ if (publisherId != null && (getProtocolSession().getClientProperties() != null) &&
+ (localInstance = getProtocolSession().getClientProperties().getObject(CLIENT_PROPERTIES_INSTANCE)) != null)
+ {
+ if(publisherId.equals(localInstance))
+ {
+ return false;
+ }
+ }
+ else
+ {
+
+ localInstance = getProtocolSession().getClientIdentifier();
+ //todo - client id should be recoreded and this test removed but handled here
+
+
+ if (localInstance != null && localInstance.equals(entry.getMessage().getPublisherIdentifier()))
+ {
+ return false;
+ }
+ }
+
+
+ }
+
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("(" + debugIdentity() + ") checking filters for message (" + entry.debugIdentity());
+ }
+ return checkFilters(entry);
+
+ }
+
+ private String id = String.valueOf(System.identityHashCode(this));
+
+ private String debugIdentity()
+ {
+ return id;
+ }
+
+ private boolean checkFilters(QueueEntry msg)
+ {
+ return (_filters == null) || _filters.allAllow(msg.getMessage());
+ }
+
+ public boolean isAutoClose()
+ {
+ return _autoClose;
+ }
+
+ public FlowCreditManager getCreditManager()
+ {
+ return _creditManager;
+ }
+
+
+ public void close()
+ {
+ boolean closed = false;
+ State state = getState();
+
+ _stateChangeLock.lock();
+ try
+ {
+ while(!closed && state != State.CLOSED)
+ {
+ closed = _state.compareAndSet(state, State.CLOSED);
+ if(!closed)
+ {
+ state = getState();
+ }
+ else
+ {
+ _stateListener.stateChange(this,state, State.CLOSED);
+ }
+ }
+ _creditManager.removeListener(this);
+ }
+ finally
+ {
+ _stateChangeLock.unlock();
+ }
+
+
+ if (closed)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Called close() on a closed subscription");
+ }
+
+ return;
+ }
+
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Closing subscription (" + debugIdentity() + "):" + this);
+ }
+ }
+
+ public boolean isClosed()
+ {
+ return getState() == State.CLOSED;
+ }
+
+
+ public boolean wouldSuspend(QueueEntry msg)
+ {
+ return !_creditManager.useCreditForMessage(msg.getMessage());//_channel.wouldSuspend(msg.getMessage());
+ }
+
+ public void getSendLock()
+ {
+ _stateChangeLock.lock();
+ }
+
+ public void releaseSendLock()
+ {
+ _stateChangeLock.unlock();
+ }
+
+ public void resend(final QueueEntry entry) throws AMQException
+ {
+ _queue.resend(entry, this);
+ }
+
+ public AMQChannel getChannel()
+ {
+ return _channel;
+ }
+
+ public AMQShortString getConsumerTag()
+ {
+ return _consumerTag;
+ }
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _channel.getProtocolSession();
+ }
+
+ public AMQQueue getQueue()
+ {
+ return _queue;
+ }
+
+ public void restoreCredit(final QueueEntry queueEntry)
+ {
+ _creditManager.addCredit(1, queueEntry.getSize());
+ }
+
+
+ public void creditStateChanged(boolean hasCredit)
+ {
+
+ if(hasCredit)
+ {
+ if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE))
+ {
+ _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE);
+ }
+ else
+ {
+ // this is a hack to get round the issue of increasing bytes credit
+ _stateListener.stateChange(this, State.ACTIVE, State.ACTIVE);
+ }
+ }
+ else
+ {
+ if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED))
+ {
+ _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED);
+ }
+ }
+ }
+
+ public State getState()
+ {
+ return _state.get();
+ }
+
+
+ public void setStateListener(final StateListener listener)
+ {
+ _stateListener = listener;
+ }
+
+
+ public QueueEntry getLastSeenEntry()
+ {
+ return _queueContext.get();
+ }
+
+ public boolean setLastSeenEntry(QueueEntry expected, QueueEntry newvalue)
+ {
+ return _queueContext.compareAndSet(expected,newvalue);
+ }
+
+
+ protected void sendToClient(final QueueEntry entry, final long deliveryTag)
+ throws AMQException
+ {
+ _deliveryMethod.deliverToClient(this,entry,deliveryTag);
+ }
+
+
+ protected void recordMessageDelivery(final QueueEntry entry, final long deliveryTag)
+ {
+ _recordMethod.recordMessageDelivery(this,entry,deliveryTag);
+ }
+
+
+ public boolean isActive()
+ {
+ return getState() == State.ACTIVE;
+ }
+
+ public QueueEntry.SubscriptionAcquiredState getOwningState()
+ {
+ return _owningState;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java
new file mode 100644
index 0000000000..3fbb6bfa4a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java
@@ -0,0 +1,247 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.subscription;
+
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.subscription.Subscription;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.nio.ByteBuffer;
+
+public class SubscriptionList
+{
+
+ private final SubscriptionNode _head = new SubscriptionNode();
+
+ private AtomicReference<SubscriptionNode> _tail = new AtomicReference<SubscriptionNode>(_head);
+ private final AMQQueue _queue;
+ private AtomicInteger _size = new AtomicInteger();
+
+
+ public final class SubscriptionNode
+ {
+ private final AtomicBoolean _deleted = new AtomicBoolean();
+ private final AtomicReference<SubscriptionNode> _next = new AtomicReference<SubscriptionNode>();
+ private final Subscription _sub;
+
+
+ public SubscriptionNode()
+ {
+
+ _sub = null;
+ _deleted.set(true);
+ }
+
+ public SubscriptionNode(final Subscription sub)
+ {
+ _sub = sub;
+ }
+
+
+ public SubscriptionNode getNext()
+ {
+
+ SubscriptionNode next = nextNode();
+ while(next != null && next.isDeleted())
+ {
+
+ final SubscriptionNode newNext = next.nextNode();
+ if(newNext != null)
+ {
+ _next.compareAndSet(next, newNext);
+ next = nextNode();
+ }
+ else
+ {
+ next = null;
+ }
+
+ }
+ return next;
+ }
+
+ private SubscriptionNode nextNode()
+ {
+ return _next.get();
+ }
+
+ public boolean isDeleted()
+ {
+ return _deleted.get();
+ }
+
+
+ public boolean delete()
+ {
+ if(_deleted.compareAndSet(false,true))
+ {
+ _size.decrementAndGet();
+ advanceHead();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ public Subscription getSubscription()
+ {
+ return _sub;
+ }
+ }
+
+
+ public SubscriptionList(AMQQueue queue)
+ {
+ _queue = queue;
+ }
+
+ private void advanceHead()
+ {
+ SubscriptionNode head = _head.nextNode();
+ while(head._next.get() != null && head.isDeleted())
+ {
+
+ final SubscriptionNode newhead = head.nextNode();
+ if(newhead != null)
+ {
+ _head._next.compareAndSet(head, newhead);
+ }
+ head = _head.nextNode();
+ }
+ }
+
+
+ public SubscriptionNode add(Subscription sub)
+ {
+ SubscriptionNode node = new SubscriptionNode(sub);
+ for (;;)
+ {
+ SubscriptionNode tail = _tail.get();
+ SubscriptionNode next = tail.nextNode();
+ if (tail == _tail.get())
+ {
+ if (next == null)
+ {
+ if (tail._next.compareAndSet(null, node))
+ {
+ _tail.compareAndSet(tail, node);
+ _size.incrementAndGet();
+ return node;
+ }
+ }
+ else
+ {
+ _tail.compareAndSet(tail, next);
+ }
+ }
+ }
+
+ }
+
+ public boolean remove(Subscription sub)
+ {
+ SubscriptionNode node = _head.getNext();
+ while(node != null)
+ {
+ if(sub.equals(node._sub) && node.delete())
+ {
+ return true;
+ }
+ node = node.getNext();
+ }
+ return false;
+ }
+
+
+ public class SubscriptionNodeIterator
+ {
+
+ private SubscriptionNode _lastNode;
+
+ SubscriptionNodeIterator(SubscriptionNode startNode)
+ {
+ _lastNode = startNode;
+ }
+
+
+ public boolean atTail()
+ {
+ return _lastNode.nextNode() == null;
+ }
+
+ public SubscriptionNode getNode()
+ {
+
+ return _lastNode;
+
+ }
+
+ public boolean advance()
+ {
+
+ if(!atTail())
+ {
+ SubscriptionNode nextNode = _lastNode.nextNode();
+ while(nextNode.isDeleted() && nextNode.nextNode() != null)
+ {
+ nextNode = nextNode.nextNode();
+ }
+ _lastNode = nextNode;
+ return true;
+
+ }
+ else
+ {
+ return false;
+ }
+
+ }
+
+ }
+
+
+ public SubscriptionNodeIterator iterator()
+ {
+ return new SubscriptionNodeIterator(_head);
+ }
+
+
+ public SubscriptionNode getHead()
+ {
+ return _head;
+ }
+
+ public int size()
+ {
+ return _size.get();
+ }
+
+
+
+}
+
+
+
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java
new file mode 100644
index 0000000000..bdd27f2d1c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java
@@ -0,0 +1,705 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.transport;
+
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.mina.common.IdleStatus;
+import org.apache.mina.common.IoFilterAdapter;
+import org.apache.mina.common.IoHandler;
+import org.apache.mina.common.IoSession;
+import org.apache.mina.util.BlockingQueue;
+import org.apache.mina.util.ByteBufferUtil;
+import org.apache.mina.util.IdentityHashSet;
+import org.apache.mina.util.Queue;
+import org.apache.mina.util.Stack;
+
+/**
+ * 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);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java
new file mode 100644
index 0000000000..3c71282c57
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java
@@ -0,0 +1,293 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.txn;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.ack.TxAck;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.*;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/** A transactional context that only supports local transactions. */
+public class LocalTransactionalContext implements TransactionalContext
+{
+ private static final Logger _log = Logger.getLogger(LocalTransactionalContext.class);
+
+ private final TxnBuffer _txnBuffer = new TxnBuffer();
+
+ private final List<DeliveryAction> _postCommitDeliveryList = new ArrayList<DeliveryAction>();
+
+ /**
+ * 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 boolean _inTran = false;
+
+ /** Are there messages to deliver. NOT Has the message been delivered */
+ private boolean _messageDelivered = false;
+ private final AMQChannel _channel;
+
+
+ private abstract class DeliveryAction
+ {
+
+ abstract public void process() throws AMQException;
+
+ }
+
+ private class RequeueAction extends DeliveryAction
+ {
+ public QueueEntry entry;
+
+ public RequeueAction(QueueEntry entry)
+ {
+ this.entry = entry;
+ }
+
+ public void process() throws AMQException
+ {
+ entry.requeue(getStoreContext());
+ }
+ }
+
+ private class PublishAction extends DeliveryAction
+ {
+ private final AMQQueue _queue;
+ private final AMQMessage _message;
+
+ public PublishAction(final AMQQueue queue, final AMQMessage message)
+ {
+ _queue = queue;
+ _message = message;
+ }
+
+ public void process() throws AMQException
+ {
+
+ _message.incrementReference();
+ try
+ {
+ QueueEntry entry = _queue.enqueue(getStoreContext(),_message);
+
+ if(entry.immediateAndNotDelivered())
+ {
+ getReturnMessages().add(new NoConsumersException(_message));
+ }
+ }
+ finally
+ {
+ _message.decrementReference(getStoreContext());
+ }
+ }
+ }
+
+ public LocalTransactionalContext(final AMQChannel channel)
+ {
+ _channel = channel;
+ }
+
+ public StoreContext getStoreContext()
+ {
+ return _channel.getStoreContext();
+ }
+
+ public List<RequiredDeliveryException> getReturnMessages()
+ {
+ return _channel.getReturnMessages();
+ }
+
+ public MessageStore getMessageStore()
+ {
+ return _channel.getMessageStore();
+ }
+
+
+ public void rollback() throws AMQException
+ {
+ _txnBuffer.rollback(getStoreContext());
+ // Hack to deal with uncommitted non-transactional writes
+ if (getMessageStore().inTran(getStoreContext()))
+ {
+ getMessageStore().abortTran(getStoreContext());
+ _inTran = false;
+ }
+
+ _postCommitDeliveryList.clear();
+ }
+
+ public void deliver(final AMQQueue queue, AMQMessage message) throws AMQException
+ {
+ // 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.
+ _postCommitDeliveryList.add(new PublishAction(queue, message));
+ _messageDelivered = true;
+
+ }
+
+ public void requeue(QueueEntry entry) throws AMQException
+ {
+ _postCommitDeliveryList.add(new RequeueAction(entry));
+ _messageDelivered = true;
+
+ }
+
+
+ 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);
+ }
+ if(!_inTran && _ackOp.checkPersistent())
+ {
+ beginTranIfNecessary();
+ }
+ }
+
+ public void messageFullyReceived(boolean persistent) throws AMQException
+ {
+ // Not required in this transactional context
+ }
+
+ public void messageProcessed(AMQProtocolSession protocolSession) throws AMQException
+ {
+ // Not required in this transactional context
+ }
+
+ public void beginTranIfNecessary() throws AMQException
+ {
+ if (!_inTran)
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Starting transaction on message store: " + this);
+ }
+
+ getMessageStore().beginTran(getStoreContext());
+ _inTran = true;
+ }
+ }
+
+ public void commit() throws AMQException
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Committing transactional context: " + this);
+ }
+
+ if (_ackOp != null)
+ {
+
+ _messageDelivered = true;
+ _ackOp.consolidate();
+ // already enlisted, after commit will reset regardless of outcome
+ _ackOp = null;
+ }
+
+ if (_messageDelivered && _inTran)
+ {
+ _txnBuffer.enlist(new StoreMessageOperation(getMessageStore()));
+ }
+ // fixme fail commit here ... QPID-440
+ try
+ {
+ _txnBuffer.commit(getStoreContext());
+ }
+ finally
+ {
+ _messageDelivered = false;
+ _inTran = getMessageStore().inTran(getStoreContext());
+ }
+
+ try
+ {
+ postCommitDelivery();
+ }
+ catch (AMQException e)
+ {
+ // OK so what do we do now...?
+ _log.error("Failed to deliver messages following txn commit: " + e, e);
+ }
+ }
+
+ private void postCommitDelivery() throws AMQException
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Performing post commit delivery");
+ }
+
+ try
+ {
+ for (DeliveryAction dd : _postCommitDeliveryList)
+ {
+ dd.process();
+ }
+ }
+ finally
+ {
+ _postCommitDeliveryList.clear();
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java
new file mode 100644
index 0000000000..28af36e3db
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java
@@ -0,0 +1,213 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.txn;
+
+import java.util.LinkedList;
+import java.util.List;
+
+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.UnacknowledgedMessageMap;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.*;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+
+/** @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;
+
+ private final StoreContext _storeContext;
+
+ /** Whether we are in a transaction */
+ private boolean _inTran;
+
+ public NonTransactionalContext(MessageStore messageStore, StoreContext storeContext, AMQChannel channel,
+ List<RequiredDeliveryException> returnMessages)
+ {
+ _channel = channel;
+ _storeContext = storeContext;
+ _returnMessages = returnMessages;
+ _messageStore = messageStore;
+
+ }
+
+
+ public StoreContext getStoreContext()
+ {
+ return _storeContext;
+ }
+
+ public void beginTranIfNecessary() throws AMQException
+ {
+ if (!_inTran)
+ {
+ _messageStore.beginTran(_storeContext);
+ _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(final AMQQueue queue, AMQMessage message) throws AMQException
+ {
+ QueueEntry entry = queue.enqueue(_storeContext, message);
+
+ //following check implements the functionality
+ //required by the 'immediate' flag:
+ if(entry.immediateAndNotDelivered())
+ {
+ _returnMessages.add(new NoConsumersException(entry.getMessage()));
+ }
+
+ }
+
+ public void requeue(QueueEntry entry) throws AMQException
+ {
+ entry.requeue(_storeContext);
+ }
+
+ public void acknowledgeMessage(final long deliveryTag, long lastDeliveryTag,
+ boolean multiple, final UnacknowledgedMessageMap unacknowledgedMessageMap)
+ throws AMQException
+ {
+
+ final boolean debug = _log.isDebugEnabled();
+ ;
+ 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(final long deliveryTag, QueueEntry message) throws AMQException
+ {
+ if (debug)
+ {
+ _log.debug("Discarding message: " + message.getMessage().getMessageId());
+ }
+ if(message.getMessage().isPersistent())
+ {
+ beginTranIfNecessary();
+ }
+ //Message has been ack so discard it. This will dequeue and decrement the reference.
+ message.discard(_storeContext);
+
+ 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");
+ }
+
+ unacknowledgedMessageMap.drainTo(deliveryTag, _storeContext);
+ }
+ }
+ else
+ {
+ QueueEntry msg;
+ msg = unacknowledgedMessageMap.get(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());
+ }
+
+ if (debug)
+ {
+ _log.debug("Discarding message: " + msg.getMessage().getMessageId());
+ }
+ if(msg.getMessage().isPersistent())
+ {
+ beginTranIfNecessary();
+ }
+
+ //Message has been ack so discard it. This will dequeue and decrement the reference.
+ msg.discard(_storeContext);
+
+ unacknowledgedMessageMap.remove(deliveryTag);
+
+
+ if (debug)
+ {
+ _log.debug("Received non-multiple ack for messaging with delivery tag " + deliveryTag + " msg id " +
+ msg.getMessage().getMessageId());
+ }
+ }
+ if(_inTran)
+ {
+ _messageStore.commitTran(_storeContext);
+ _inTran = false;
+ }
+ }
+
+ public void messageFullyReceived(boolean persistent) throws AMQException
+ {
+ if (persistent)
+ {
+ _messageStore.commitTran(_storeContext);
+ _inTran = false;
+ }
+ }
+
+ public void messageProcessed(AMQProtocolSession protocolSession) throws AMQException
+ {
+ _channel.processReturns();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java
new file mode 100644
index 0000000000..0e4d6c2030
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.txn;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+
+/**
+ * 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(StoreContext context) throws AMQException
+ {
+ }
+
+ public void undoPrepare()
+ {
+ }
+
+ public void commit(StoreContext context) throws AMQException
+ {
+ _messsageStore.commitTran(context);
+ }
+
+ public void rollback(StoreContext context) throws AMQException
+ {
+ _messsageStore.abortTran(context);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java
new file mode 100644
index 0000000000..647ba66fb4
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java
@@ -0,0 +1,179 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.txn;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.store.StoreContext;
+
+/**
+ * TransactionalContext provides a context in which transactional operations on {@link AMQMessage}s are performed.
+ * Different levels of transactional support for the delivery of messages may be provided by different implementations
+ * of this interface.
+ *
+ * <p/>The fundamental transactional operations that can be performed on a message queue are 'enqueue' and 'dequeue'.
+ * In this interface, these have been recast as the {@link #messageFullyReceived} and {@link #acknowledgeMessage}
+ * operations. This interface essentially provides a way to make enqueueing and dequeuing transactional.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Explicitly accept a transaction start notification.
+ * <tr><td> Commit all pending operations in a transaction.
+ * <tr><td> Rollback all pending operations in a transaction.
+ * <tr><td> Deliver a message to a queue as part of a transaction.
+ * <tr><td> Redeliver a message to a queue as part of a transaction.
+ * <tr><td> Mark a message as acknowledged as part of a transaction.
+ * <tr><td> Accept notification that a message has been completely received as part of a transaction.
+ * <tr><td> Accept notification that a message has been fully processed as part of a transaction.
+ * <tr><td> Associate a message store context with this transaction context.
+ * </table>
+ *
+ * @todo The 'fullyReceived' and 'messageProcessed' events sit uncomfortably in the responsibilities of a transactional
+ * context. They are non-transactional operations, used to trigger other side-effects. Consider moving them
+ * somewhere else, a seperate interface for example.
+ *
+ * @todo This transactional context could be written as a wrapper extension to a Queue implementation, that provides
+ * transactional management of the enqueue and dequeue operations, with added commit/rollback methods. Any
+ * queue implementation could be made transactional by wrapping it as a transactional queue. This would mean
+ * that the enqueue/dequeue operations do not need to be recast as deliver/acknowledge operations, which may be
+ * conceptually neater.
+ *
+ * For example:
+ * <pre>
+ * public interface Transactional
+ * {
+ * public void commit();
+ * public void rollback();
+ * }
+ *
+ * public interface TransactionalQueue<E> extends Transactional, SizeableQueue<E>
+ * {}
+ *
+ * public class Queues
+ * {
+ * ...
+ * // For transactional messaging, take a transactional view onto the queue.
+ * public static <E> TransactionalQueue<E> getTransactionalQueue(SizeableQueue<E> queue) { ... }
+ *
+ * // For non-transactional messaging, take a non-transactional view onto the queue.
+ * public static <E> TransactionalQueue<E> getNonTransactionalQueue(SizeableQueue<E> queue) { ... }
+ * }
+ * </pre>
+ */
+public interface TransactionalContext
+{
+ /**
+ * Explicitly begins the transaction, if it has not already been started. {@link #commit} or {@link #rollback}
+ * should automatically begin the next transaction in the chain.
+ *
+ * @throws AMQException If the transaction cannot be started for any reason.
+ */
+ void beginTranIfNecessary() throws AMQException;
+
+ /**
+ * Makes all pending operations on the transaction permanent and visible.
+ *
+ * @throws AMQException If the transaction cannot be committed for any reason.
+ */
+ void commit() throws AMQException;
+
+ /**
+ * Erases all pending operations on the transaction.
+ *
+ * @throws AMQException If the transaction cannot be committed for any reason.
+ */
+ void rollback() throws AMQException;
+
+ /**
+ * Delivers the specified message to the specified queue.
+ *
+ * <p/>This is an 'enqueue' operation.
+ *
+ * @param queue
+ * @param message The message to deliver
+ * @throws AMQException If the message cannot be delivered for any reason.
+ */
+ void deliver(final AMQQueue queue, AMQMessage message) throws AMQException;
+
+ /**
+ * Requeues the specified message entry (message queue pair)
+ *
+ *
+ * @param queueEntry The message,queue pair
+ *
+ * @throws AMQException If the message cannot be delivered for any reason.
+ */
+ void requeue(QueueEntry queueEntry) throws AMQException;
+
+
+ /**
+ * Acknowledges a message or many messages as delivered. All messages up to a specified one, may be acknowledged by
+ * setting the 'multiple' flag. It is also possible for the acknowledged message id to be zero, when the 'multiple'
+ * flag is set, in which case an acknowledgement up to the latest delivered message should be done.
+ *
+ * <p/>This is a 'dequeue' operation.
+ *
+ * @param deliveryTag The id of the message to acknowledge, or zero, if using multiple acknowledgement
+ * up to the latest message.
+ * @param lastDeliveryTag The latest message delivered.
+ * @param multiple <tt>true</tt> if all message ids up the acknowledged one or latest delivered, are
+ * to be acknowledged, <tt>false</tt> otherwise.
+ * @param unacknowledgedMessageMap The unacknowledged messages in the transaction, to remove the acknowledged message
+ * from.
+ *
+ * @throws AMQException If the message cannot be acknowledged for any reason.
+ */
+ void acknowledgeMessage(long deliveryTag, long lastDeliveryTag, boolean multiple,
+ UnacknowledgedMessageMap unacknowledgedMessageMap) throws AMQException;
+
+ /**
+ * Notifies the transactional context that a message has been fully received. The actual message that was received
+ * is not specified. This event may be used to trigger a process related to the receipt of the message, for example,
+ * flushing its data to disk.
+ *
+ * @param persistent <tt>true</tt> if the received message is persistent, <tt>false</tt> otherwise.
+ *
+ * @throws AMQException If the fully received event cannot be processed for any reason.
+ */
+ void messageFullyReceived(boolean persistent) throws AMQException;
+
+ /**
+ * Notifies the transactional context that a message has been delivered, succesfully or otherwise. The actual
+ * message that was delivered is not specified. This event may be used to trigger a process related to the
+ * outcome of the delivery of the message, for example, cleaning up failed deliveries.
+ *
+ * @param protocolSession The protocol session of the deliverable message.
+ *
+ * @throws AMQException If the message processed event cannot be handled for any reason.
+ */
+ void messageProcessed(AMQProtocolSession protocolSession) throws AMQException;
+
+ /**
+ * Gets the message store context associated with this transactional context.
+ *
+ * @return The message store context associated with this transactional context.
+ */
+ StoreContext getStoreContext();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java
new file mode 100644
index 0000000000..46a68b6a23
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java
@@ -0,0 +1,109 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.txn;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.StoreContext;
+
+/** Holds a list of TxnOp instance representing transactional operations. */
+public class TxnBuffer
+{
+ private final List<TxnOp> _ops = new ArrayList<TxnOp>();
+ private static final Logger _log = Logger.getLogger(TxnBuffer.class);
+
+ public TxnBuffer()
+ {
+ }
+
+ public void commit(StoreContext context) throws AMQException
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Committing " + _ops.size() + " ops to commit.:" + _ops);
+ }
+
+ if (prepare(context))
+ {
+ for (TxnOp op : _ops)
+ {
+ op.commit(context);
+ }
+ }
+ _ops.clear();
+ }
+
+ private boolean prepare(StoreContext context) throws AMQException
+ {
+ for (int i = 0; i < _ops.size(); i++)
+ {
+ TxnOp op = _ops.get(i);
+ try
+ {
+ op.prepare(context);
+ }
+ catch (AMQException e)
+ {
+ undoPrepare(i);
+ throw e;
+ }
+ catch (RuntimeException e)
+ {
+ undoPrepare(i);
+ throw e;
+ }
+ }
+ return true;
+ }
+
+ private void undoPrepare(int lastPrepared)
+ {
+ //compensate previously prepared ops
+ for (int j = 0; j < lastPrepared; j++)
+ {
+ _ops.get(j).undoPrepare();
+ }
+ }
+
+
+
+ public void rollback(StoreContext context) throws AMQException
+ {
+ for (TxnOp op : _ops)
+ {
+ op.rollback(context);
+ }
+ _ops.clear();
+ }
+
+ public void enlist(TxnOp op)
+ {
+ _ops.add(op);
+ }
+
+ public void cancel(TxnOp op)
+ {
+ _ops.remove(op);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java
new file mode 100644
index 0000000000..919c078cf0
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java
@@ -0,0 +1,55 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.txn;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.StoreContext;
+
+/**
+ * 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(StoreContext context) throws AMQException;
+ /**
+ * Complete the operation started by prepare. Can now update in
+ * memory state or make netork transfers.
+ */
+ public void commit(StoreContext context) 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(StoreContext context) throws AMQException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java
new file mode 100644
index 0000000000..e730e2f3c3
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java
@@ -0,0 +1,131 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.util;
+
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+
+public class CircularBuffer implements Iterable
+{
+
+ private static final Logger _logger = Logger.getLogger(CircularBuffer.class);
+
+ 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)
+ {
+ _logger.info(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);
+ _logger.info(buffer);
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/util/ConcurrentLinkedQueueNoSize.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/ConcurrentLinkedQueueNoSize.java
new file mode 100644
index 0000000000..cf5e71a6e2
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/ConcurrentLinkedQueueNoSize.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.util;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class ConcurrentLinkedQueueNoSize<E> extends ConcurrentLinkedQueue<E>
+{
+ public int size()
+ {
+ if (isEmpty())
+ {
+ return 0;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java
new file mode 100644
index 0000000000..eda97e0ed2
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java
@@ -0,0 +1,105 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.util;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+
+/**
+ * Dynamic proxy that records invocations in a fixed size circular buffer,
+ * dumping details on hitting an exception.
+ * <p>
+ * Useful in debugging.
+ * <p>
+ */
+public class LoggingProxy implements InvocationHandler
+{
+ private final Object _target;
+ private final CircularBuffer _log;
+
+ public LoggingProxy(Object target, int size)
+ {
+ _target = target;
+ _log = new CircularBuffer(size);
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ try
+ {
+ entered(method, args);
+ Object result = method.invoke(_target, args);
+ returned(method, result);
+ return result;
+ }
+ catch(InvocationTargetException e)
+ {
+ dump();
+ throw e.getTargetException();
+ }
+ }
+
+ void dump()
+ {
+ _log.dump();
+ }
+
+ CircularBuffer getBuffer()
+ {
+ return _log;
+ }
+
+ private synchronized void entered(Method method, Object[] args)
+ {
+ if (args == null)
+ {
+ _log.add(Thread.currentThread() + ": " + method.getName() + "() entered");
+ }
+ else
+ {
+ _log.add(Thread.currentThread() + ": " + method.getName() + "(" + Arrays.toString(args) + ") entered");
+ }
+ }
+
+ private synchronized void returned(Method method, Object result)
+ {
+ if (method.getReturnType() == Void.TYPE)
+ {
+ _log.add(Thread.currentThread() + ": " + method.getName() + "() returned");
+ }
+ else
+ {
+ _log.add(Thread.currentThread() + ": " + method.getName() + "() returned " + result);
+ }
+ }
+
+ public Object getProxy(Class... c)
+ {
+ return Proxy.newProxyInstance(_target.getClass().getClassLoader(), c, this);
+ }
+
+ public int getBufferSize() {
+ return _log.size();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java
new file mode 100644
index 0000000000..eda2d3a94e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java
@@ -0,0 +1,86 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.util;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Properties;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.MapConfiguration;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.management.NoopManagedObjectRegistry;
+import org.apache.qpid.server.plugins.PluginManager;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.access.ACLManager;
+import org.apache.qpid.server.security.access.plugins.AllowAll;
+import org.apache.qpid.server.security.auth.database.PropertiesPrincipalDatabaseManager;
+import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+
+public class NullApplicationRegistry extends ApplicationRegistry
+{
+ public NullApplicationRegistry() throws ConfigurationException
+ {
+ super(new ServerConfiguration(new PropertiesConfiguration()));
+ }
+
+ public void initialise() throws Exception
+ {
+ _logger.info("Initialising NullApplicationRegistry");
+
+ _configuration.setHousekeepingExpiredMessageCheckPeriod(200);
+
+ Properties users = new Properties();
+
+ users.put("guest", "guest");
+
+ _databaseManager = new PropertiesPrincipalDatabaseManager("default", users);
+
+ _accessManager = new ACLManager(_configuration.getSecurityConfiguration(), _pluginManager, AllowAll.FACTORY);
+
+ _authenticationManager = new PrincipalDatabaseAuthenticationManager(null, null);
+
+ _managedObjectRegistry = new NoopManagedObjectRegistry();
+ _virtualHostRegistry = new VirtualHostRegistry();
+ PropertiesConfiguration vhostProps = new PropertiesConfiguration();
+ VirtualHostConfiguration hostConfig = new VirtualHostConfiguration("test", vhostProps);
+ VirtualHost dummyHost = new VirtualHost(hostConfig);
+ _virtualHostRegistry.registerVirtualHost(dummyHost);
+ _virtualHostRegistry.setDefaultVirtualHostName("test");
+ _pluginManager = new PluginManager("");
+
+ }
+
+ public Collection<String> getVirtualHostNames()
+ {
+ String[] hosts = {"test"};
+ return Arrays.asList(hosts);
+ }
+}
+
+
+
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java
new file mode 100644
index 0000000000..f4c81fbbb8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.virtualhost;
+
+import java.io.IOException;
+
+import org.apache.qpid.server.management.MBeanAttribute;
+
+/**
+ * The management interface exposed to allow management of an Exchange.
+ * @version 0.1
+ */
+public interface ManagedVirtualHost
+{
+ static final String TYPE = "VirtualHost";
+ static final int VERSION = 1;
+
+ /**
+ * Returns the name of the managed virtualHost.
+ * @return the name of the exchange.
+ * @throws java.io.IOException
+ */
+ @MBeanAttribute(name="Name", description= TYPE + " Name")
+ String getName() throws IOException;
+
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java
new file mode 100644
index 0000000000..0ae0594de8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java
@@ -0,0 +1,593 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.virtualhost;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.server.AMQBrokerManagerMBean;
+import org.apache.qpid.server.configuration.ExchangeConfiguration;
+import org.apache.qpid.server.configuration.QueueConfiguration;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.connection.ConnectionRegistry;
+import org.apache.qpid.server.connection.IConnectionRegistry;
+import org.apache.qpid.server.exchange.DefaultExchangeFactory;
+import org.apache.qpid.server.exchange.DefaultExchangeRegistry;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.management.AMQManagedObject;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.AMQQueueFactory;
+import org.apache.qpid.server.queue.DefaultQueueRegistry;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.queue.MessageMetaData;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.access.ACLManager;
+import org.apache.qpid.server.security.access.Accessable;
+import org.apache.qpid.server.security.access.plugins.SimpleXML;
+import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
+import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+
+import javax.management.NotCompliantMBeanException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class VirtualHost implements Accessable
+{
+ private static final Logger _logger = Logger.getLogger(VirtualHost.class);
+
+ private final String _name;
+
+ private ConnectionRegistry _connectionRegistry;
+
+ private QueueRegistry _queueRegistry;
+
+ private ExchangeRegistry _exchangeRegistry;
+
+ private ExchangeFactory _exchangeFactory;
+
+ private MessageStore _messageStore;
+
+ protected VirtualHostMBean _virtualHostMBean;
+
+ private AMQBrokerManagerMBean _brokerMBean;
+
+ private AuthenticationManager _authenticationManager;
+
+ private ACLManager _accessManager;
+
+ private final Timer _houseKeepingTimer;
+ private VirtualHostConfiguration _configuration;
+
+ public void setAccessableName(String name)
+ {
+ _logger.warn("Setting Accessable Name for VirualHost is not allowed. ("
+ + name + ") ignored remains :" + getAccessableName());
+ }
+
+ public String getAccessableName()
+ {
+ return _name;
+ }
+
+ public IConnectionRegistry getConnectionRegistry()
+ {
+ return _connectionRegistry;
+ }
+
+ public VirtualHostConfiguration getConfiguration()
+ {
+ return _configuration;
+ }
+
+ /**
+ * 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.
+ */
+ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtualHost
+ {
+ public VirtualHostMBean() throws NotCompliantMBeanException
+ {
+ super(ManagedVirtualHost.class, ManagedVirtualHost.TYPE, ManagedVirtualHost.VERSION);
+ }
+
+ public String getObjectInstanceName()
+ {
+ return _name.toString();
+ }
+
+ public String getName()
+ {
+ return _name.toString();
+ }
+
+ public VirtualHost getVirtualHost()
+ {
+ return VirtualHost.this;
+ }
+
+ } // End of MBean class
+
+ /**
+ * Normal Constructor
+ *
+ * @param hostConfig
+ *
+ * @throws Exception
+ */
+ public VirtualHost(VirtualHostConfiguration hostConfig) throws Exception
+ {
+ this(hostConfig, null);
+ }
+
+ public VirtualHost(VirtualHostConfiguration hostConfig, MessageStore store) throws Exception
+ {
+ _configuration = hostConfig;
+ _name = hostConfig.getName();
+
+ if (_name == null || _name.length() == 0)
+ {
+ throw new IllegalArgumentException("Illegal name (" + _name + ") for virtualhost.");
+ }
+
+ _virtualHostMBean = new VirtualHostMBean();
+
+ _connectionRegistry = new ConnectionRegistry(this);
+
+ _houseKeepingTimer = new Timer("Queue-housekeeping-" + _name, true);
+
+ _queueRegistry = new DefaultQueueRegistry(this);
+
+ _exchangeFactory = new DefaultExchangeFactory(this);
+ _exchangeFactory.initialise(hostConfig);
+
+ _exchangeRegistry = new DefaultExchangeRegistry(this);
+
+ //Create a temporary RT to store the durable entries from the config file
+ // so we can replay them in to the real _RT after it has been loaded.
+ /// This should be removed after the _RT has been fully split from the the TL
+
+ StartupRoutingTable configFileRT = new StartupRoutingTable();
+
+ _messageStore = configFileRT;
+
+ // This needs to be after the RT has been defined as it creates the default durable exchanges.
+ _exchangeRegistry.initialise();
+ initialiseModel(hostConfig);
+
+ if (store != null)
+ {
+ _messageStore = store;
+ }
+ else
+ {
+ if (hostConfig == null)
+ {
+ throw new IllegalAccessException("HostConfig and MessageStore cannot be null");
+ }
+ initialiseMessageStore(hostConfig);
+ }
+
+ //Now that the RT has been initialised loop through the persistent queues/exchanges created from the config
+ // file and write them in to the new routing Table.
+ for (StartupRoutingTable.CreateQueueTuple cqt : configFileRT.queue)
+ {
+ _messageStore.createQueue(cqt.queue, cqt.arguments);
+ }
+
+ for (Exchange exchange : configFileRT.exchange)
+ {
+ _messageStore.createExchange(exchange);
+ }
+
+ for (StartupRoutingTable.CreateBindingTuple cbt : configFileRT.bindings)
+ {
+ _messageStore.bindQueue(cbt.exchange, cbt.routingKey, cbt.queue, cbt.arguments);
+ }
+
+ _authenticationManager = new PrincipalDatabaseAuthenticationManager(_name, hostConfig);
+
+ _accessManager = ApplicationRegistry.getInstance().getAccessManager();
+ _accessManager.configureHostPlugins(hostConfig.getSecurityConfiguration());
+
+ _brokerMBean = new AMQBrokerManagerMBean(_virtualHostMBean);
+ _brokerMBean.register();
+ initialiseHouseKeeping(hostConfig.getHousekeepingExpiredMessageCheckPeriod());
+ }
+
+ private void initialiseHouseKeeping(long period)
+ {
+ /* add a timer task to iterate over queues, cleaning expired messages from queues with no consumers */
+ if (period != 0L)
+ {
+ class RemoveExpiredMessagesTask extends TimerTask
+ {
+ public void run()
+ {
+ for (AMQQueue q : _queueRegistry.getQueues())
+ {
+
+ try
+ {
+ q.checkMessageStatus();
+ }
+ catch (AMQException e)
+ {
+ _logger.error("Exception in housekeeping for queue: " + q.getName().toString(), e);
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ _houseKeepingTimer.scheduleAtFixedRate(new RemoveExpiredMessagesTask(),
+ period / 2,
+ period);
+ }
+ }
+
+ private void initialiseMessageStore(VirtualHostConfiguration hostConfig) throws Exception
+ {
+ String messageStoreClass = hostConfig.getMessageStoreClass();
+
+ Class clazz = Class.forName(messageStoreClass);
+ Object o = clazz.newInstance();
+
+ if (!(o instanceof MessageStore))
+ {
+ throw new ClassCastException("Message store class must implement " + MessageStore.class + ". Class " + clazz +
+ " does not.");
+ }
+ MessageStore messageStore = (MessageStore) o;
+ messageStore.configure(this, "store", hostConfig);
+ _messageStore = messageStore;
+ }
+
+ private void initialiseModel(VirtualHostConfiguration config) throws ConfigurationException, AMQException
+ {
+ _logger.debug("Loading configuration for virtualhost: " + config.getName());
+
+ List exchangeNames = config.getExchanges();
+
+ for (Object exchangeNameObj : exchangeNames)
+ {
+ String exchangeName = String.valueOf(exchangeNameObj);
+ configureExchange(config.getExchangeConfiguration(exchangeName));
+ }
+
+ String[] queueNames = config.getQueueNames();
+
+ for (Object queueNameObj : queueNames)
+ {
+ String queueName = String.valueOf(queueNameObj);
+ configureQueue(config.getQueueConfiguration(queueName));
+ }
+ }
+
+ private void configureExchange(ExchangeConfiguration exchangeConfiguration) throws AMQException
+ {
+ AMQShortString exchangeName = new AMQShortString(exchangeConfiguration.getName());
+
+ Exchange exchange;
+ exchange = _exchangeRegistry.getExchange(exchangeName);
+ if (exchange == null)
+ {
+
+ AMQShortString type = new AMQShortString(exchangeConfiguration.getType());
+ boolean durable = exchangeConfiguration.getDurable();
+ boolean autodelete = exchangeConfiguration.getAutoDelete();
+
+ Exchange newExchange = _exchangeFactory.createExchange(exchangeName, type, durable, autodelete, 0);
+ _exchangeRegistry.registerExchange(newExchange);
+ }
+ }
+
+ private void configureQueue(QueueConfiguration queueConfiguration) throws AMQException, ConfigurationException
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(queueConfiguration, this);
+
+ if (queue.isDurable())
+ {
+ _messageStore.createQueue(queue);
+ }
+
+ String exchangeName = queueConfiguration.getExchange();
+
+ Exchange exchange = _exchangeRegistry.getExchange(exchangeName == null ? null : new AMQShortString(exchangeName));
+
+ if (exchange == null)
+ {
+ exchange = _exchangeRegistry.getDefaultExchange();
+ }
+
+ if (exchange == null)
+ {
+ throw new ConfigurationException("Attempt to bind queue to unknown exchange:" + exchangeName);
+ }
+
+ List routingKeys = queueConfiguration.getRoutingKeys();
+ if (routingKeys == null || routingKeys.isEmpty())
+ {
+ routingKeys = Collections.singletonList(queue.getName());
+ }
+
+ for (Object routingKeyNameObj : routingKeys)
+ {
+ AMQShortString routingKey = new AMQShortString(String.valueOf(routingKeyNameObj));
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Binding queue:" + queue + " with routing key '" + routingKey + "' to exchange:" + this);
+ }
+ queue.bind(exchange, routingKey, null);
+ }
+
+ if (exchange != _exchangeRegistry.getDefaultExchange())
+ {
+ queue.bind(_exchangeRegistry.getDefaultExchange(), queue.getName(), null);
+ }
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public QueueRegistry getQueueRegistry()
+ {
+ return _queueRegistry;
+ }
+
+ public ExchangeRegistry getExchangeRegistry()
+ {
+ return _exchangeRegistry;
+ }
+
+ public ExchangeFactory getExchangeFactory()
+ {
+ return _exchangeFactory;
+ }
+
+ public ApplicationRegistry getApplicationRegistry()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public MessageStore getMessageStore()
+ {
+ return _messageStore;
+ }
+
+ public AuthenticationManager getAuthenticationManager()
+ {
+ return _authenticationManager;
+ }
+
+ public ACLManager getAccessManager()
+ {
+ return _accessManager;
+ }
+
+ public void close() throws Exception
+ {
+
+ //Stop Connections
+ _connectionRegistry.close();
+
+ //Stop the Queues processing
+ if (_queueRegistry != null)
+ {
+ for (AMQQueue queue : _queueRegistry.getQueues())
+ {
+ queue.stop();
+ }
+ }
+
+ //Stop Housekeeping
+ if (_houseKeepingTimer != null)
+ {
+ _houseKeepingTimer.cancel();
+ }
+
+ //Close MessageStore
+ if (_messageStore != null)
+ {
+ _messageStore.close();
+ }
+ }
+
+ public ManagedObject getBrokerMBean()
+ {
+ return _brokerMBean;
+ }
+
+ public ManagedObject getManagedObject()
+ {
+ return _virtualHostMBean;
+ }
+
+ /**
+ * Temporary Startup RT class to record the creation of persistent queues / exchanges.
+ *
+ *
+ * This is so we can replay the creation of queues/exchanges in to the real _RT after it has been loaded.
+ * This should be removed after the _RT has been fully split from the the TL
+ */
+ private class StartupRoutingTable implements MessageStore
+ {
+ public List<Exchange> exchange = new LinkedList<Exchange>();
+ public List<CreateQueueTuple> queue = new LinkedList<CreateQueueTuple>();
+ public List<CreateBindingTuple> bindings = new LinkedList<CreateBindingTuple>();
+
+ public void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception
+ {
+ }
+
+ public void close() throws Exception
+ {
+ }
+
+ public void removeMessage(StoreContext storeContext, Long messageId) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void createExchange(Exchange exchange) throws AMQException
+ {
+ if (exchange.isDurable())
+ {
+ this.exchange.add(exchange);
+ }
+ }
+
+ public void removeExchange(Exchange exchange) throws AMQException
+ {
+ }
+
+ public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ if (exchange.isDurable() && queue.isDurable())
+ {
+ bindings.add(new CreateBindingTuple(exchange, routingKey, queue, args));
+ }
+ }
+
+ public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ }
+
+ public void createQueue(AMQQueue queue) throws AMQException
+ {
+ createQueue(queue, null);
+ }
+
+ public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQException
+ {
+ if (queue.isDurable())
+ {
+ this.queue.add(new CreateQueueTuple(queue, arguments));
+ }
+ }
+
+ public void removeQueue(AMQQueue queue) throws AMQException
+ {
+ }
+
+ public void enqueueMessage(StoreContext context, AMQQueue queue, Long messageId) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void dequeueMessage(StoreContext context, AMQQueue queue, Long messageId) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void beginTran(StoreContext context) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void commitTran(StoreContext context) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void abortTran(StoreContext context) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean inTran(StoreContext context)
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public Long getNewMessageId()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void storeContentBodyChunk(StoreContext context, Long messageId, int index, ContentChunk contentBody, boolean lastContentBody) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void storeMessageMetaData(StoreContext context, Long messageId, MessageMetaData messageMetaData) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public MessageMetaData getMessageMetaData(StoreContext context, Long messageId) throws AMQException
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public ContentChunk getContentBodyChunk(StoreContext context, Long messageId, int index) throws AMQException
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isPersistent()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ private class CreateQueueTuple
+ {
+ public AMQQueue queue;
+ public FieldTable arguments;
+
+ public CreateQueueTuple(AMQQueue queue, FieldTable arguments)
+ {
+ this.queue = queue;
+ this.arguments = arguments;
+ }
+ }
+
+ private class CreateBindingTuple
+ {
+ public AMQQueue queue;
+ public FieldTable arguments;
+ public Exchange exchange;
+ public AMQShortString routingKey;
+
+ public CreateBindingTuple(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args)
+ {
+ this.exchange = exchange;
+ this.routingKey = routingKey;
+ this.queue = queue;
+ arguments = args;
+ }
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java
new file mode 100644
index 0000000000..27917fac8a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java
@@ -0,0 +1,70 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.virtualhost;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class VirtualHostRegistry
+{
+ private final Map<String, VirtualHost> _registry = new ConcurrentHashMap<String,VirtualHost>();
+
+
+ private String _defaultVirtualHostName;
+
+ public synchronized void registerVirtualHost(VirtualHost host) throws Exception
+ {
+ if(_registry.containsKey(host.getName()))
+ {
+ throw new Exception("Virtual Host with name " + host.getName() + " already registered.");
+ }
+ _registry.put(host.getName(),host);
+ }
+
+ public VirtualHost getVirtualHost(String name)
+ {
+ if(name == null || name.trim().length() == 0 )
+ {
+ name = getDefaultVirtualHostName();
+ }
+
+ return _registry.get(name);
+ }
+
+ private String getDefaultVirtualHostName()
+ {
+ return _defaultVirtualHostName;
+ }
+
+ public void setDefaultVirtualHostName(String defaultVirtualHostName)
+ {
+ _defaultVirtualHostName = defaultVirtualHostName;
+ }
+
+
+ public Collection<VirtualHost> getVirtualHosts()
+ {
+ return new ArrayList<VirtualHost>(_registry.values());
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/MessageStoreTool.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/MessageStoreTool.java
new file mode 100644
index 0000000000..faa7b85d58
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/MessageStoreTool.java
@@ -0,0 +1,652 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.configuration.Configuration;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry;
+import org.apache.qpid.server.store.MemoryMessageStore;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.tools.messagestore.commands.Clear;
+import org.apache.qpid.tools.messagestore.commands.Command;
+import org.apache.qpid.tools.messagestore.commands.Copy;
+import org.apache.qpid.tools.messagestore.commands.Dump;
+import org.apache.qpid.tools.messagestore.commands.Help;
+import org.apache.qpid.tools.messagestore.commands.List;
+import org.apache.qpid.tools.messagestore.commands.Load;
+import org.apache.qpid.tools.messagestore.commands.Quit;
+import org.apache.qpid.tools.messagestore.commands.Select;
+import org.apache.qpid.tools.messagestore.commands.Show;
+import org.apache.qpid.tools.messagestore.commands.Move;
+import org.apache.qpid.tools.messagestore.commands.Purge;
+import org.apache.qpid.tools.utils.CommandParser;
+import org.apache.qpid.tools.utils.Console;
+import org.apache.qpid.tools.utils.SimpleCommandParser;
+import org.apache.qpid.tools.utils.SimpleConsole;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * MessageStoreTool.
+ */
+public class MessageStoreTool
+{
+ /** Text outputted at the start of each console.*/
+ private static final String BOILER_PLATE = "MessageStoreTool - for examining Persistent Qpid Broker MessageStore instances";
+
+ /** I/O Wrapper. */
+ protected Console _console;
+
+ /** Batch mode flag. */
+ protected boolean _batchMode;
+
+ /** Internal State object. */
+ private State _state = new State();
+
+ private HashMap<String, Command> _commands = new HashMap<String, Command>();
+
+ /** SLF4J Logger. */
+ private static Logger _devlog = LoggerFactory.getLogger(MessageStoreTool.class);
+
+ /** Loaded configuration file. */
+ private Configuration _config;
+
+ /** Control used for main run loop. */
+ private boolean _running = true;
+ private boolean _initialised = false;
+
+ //---------------------------------------------------------------------------------------------------/
+
+ public static void main(String[] args) throws Configuration.InitException
+ {
+
+ MessageStoreTool tool = new MessageStoreTool(args);
+
+ tool.start();
+ }
+
+
+ public MessageStoreTool(String[] args) throws Configuration.InitException
+ {
+ this(args, System.in, System.out);
+ }
+
+ public MessageStoreTool(String[] args, InputStream in, OutputStream out) throws Configuration.InitException
+ {
+ BufferedReader consoleReader = new BufferedReader(new InputStreamReader(in));
+ BufferedWriter consoleWriter = new BufferedWriter(new OutputStreamWriter(out));
+
+ Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownHook(this)));
+ _batchMode = false;
+
+ _console = new SimpleConsole(consoleWriter, consoleReader);
+
+ _config = new Configuration();
+
+ setOptions();
+ _config.processCommandline(args);
+ }
+
+
+ private void setOptions()
+ {
+ 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 By "
+ + "default looks for a file named "
+ + Configuration.DEFAULT_CONFIG_FILE + " in " + Configuration.QPID_HOME)
+ .withLongOpt("config")
+ .create("c");
+
+ _config.setOption(help);
+ _config.setOption(version);
+ _config.setOption(configFile);
+ }
+
+ public State getState()
+ {
+ return _state;
+ }
+
+ public Map<String, Command> getCommands()
+ {
+ return _commands;
+ }
+
+ public void setConfigurationFile(String configfile) throws Configuration.InitException
+ {
+ _config.loadConfig(new File(configfile));
+ setup();
+ }
+
+ public Console getConsole()
+ {
+ return _console;
+ }
+
+ public void setConsole(Console console)
+ {
+ _console = console;
+ }
+
+ /**
+ * Simple ShutdownHook to cleanly shutdown the databases
+ */
+ class ShutdownHook implements Runnable
+ {
+ MessageStoreTool _tool;
+
+ ShutdownHook(MessageStoreTool messageStoreTool)
+ {
+ _tool = messageStoreTool;
+ }
+
+ public void run()
+ {
+ _tool.quit();
+ }
+ }
+
+ public void quit()
+ {
+ _running = false;
+
+ if (_initialised)
+ {
+ ApplicationRegistry.remove(1);
+ }
+
+ _console.println("...exiting");
+
+ _console.close();
+ }
+
+ public void setBatchMode(boolean batchmode)
+ {
+ _batchMode = batchmode;
+ }
+
+ /**
+ * Main loop
+ */
+ protected void start()
+ {
+ setup();
+
+ if (!_initialised)
+ {
+ System.exit(1);
+ }
+
+ _console.println("");
+
+ _console.println(BOILER_PLATE);
+
+ runCLI();
+ }
+
+ private void setup()
+ {
+ loadDefaultVirtualHosts();
+
+ loadCommands();
+
+ _state.clearAll();
+ }
+
+ private void loadCommands()
+ {
+ _commands.clear();
+ //todo Dynamically load the classes that exis in com.redhat.etp.qpid.commands
+ _commands.put("close", new Clear(this));
+ _commands.put("copy", new Copy(this));
+ _commands.put("dump", new Dump(this));
+ _commands.put("help", new Help(this));
+ _commands.put("list", new List(this));
+ _commands.put("load", new Load(this));
+ _commands.put("move", new Move(this));
+ _commands.put("purge", new Purge(this));
+ _commands.put("quit", new Quit(this));
+ _commands.put("select", new Select(this));
+ _commands.put("show", new Show(this));
+ }
+
+ private void loadDefaultVirtualHosts()
+ {
+ final File configFile = _config.getConfigFile();
+
+ loadVirtualHosts(configFile);
+ }
+
+ private void loadVirtualHosts(File configFile)
+ {
+
+ if (!configFile.exists())
+ {
+ _devlog.error("Config file not found:" + configFile.getAbsolutePath());
+ return;
+ }
+ else
+ {
+ _devlog.debug("using config file :" + configFile.getAbsolutePath());
+ }
+
+ try
+ {
+ ConfigurationFileApplicationRegistry registry = new ConfigurationFileApplicationRegistry(configFile);
+
+ ApplicationRegistry.remove(1);
+
+ ApplicationRegistry.initialise(registry);
+
+ checkMessageStores();
+ _initialised = true;
+ }
+ catch (ConfigurationException e)
+ {
+ _console.println("Unable to load configuration due to configuration error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ catch (Exception e)
+ {
+ _console.println("Unable to load configuration due to: " + e.getMessage());
+ e.printStackTrace();
+ }
+
+
+ }
+
+ private void checkMessageStores()
+ {
+ Collection<VirtualHost> vhosts = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHosts();
+
+ boolean warning = false;
+ for (VirtualHost vhost : vhosts)
+ {
+ if (vhost.getMessageStore() instanceof MemoryMessageStore)
+ {
+ _console.println("WARNING: Virtualhost '" + vhost.getName() + "' is using a MemoryMessageStore. "
+ + "Changes will not persist.");
+ warning = true;
+ }
+ }
+
+ if (warning)
+ {
+ _console.println("");
+ _console.println("Please ensure you are using the correct config file currently using '"
+ + _config.getConfigFile().getAbsolutePath() + "'");
+ _console.println("New config file can be specifed by 'load <config file>' or -c on the commandline.");
+ _console.println("");
+ }
+ }
+
+ private void runCLI()
+ {
+ while (_running)
+ {
+ if (!_batchMode)
+ {
+ printPrompt();
+ }
+
+ String[] args = _console.readCommand();
+
+ while (args != null)
+ {
+ exec(args);
+
+ if (_running)
+ {
+ if (!_batchMode)
+ {
+ printPrompt();
+ }
+
+ args = _console.readCommand();
+ }
+ }
+ }
+ }
+
+ private void printPrompt()
+ {
+ _console.print(prompt());
+ }
+
+
+ /**
+ * Execute a script (batch mode).
+ *
+ * @param script The file script
+ */
+ protected void runScripts(String script)
+ {
+ //Store Current State
+ boolean oldBatch = _batchMode;
+ CommandParser oldParser = _console.getCommandParser();
+ setBatchMode(true);
+
+ try
+ {
+ _devlog.debug("Running script '" + script + "'");
+
+ _console.setCommandParser(new SimpleCommandParser(new BufferedReader(new FileReader(script))));
+
+ start();
+ }
+ catch (java.io.FileNotFoundException e)
+ {
+ _devlog.error("Script not found: '" + script + "' due to:" + e.getMessage());
+ }
+
+ //Restore previous state
+ _console.setCommandParser(oldParser);
+ setBatchMode(oldBatch);
+ }
+
+ public String prompt()
+ {
+ String state = _state.toString();
+ if (state != null && state.length() != 0)
+ {
+ return state + ":bdb$ ";
+ }
+ else
+ {
+ return "bdb$ ";
+ }
+ }
+
+ /**
+ * Execute the command.
+ *
+ * @param args [command, arg0, arg1...].
+ */
+ protected void exec(String[] args)
+ {
+ // Comment lines start with a #
+ if (args.length == 0 || args[0].startsWith("#"))
+ {
+ return;
+ }
+
+ final String command = args[0];
+
+ Command cmd = _commands.get(command);
+
+ if (cmd == null)
+ {
+ _console.println("Command not understood: " + command);
+ }
+ else
+ {
+ cmd.execute(args);
+ }
+ }
+
+
+ /**
+ * Displays usage info.
+ */
+ protected static void help()
+ {
+ System.out.println(BOILER_PLATE);
+ System.out.println("Usage: java " + MessageStoreTool.class + " [Options]");
+ System.out.println(" [-c <broker config file>] : Defaults to \"$QPID_HOME/etc/config.xml\"");
+ }
+
+
+ /**
+ * This class is used to store the current state of the tool.
+ *
+ * This is then interrogated by the various commands to augment their behaviour.
+ *
+ *
+ */
+ public class State
+ {
+ private VirtualHost _vhost = null;
+ private AMQQueue _queue = null;
+ private Exchange _exchange = null;
+ private java.util.List<Long> _msgids = null;
+
+ public State()
+ {
+ }
+
+ public void setQueue(AMQQueue queue)
+ {
+ _queue = queue;
+ }
+
+ public AMQQueue getQueue()
+ {
+ return _queue;
+ }
+
+ public void setVhost(VirtualHost vhost)
+ {
+ _vhost = vhost;
+ }
+
+ public VirtualHost getVhost()
+ {
+ return _vhost;
+ }
+
+ public Exchange getExchange()
+ {
+ return _exchange;
+ }
+
+ public void setExchange(Exchange exchange)
+ {
+ _exchange = exchange;
+ }
+
+ public String toString()
+ {
+ StringBuilder status = new StringBuilder();
+
+ if (_vhost != null)
+ {
+ status.append(_vhost.getName());
+
+ if (_exchange != null)
+ {
+ status.append("[");
+ status.append(_exchange.getName());
+ status.append("]");
+
+ if (_queue != null)
+ {
+ status.append("->'");
+ status.append(_queue.getName());
+ status.append("'");
+
+ if (_msgids != null)
+ {
+ status.append(printMessages());
+ }
+ }
+ }
+ }
+
+ return status.toString();
+ }
+
+
+ public String printMessages()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ Long previous = null;
+
+ Long start = null;
+ for (Long id : _msgids)
+ {
+ if (previous != null)
+ {
+ if (id == previous + 1)
+ {
+ if (start == null)
+ {
+ start = previous;
+ }
+ }
+ else
+ {
+ if (start != null)
+ {
+ sb.append(",");
+ sb.append(start);
+ sb.append("-");
+ sb.append(id);
+ start = null;
+ }
+ else
+ {
+ sb.append(",");
+ sb.append(previous);
+ }
+ }
+ }
+
+ previous = id;
+ }
+
+ if (start != null)
+ {
+ sb.append(",");
+ sb.append(start);
+ sb.append("-");
+ sb.append(_msgids.get(_msgids.size() - 1));
+ }
+ else
+ {
+ sb.append(",");
+ sb.append(previous);
+ }
+
+ // surround list in ()
+ sb.replace(0, 1, "(");
+ sb.append(")");
+ return sb.toString();
+ }
+
+ public void clearAll()
+ {
+ _vhost = null;
+ clearExchange();
+ }
+
+ public void clearExchange()
+ {
+ _exchange = null;
+ clearQueue();
+ }
+
+ public void clearQueue()
+ {
+ _queue = null;
+ clearMessages();
+ }
+
+ public void clearMessages()
+ {
+ _msgids = null;
+ }
+
+ /**
+ * A common location to provide parsing of the message id string
+ * utilised by a number of the commands.
+ * The String is comma separated list of ids that can be individual ids
+ * or a range (4-10)
+ *
+ * @param msgString string of msg ids to parse 1,2,4-10
+ */
+ public void setMessages(String msgString)
+ {
+ StringTokenizer tok = new StringTokenizer(msgString, ",");
+
+ if (tok.hasMoreTokens())
+ {
+ _msgids = new LinkedList<Long>();
+ }
+
+ while (tok.hasMoreTokens())
+ {
+ String next = tok.nextToken();
+ if (next.contains("-"))
+ {
+ Long start = Long.parseLong(next.substring(0, next.indexOf("-")));
+ Long end = Long.parseLong(next.substring(next.indexOf("-") + 1));
+
+ if (end >= start)
+ {
+ for (long l = start; l <= end; l++)
+ {
+ _msgids.add(l);
+ }
+ }
+ }
+ else
+ {
+ _msgids.add(Long.parseLong(next));
+ }
+ }
+
+ }
+
+ public void setMessages(java.util.List<Long> msgids)
+ {
+ _msgids = msgids;
+ }
+
+ public java.util.List<Long> getMessages()
+ {
+ return _msgids;
+ }
+ }//Class State
+
+}//Class MessageStoreTool
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/AbstractCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/AbstractCommand.java
new file mode 100644
index 0000000000..5444197cb4
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/AbstractCommand.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+import org.apache.qpid.tools.utils.Console;
+
+public abstract class AbstractCommand implements Command
+{
+ protected Console _console;
+ protected MessageStoreTool _tool;
+
+ public AbstractCommand(MessageStoreTool tool)
+ {
+ _console = tool.getConsole();
+ _tool = tool;
+ }
+
+ public void setOutput(Console out)
+ {
+ _console = out;
+ }
+
+ protected void commandError(String message, String[] args)
+ {
+ _console.print(getCommand() + " : " + message);
+
+ if (args != null)
+ {
+ for (int i = 1; i < args.length; i++)
+ {
+ _console.print(args[i]);
+ }
+ }
+ _console.println("");
+ _console.println(help());
+ }
+
+
+ public abstract String help();
+
+ public abstract String usage();
+
+ public abstract String getCommand();
+
+
+ public abstract void execute(String... args);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Clear.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Clear.java
new file mode 100644
index 0000000000..b0006b3fe6
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Clear.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+
+public class Clear extends AbstractCommand
+{
+ public Clear(MessageStoreTool tool)
+ {
+ super(tool);
+ }
+
+ public String help()
+ {
+ return "Clears any selection.";
+ }
+
+ public String usage()
+ {
+ return "clear [ all | virtualhost | exchange | queue | msgs ]";
+ }
+
+ public String getCommand()
+ {
+ return "clear";
+ }
+
+ public void execute(String... args)
+ {
+ assert args.length > 0;
+ assert args[0].equals(getCommand());
+
+ if (args.length < 1)
+ {
+ doClose("all");
+ }
+ else
+ {
+ doClose(args[1]);
+ }
+ }
+
+ private void doClose(String type)
+ {
+ if (type.equals("virtualhost")
+ || type.equals("all"))
+ {
+ _tool.getState().clearAll();
+ }
+
+ if (type.equals("exchange"))
+ {
+ _tool.getState().clearExchange();
+ }
+
+ if (type.equals("queue"))
+ {
+ _tool.getState().clearQueue();
+ }
+
+ if (type.equals("msgs"))
+ {
+ _tool.getState().clearMessages();
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Command.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Command.java
new file mode 100644
index 0000000000..bfa775a34a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Command.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.qpid.tools.utils.Console;
+
+public interface Command
+{
+ public void setOutput(Console out);
+
+ public String help();
+
+ public abstract String usage();
+
+ String getCommand();
+
+ public void execute(String... args);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Copy.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Copy.java
new file mode 100644
index 0000000000..0869d9a497
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Copy.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+import org.apache.qpid.server.queue.AMQQueue;
+
+public class Copy extends Move
+{
+ public Copy(MessageStoreTool tool)
+ {
+ super(tool);
+ }
+
+ public String help()
+ {
+ return "Copy messages between queues.";/*\n" +
+ "The currently selected message set will be copied to the specifed queue.\n" +
+ "Alternatively the values can be provided on the command line."; */
+ }
+
+ public String usage()
+ {
+ return "copy to=<queue> [from=<queue>] [msgids=<msgids eg, 1,2,4-10>]";
+ }
+
+ public String getCommand()
+ {
+ return "copy";
+ }
+
+ protected void doCommand(AMQQueue fromQueue, long start, long end, AMQQueue toQueue)
+ {
+ fromQueue.copyMessagesToAnotherQueue(start, end, toQueue.getName().toString(), _storeContext);
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Dump.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Dump.java
new file mode 100644
index 0000000000..731f6140f9
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Dump.java
@@ -0,0 +1,302 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.QueueEntryImpl;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+import org.apache.qpid.tools.utils.Console;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public class Dump extends Show
+{
+ private static final int LINE_SIZE = 8;
+ private static final String DEFAULT_ENCODING = "utf-8";
+ private static final boolean SPACE_BYTES = true;
+ private static final String BYTE_SPACER = " ";
+ private static final String NON_PRINTING_ASCII_CHAR = "?";
+
+ protected boolean _content = true;
+
+ public Dump(MessageStoreTool tool)
+ {
+ super(tool);
+ }
+
+ public String help()
+ {
+ return "Dump selected message content. Default: show=content";
+ }
+
+ public String usage()
+ {
+ return getCommand() + " [show=[all],[msgheaders],[_amqHeaders],[routing],[content]] [id=<msgid e.g. 1,2,4-10>]";
+ }
+
+ public String getCommand()
+ {
+ return "dump";
+ }
+
+ public void execute(String... args)
+ {
+ assert args.length > 0;
+ assert args[0].equals(getCommand());
+
+
+ if (args.length >= 2)
+ {
+ for (String arg : args)
+ {
+ if (arg.startsWith("show="))
+ {
+ _content = arg.contains("content") || arg.contains("all");
+ }
+ }
+
+ parseArgs(args);
+ }
+
+ performShow();
+ }
+
+
+ protected List<List> createMessageData(java.util.List<Long> msgids, List<QueueEntry> messages, boolean showHeaders, boolean showRouting,
+ boolean showMessageHeaders)
+ {
+
+ List<List> display = new LinkedList<List>();
+
+ List<String> hex = new LinkedList<String>();
+ List<String> ascii = new LinkedList<String>();
+ display.add(hex);
+ display.add(ascii);
+
+ for (QueueEntry entry : messages)
+ {
+ AMQMessage msg = entry.getMessage();
+ if (!includeMsg(msg, msgids))
+ {
+ continue;
+ }
+
+ //Add divider between messages
+ hex.add(Console.ROW_DIVIDER);
+ ascii.add(Console.ROW_DIVIDER);
+
+ // Show general message information
+ hex.add(Show.Columns.ID.name());
+ ascii.add(msg.getMessageId().toString());
+
+ hex.add(Console.ROW_DIVIDER);
+ ascii.add(Console.ROW_DIVIDER);
+
+ if (showRouting)
+ {
+ addShowInformation(hex, ascii, msg, "Routing Details", true, false, false);
+ }
+ if (showHeaders)
+ {
+ addShowInformation(hex, ascii, msg, "Headers", false, true, false);
+ }
+ if (showMessageHeaders)
+ {
+ addShowInformation(hex, ascii, msg, null, false, false, true);
+ }
+
+ // Add Content Body seciont
+ hex.add("Content Body");
+ ascii.add("");
+ hex.add(Console.ROW_DIVIDER);
+ ascii.add(Console.ROW_DIVIDER);
+
+ Iterator bodies = msg.getContentBodyIterator();
+ if (bodies.hasNext())
+ {
+
+ hex.add("Hex");
+ hex.add(Console.ROW_DIVIDER);
+
+
+ ascii.add("ASCII");
+ ascii.add(Console.ROW_DIVIDER);
+
+ while (bodies.hasNext())
+ {
+ ContentChunk chunk = (ContentChunk) bodies.next();
+
+ //Duplicate so we don't destroy original data :)
+ ByteBuffer hexBuffer = chunk.getData().duplicate();
+
+ ByteBuffer charBuffer = hexBuffer.duplicate();
+
+ Hex hexencoder = new Hex();
+
+ while (hexBuffer.hasRemaining())
+ {
+ byte[] line = new byte[LINE_SIZE];
+
+ int bufsize = hexBuffer.remaining();
+ if (bufsize < LINE_SIZE)
+ {
+ hexBuffer.get(line, 0, bufsize);
+ }
+ else
+ {
+ bufsize = line.length;
+ hexBuffer.get(line);
+ }
+
+ byte[] encoded = hexencoder.encode(line);
+
+ try
+ {
+ String encStr = new String(encoded, 0, bufsize * 2, DEFAULT_ENCODING);
+ String hexLine = "";
+
+ int strKength = encStr.length();
+ for (int c = 0; c < strKength; c++)
+ {
+ hexLine += encStr.charAt(c);
+
+ if (c % 2 == 1 && SPACE_BYTES)
+ {
+ hexLine += BYTE_SPACER;
+ }
+ }
+
+ hex.add(hexLine);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ _console.println(e.getMessage());
+ return null;
+ }
+ }
+
+ while (charBuffer.hasRemaining())
+ {
+ String asciiLine = "";
+
+ for (int pos = 0; pos < LINE_SIZE; pos++)
+ {
+ if (charBuffer.hasRemaining())
+ {
+ byte ch = charBuffer.get();
+
+ if (isPrintable(ch))
+ {
+ asciiLine += (char) ch;
+ }
+ else
+ {
+ asciiLine += NON_PRINTING_ASCII_CHAR;
+ }
+
+ if (SPACE_BYTES)
+ {
+ asciiLine += BYTE_SPACER;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ ascii.add(asciiLine);
+ }
+ }
+ }
+ else
+ {
+ List<String> result = new LinkedList<String>();
+
+ display.add(result);
+ result.add("No ContentBodies");
+ }
+ }
+
+ // if hex is empty then we have no data to display
+ if (hex.size() == 0)
+ {
+ return null;
+ }
+
+ return display;
+ }
+
+ private void addShowInformation(List<String> column1, List<String> column2, AMQMessage msg,
+ String title, boolean routing, boolean headers, boolean messageHeaders)
+ {
+ List<QueueEntry> single = new LinkedList<QueueEntry>();
+ single.add(new QueueEntryImpl(null,msg, Long.MIN_VALUE));
+
+ List<List> routingData = super.createMessageData(null, single, headers, routing, messageHeaders);
+
+ //Reformat data
+ if (title != null)
+ {
+ column1.add(title);
+ column2.add("");
+ column1.add(Console.ROW_DIVIDER);
+ column2.add(Console.ROW_DIVIDER);
+ }
+
+ // look at all columns in the routing Data
+ for (List item : routingData)
+ {
+ // the item should be:
+ // Title
+ // *divider
+ // value
+ // otherwise we can't reason about the correct value
+ if (item.size() == 3)
+ {
+ //Filter out the columns we are not interested in.
+
+ String columnName = item.get(0).toString();
+
+ if (!(columnName.equals(Show.Columns.ID.name())
+ || columnName.equals(Show.Columns.Size.name())))
+ {
+ column1.add(columnName);
+ column2.add(item.get(2).toString());
+ }
+ }
+ }
+ column1.add(Console.ROW_DIVIDER);
+ column2.add(Console.ROW_DIVIDER);
+ }
+
+ private boolean isPrintable(byte c)
+ {
+ return c > 31 && c < 127;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Help.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Help.java
new file mode 100644
index 0000000000..0f9546541b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Help.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+import org.apache.qpid.tools.utils.Console;
+
+import java.util.LinkedList;
+import java.util.Map;
+
+public class Help extends AbstractCommand
+{
+ public Help(MessageStoreTool tool)
+ {
+ super(tool);
+ }
+
+ public String help()
+ {
+ return "Provides detailed help on commands.";
+ }
+
+ public String getCommand()
+ {
+ return "help";
+ }
+
+ public String usage()
+ {
+ return "help [<command>]";
+ }
+
+ public void execute(String... args)
+ {
+ assert args.length > 0;
+ assert args[0].equals(getCommand());
+
+ if (args.length > 1)
+ {
+ Command command = _tool.getCommands().get(args[1]);
+ if (command != null)
+ {
+ _console.println(command.help());
+ _console.println("Usage:" + command.usage());
+ }
+ else
+ {
+ commandError("Command not found: ", args);
+ }
+ }
+ else
+ {
+ java.util.List<java.util.List> data = new LinkedList<java.util.List>();
+
+ java.util.List<String> commandName = new LinkedList<String>();
+ java.util.List<String> commandDescription = new LinkedList<String>();
+
+ data.add(commandName);
+ data.add(commandDescription);
+
+ //Set up Headers
+ commandName.add("Command");
+ commandDescription.add("Description");
+
+ commandName.add(Console.ROW_DIVIDER);
+ commandDescription.add(Console.ROW_DIVIDER);
+
+ //Add current Commands with descriptions
+ Map<String, Command> commands = _tool.getCommands();
+
+ for (Command command : commands.values())
+ {
+ commandName.add(command.getCommand());
+ commandDescription.add(command.help());
+ }
+
+ _console.printMap("Available Commands", data);
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/List.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/List.java
new file mode 100644
index 0000000000..df8b59ec19
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/List.java
@@ -0,0 +1,314 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+import org.apache.qpid.tools.utils.Console;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+public class List extends AbstractCommand
+{
+
+ public List(MessageStoreTool tool)
+ {
+ super(tool);
+ }
+
+ public void setOutput(Console out)
+ {
+ _console = out;
+ }
+
+ public String help()
+ {
+ return "list available items.";
+ }
+
+ public String usage()
+ {
+ return "list queues [<exchange>] | exchanges | bindings [<exchange>] | all";
+ }
+
+ public String getCommand()
+ {
+ return "list";
+ }
+
+ public void execute(String... args)
+ {
+ assert args.length > 0;
+ assert args[0].equals(getCommand());
+
+ if (args.length > 1)
+ {
+ if ((args[1].equals("exchanges"))
+ || (args[1].equals("queues"))
+ || (args[1].equals("bindings"))
+ || (args[1].equals("all")))
+ {
+ if (args.length == 2)
+ {
+ doList(args[1]);
+ }
+ else if (args.length == 3)
+ {
+ doList(args[1], args[2]);
+ }
+ }
+ else
+ {
+ commandError("Unknown options. ", args);
+ }
+ }
+ else if (args.length < 2)
+ {
+ doList("all");
+ }
+ else
+ {
+ doList(args[1]);
+ }
+ }
+
+ private void doList(String... listItem)
+ {
+ if (_tool.getState().getVhost() == null)
+ {
+ _console.println("No Virtualhost open. Open a Virtualhost first.");
+ listVirtualHosts();
+ return;
+ }
+
+ VirtualHost vhost = _tool.getState().getVhost();
+
+ java.util.List<String> data = null;
+
+ if (listItem[0].equals("queues"))
+ {
+ if (listItem.length > 1)
+ {
+ data = listQueues(vhost, new AMQShortString(listItem[1]));
+ }
+ else
+ {
+ Exchange exchange = _tool.getState().getExchange();
+ data = listQueues(vhost, exchange);
+ }
+ }
+
+ if (listItem[0].equals("exchanges"))
+ {
+ data = listExchanges(vhost);
+ }
+
+ if (listItem[0].equals("bindings"))
+ {
+
+ if (listItem.length > 1)
+ {
+ data = listBindings(vhost, new AMQShortString(listItem[1]));
+ }
+ else
+ {
+ Exchange exchange = _tool.getState().getExchange();
+
+ data = listBindings(vhost, exchange);
+ }
+ }
+
+ if (data != null)
+ {
+ if (data.size() == 1)
+ {
+ _console.println("No '" + listItem[0] + "' to display,");
+ }
+ else
+ {
+ _console.displayList(true, data.toArray(new String[0]));
+ }
+ }
+
+
+ if (listItem[0].equals("all"))
+ {
+
+ boolean displayed = false;
+ Exchange exchange = _tool.getState().getExchange();
+
+ //Do the display here for each one so that they are pretty printed
+ data = listQueues(vhost, exchange);
+ if (data != null)
+ {
+ displayed = true;
+ _console.displayList(true, data.toArray(new String[0]));
+ }
+
+ if (exchange == null)
+ {
+ data = listExchanges(vhost);
+ if (data != null)
+ {
+ displayed = true;
+ _console.displayList(true, data.toArray(new String[0]));
+ }
+ }
+
+ data = listBindings(vhost, exchange);
+ if (data != null)
+ {
+ displayed = true;
+ _console.displayList(true, data.toArray(new String[0]));
+ }
+
+ if (!displayed)
+ {
+ _console.println("Nothing to list");
+ }
+ }
+ }
+
+ private void listVirtualHosts()
+ {
+ Collection<VirtualHost> vhosts = ApplicationRegistry.getInstance()
+ .getVirtualHostRegistry().getVirtualHosts();
+
+ String[] data = new String[vhosts.size() + 1];
+
+ data[0] = "Available VirtualHosts";
+
+ int index = 1;
+ for (VirtualHost vhost : vhosts)
+ {
+ data[index] = vhost.getName();
+ index++;
+ }
+
+ _console.displayList(true, data);
+ }
+
+ private java.util.List<String> listBindings(VirtualHost vhost, AMQShortString exchangeName)
+ {
+ return listBindings(vhost, vhost.getExchangeRegistry().getExchange(exchangeName));
+ }
+
+ private java.util.List<String> listBindings(VirtualHost vhost, Exchange exchange)
+ {
+ Collection<AMQShortString> queues = vhost.getQueueRegistry().getQueueNames();
+
+ if (queues == null || queues.size() == 0)
+ {
+ return null;
+ }
+
+ java.util.List<String> data = new LinkedList<String>();
+
+ data.add("Current Bindings");
+
+ for (AMQShortString queue : queues)
+ {
+ if (exchange != null)
+ {
+ if (exchange.isBound(queue))
+ {
+ data.add(queue.toString());
+ }
+ }
+ else
+ {
+ data.add(queue.toString());
+ }
+ }
+
+ return data;
+ }
+
+ private java.util.List<String> listExchanges(VirtualHost vhost)
+ {
+ Collection<AMQShortString> queues = vhost.getExchangeRegistry().getExchangeNames();
+
+ if (queues == null || queues.size() == 0)
+ {
+ return null;
+ }
+
+ java.util.List<String> data = new LinkedList<String>();
+
+ data.add("Available Exchanges");
+
+ for (AMQShortString queue : queues)
+ {
+ data.add(queue.toString());
+ }
+
+ return data;
+ }
+
+ private java.util.List<String> listQueues(VirtualHost vhost, AMQShortString exchangeName)
+ {
+ return listQueues(vhost, vhost.getExchangeRegistry().getExchange(exchangeName));
+ }
+
+ private java.util.List<String> listQueues(VirtualHost vhost, Exchange exchange)
+ {
+ Collection<AMQQueue> queues = vhost.getQueueRegistry().getQueues();
+
+ if (queues == null || queues.size() == 0)
+ {
+ return null;
+ }
+
+ java.util.List<String> data = new LinkedList<String>();
+
+ data.add("Available Queues");
+
+ for (AMQQueue queue : queues)
+ {
+ if (exchange != null)
+ {
+ if (exchange.isBound(queue))
+ {
+ data.add(queue.getName().toString());
+ }
+ }
+ else
+ {
+ data.add(queue.getName().toString());
+ }
+ }
+
+ if (exchange != null)
+ {
+ if (queues.size() == 1)
+ {
+ return null;
+ }
+ }
+
+ return data;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Load.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Load.java
new file mode 100644
index 0000000000..244a311c30
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Load.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.qpid.configuration.Configuration;
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+
+public class Load extends AbstractCommand
+{
+ public Load(MessageStoreTool tool)
+ {
+ super(tool);
+ }
+
+ public String help()
+ {
+ return "Loads specified broker configuration file.";
+ }
+
+ public String usage()
+ {
+ return "load <configuration file>";
+ }
+
+ public String getCommand()
+ {
+ return "load";
+ }
+
+ public void execute(String... args)
+ {
+ assert args.length > 0;
+ assert args[0].equals(getCommand());
+
+ if (args.length > 2)
+ {
+ _console.print("load " + args[1] + ": additional options not understood:");
+ for (int i = 2; i < args.length; i++)
+ {
+ _console.print(args[i] + " ");
+ }
+ _console.println("");
+ }
+ else if (args.length < 2)
+ {
+ _console.println("Enter Configuration file.");
+ String input = _console.readln();
+ if (input != null)
+ {
+ doLoad(input);
+ }
+ else
+ {
+ _console.println("Did not recognise config file.");
+ }
+ }
+ else
+ {
+ doLoad(args[1]);
+ }
+ }
+
+ private void doLoad(String configfile)
+ {
+ _console.println("Loading Configuration:" + configfile);
+
+ try
+ {
+ _tool.setConfigurationFile(configfile);
+ }
+ catch (Configuration.InitException e)
+ {
+ _console.println("Unable to open config file due to: '" + e.getMessage() + "'");
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Move.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Move.java
new file mode 100644
index 0000000000..a8dd58ca83
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Move.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.queue.QueueEntryImpl;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class Move extends AbstractCommand
+{
+
+ /**
+ * Since the Coopy command is not associated with a real channel we can safely create our own store context
+ * for use in the few methods that require one.
+ */
+ protected StoreContext _storeContext = new StoreContext();
+
+ public Move(MessageStoreTool tool)
+ {
+ super(tool);
+ }
+
+ public String help()
+ {
+ return "Move messages between queues.";/*\n" +
+ "The currently selected message set will be moved to the specifed queue.\n" +
+ "Alternatively the values can be provided on the command line.";*/
+ }
+
+ public String usage()
+ {
+ return "move to=<queue> [from=<queue>] [msgids=<msgids eg, 1,2,4-10>]";
+ }
+
+ public String getCommand()
+ {
+ return "move";
+ }
+
+ public void execute(String... args)
+ {
+ AMQQueue toQueue = null;
+ AMQQueue fromQueue = _tool.getState().getQueue();
+ java.util.List<Long> msgids = _tool.getState().getMessages();
+
+ if (args.length >= 2)
+ {
+ for (String arg : args)
+ {
+ if (arg.startsWith("to="))
+ {
+ String queueName = arg.substring(arg.indexOf("=") + 1);
+ toQueue = _tool.getState().getVhost().getQueueRegistry().getQueue(new AMQShortString(queueName));
+ }
+
+ if (arg.startsWith("from="))
+ {
+ String queueName = arg.substring(arg.indexOf("=") + 1);
+ fromQueue = _tool.getState().getVhost().getQueueRegistry().getQueue(new AMQShortString(queueName));
+ }
+
+ if (arg.startsWith("msgids="))
+ {
+ String msgidStr = arg.substring(arg.indexOf("=") + 1);
+
+ // Record the current message selection
+ java.util.List<Long> currentIDs = _tool.getState().getMessages();
+
+ // Use the ToolState class to perform the messasge parsing
+ _tool.getState().setMessages(msgidStr);
+ msgids = _tool.getState().getMessages();
+
+ // Reset the original selection of messages
+ _tool.getState().setMessages(currentIDs);
+ }
+ }
+ }
+
+ if (!checkRequirements(fromQueue, toQueue, msgids))
+ {
+ return;
+ }
+
+ processIDs(fromQueue, toQueue, msgids);
+ }
+
+ private void processIDs(AMQQueue fromQueue, AMQQueue toQueue, java.util.List<Long> msgids)
+ {
+ Long previous = null;
+ Long start = null;
+
+ if (msgids == null)
+ {
+ msgids = allMessageIDs(fromQueue);
+ }
+
+ if (msgids == null || msgids.size() == 0)
+ {
+ _console.println("No Messages to move.");
+ return;
+ }
+
+ for (long id : msgids)
+ {
+ if (previous != null)
+ {
+ if (id == previous + 1)
+ {
+ if (start == null)
+ {
+ start = previous;
+ }
+ }
+ else
+ {
+ if (start != null)
+ {
+ //move a range of ids
+ doCommand(fromQueue, start, id, toQueue);
+ start = null;
+ }
+ else
+ {
+ //move a single id
+ doCommand(fromQueue, id, id, toQueue);
+ }
+ }
+ }
+
+ previous = id;
+ }
+
+ if (start != null)
+ {
+ //move a range of ids
+ doCommand(fromQueue, start, previous, toQueue);
+ }
+ }
+
+ private List<Long> allMessageIDs(AMQQueue fromQueue)
+ {
+ List<Long> ids = new LinkedList<Long>();
+
+ if (fromQueue != null)
+ {
+ List<QueueEntry> messages = fromQueue.getMessagesOnTheQueue();
+ if (messages != null)
+ {
+ for (QueueEntry msg : messages)
+ {
+ ids.add(msg.getMessage().getMessageId());
+ }
+ }
+ }
+
+ return ids;
+ }
+
+ protected boolean checkRequirements(AMQQueue fromQueue, AMQQueue toQueue, List<Long> msgids)
+ {
+ if (toQueue == null)
+ {
+ _console.println("Destination queue not specifed.");
+ _console.println(usage());
+ return false;
+ }
+
+ if (fromQueue == null)
+ {
+ _console.println("Source queue not specifed.");
+ _console.println(usage());
+ return false;
+ }
+
+ return true;
+ }
+
+ protected void doCommand(AMQQueue fromQueue, long start, long id, AMQQueue toQueue)
+ {
+ fromQueue.moveMessagesToAnotherQueue(start, id, toQueue.getName().toString(), _storeContext);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Purge.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Purge.java
new file mode 100644
index 0000000000..5e99997863
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Purge.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+import org.apache.qpid.server.queue.AMQQueue;
+
+public class Purge extends Move
+{
+ public Purge(MessageStoreTool tool)
+ {
+ super(tool);
+ }
+
+ public String help()
+ {
+ return "Purge messages from a queue.\n" +
+ "The currently selected message set will be purged from the specifed queue.\n" +
+ "Alternatively the values can be provided on the command line.";
+ }
+
+ public String usage()
+ {
+ return "purge from=<queue> [msgids=<msgids eg, 1,2,4-10>]";
+ }
+
+ public String getCommand()
+ {
+ return "purge";
+ }
+
+
+ protected boolean checkRequirements(AMQQueue fromQueue, AMQQueue toQueue, java.util.List<Long> msgids)
+ {
+ if (fromQueue == null)
+ {
+ _console.println("Source queue not specifed.");
+ _console.println(usage());
+ return false;
+ }
+
+ return true;
+ }
+
+ protected void doCommand(AMQQueue fromQueue, long start, long end, AMQQueue toQueue)
+ {
+ fromQueue.removeMessagesFromQueue(start, end, _storeContext);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Quit.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Quit.java
new file mode 100644
index 0000000000..a81bc07c38
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Quit.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+
+public class Quit extends AbstractCommand
+{
+ public Quit(MessageStoreTool tool)
+ {
+ super(tool);
+ }
+
+ public String help()
+ {
+ return "Quit the tool.";
+ }
+
+ public String usage()
+ {
+ return "quit";
+ }
+
+ public String getCommand()
+ {
+ return "quit";
+ }
+
+ public void execute(String... args)
+ {
+ assert args.length > 0;
+ assert args[0].equals("quit");
+
+ _tool.quit();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Select.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Select.java
new file mode 100644
index 0000000000..ff59568374
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Select.java
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+
+import java.util.LinkedList;
+import java.util.StringTokenizer;
+
+public class Select extends AbstractCommand
+{
+
+ public Select(MessageStoreTool tool)
+ {
+ super(tool);
+ }
+
+ public String help()
+ {
+ return "Perform a selection.";
+ }
+
+ public String usage()
+ {
+ return "select virtualhost <name> |exchange <name> |queue <name> | msg id=<msgids eg. 1,2,4-10>";
+ }
+
+ public String getCommand()
+ {
+ return "select";
+ }
+
+ public void execute(String... args)
+ {
+ assert args.length > 2;
+ assert args[0].equals("select");
+
+ if (args.length < 3)
+ {
+ if (args[1].equals("show"))
+ {
+ doSelect(args[1], null);
+ }
+ else
+ {
+ _console.print("select : unknown command:");
+ _console.println(help());
+ }
+ }
+ else
+ {
+ if (args[1].equals("virtualhost")
+ || args[1].equals("vhost")
+ || args[1].equals("exchange")
+ || args[1].equals("queue")
+ || args[1].equals("msg")
+ )
+ {
+ doSelect(args[1], args[2]);
+ }
+ else
+ {
+ _console.println(help());
+ }
+ }
+ }
+
+ private void doSelect(String type, String item)
+ {
+ if (type.equals("virtualhost"))
+ {
+
+ VirtualHost vhost = ApplicationRegistry.getInstance()
+ .getVirtualHostRegistry().getVirtualHost(item);
+
+ if (vhost == null)
+ {
+ _console.println("Virtualhost '" + item + "' not found.");
+ }
+ else
+ {
+ _tool.getState().setVhost(vhost);
+ }
+ }
+
+ if (type.equals("exchange"))
+ {
+
+ VirtualHost vhost = _tool.getState().getVhost();
+
+ if (vhost == null)
+ {
+ _console.println("No Virtualhost open. Open a Virtualhost first.");
+ return;
+ }
+
+
+ Exchange exchange = vhost.getExchangeRegistry().getExchange(new AMQShortString(item));
+
+ if (exchange == null)
+ {
+ _console.println("Exchange '" + item + "' not found.");
+ }
+ else
+ {
+ _tool.getState().setExchange(exchange);
+ }
+
+ if (_tool.getState().getQueue() != null)
+ {
+ if (!exchange.isBound(_tool.getState().getQueue()))
+ {
+ _tool.getState().setQueue(null);
+ }
+ }
+ }
+
+ if (type.equals("queue"))
+ {
+ VirtualHost vhost = _tool.getState().getVhost();
+
+ if (vhost == null)
+ {
+ _console.println("No Virtualhost open. Open a Virtualhost first.");
+ return;
+ }
+
+ AMQQueue queue = vhost.getQueueRegistry().getQueue(new AMQShortString(item));
+
+ if (queue == null)
+ {
+ _console.println("Queue '" + item + "' not found.");
+ }
+ else
+ {
+ _tool.getState().setQueue(queue);
+
+ if (_tool.getState().getExchange() == null)
+ {
+ for (AMQShortString exchangeName : vhost.getExchangeRegistry().getExchangeNames())
+ {
+ Exchange exchange = vhost.getExchangeRegistry().getExchange(exchangeName);
+ if (exchange.isBound(queue))
+ {
+ _tool.getState().setExchange(exchange);
+ break;
+ }
+ }
+ }
+
+ //remove the message selection
+ _tool.getState().setMessages((java.util.List<Long>) null);
+ }
+ }
+
+ if (type.equals("msg"))
+ {
+ if (item.startsWith("id="))
+ {
+ StringTokenizer tok = new StringTokenizer(item.substring(item.indexOf("=") + 1), ",");
+
+ java.util.List<Long> msgids = null;
+
+ if (tok.hasMoreTokens())
+ {
+ msgids = new LinkedList<Long>();
+ }
+
+ while (tok.hasMoreTokens())
+ {
+ String next = tok.nextToken();
+ if (next.contains("-"))
+ {
+ Long start = Long.parseLong(next.substring(0, next.indexOf("-")));
+ Long end = Long.parseLong(next.substring(next.indexOf("-") + 1));
+
+ if (end >= start)
+ {
+ for (long l = start; l <= end; l++)
+ {
+ msgids.add(l);
+ }
+ }
+ }
+ else
+ {
+ msgids.add(Long.parseLong(next));
+ }
+ }
+
+ _tool.getState().setMessages(msgids);
+ }
+
+ }
+
+ if (type.equals("show"))
+ {
+ _console.println(_tool.getState().toString());
+ if (_tool.getState().getMessages() != null)
+ {
+ _console.print("Msgs:");
+ for (Long l : _tool.getState().getMessages())
+ {
+ _console.print(" " + l);
+ }
+ _console.println("");
+ }
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Show.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Show.java
new file mode 100644
index 0000000000..2fa017fc64
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Show.java
@@ -0,0 +1,515 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.messagestore.commands;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.QueueEntryImpl;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+import org.apache.qpid.tools.utils.Console;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class Show extends AbstractCommand
+{
+ protected boolean _amqHeaders = false;
+ protected boolean _routing = false;
+ protected boolean _msgHeaders = false;
+
+ public Show(MessageStoreTool tool)
+ {
+ super(tool);
+ }
+
+ public String help()
+ {
+ return "Shows the messages headers.";
+ }
+
+ public String usage()
+ {
+ return getCommand() + " [show=[all],[msgheaders],[amqheaders],[routing]] [id=<msgid e.g. 1,2,4-10>]";
+ }
+
+ public String getCommand()
+ {
+ return "show";
+ }
+
+ public void execute(String... args)
+ {
+ assert args.length > 0;
+ assert args[0].equals(getCommand());
+
+ if (args.length < 2)
+ {
+ parseArgs("all");
+ }
+ else
+ {
+ parseArgs(args);
+ }
+
+ performShow();
+ }
+
+ protected void parseArgs(String... args)
+ {
+ List<Long> msgids = null;
+
+ if (args.length >= 2)
+ {
+ for (String arg : args)
+ {
+ if (arg.startsWith("show="))
+ {
+ _msgHeaders = arg.contains("msgheaders") || arg.contains("all");
+ _amqHeaders = arg.contains("amqheaders") || arg.contains("all");
+ _routing = arg.contains("routing") || arg.contains("all");
+ }
+
+ if (arg.startsWith("id="))
+ {
+ _tool.getState().setMessages(msgids);
+ }
+ }//for args
+ }// if args > 2
+ }
+
+ protected void performShow()
+ {
+ if (_tool.getState().getVhost() == null)
+ {
+ _console.println("No Virtualhost selected. 'DuSelect' a Virtualhost first.");
+ return;
+ }
+
+ AMQQueue _queue = _tool.getState().getQueue();
+
+ List<Long> msgids = _tool.getState().getMessages();
+
+ if (_queue != null)
+ {
+ List<QueueEntry> messages = _queue.getMessagesOnTheQueue();
+ if (messages == null || messages.size() == 0)
+ {
+ _console.println("No messages on queue");
+ return;
+ }
+
+ List<List> data = createMessageData(msgids, messages, _amqHeaders, _routing, _msgHeaders);
+ if (data != null)
+ {
+ _console.printMap(null, data);
+ }
+ else
+ {
+ String message = "No data to display.";
+ if (msgids != null)
+ {
+ message += " Is message selection correct? " + _tool.getState().printMessages();
+ }
+ _console.println(message);
+ }
+
+ }
+ else
+ {
+ _console.println("No Queue specified to show.");
+ }
+ }
+
+ /**
+ * Create the list data for display from the messages.
+ *
+ * @param msgids The list of message ids to display
+ * @param messages A list of messages to format and display.
+ * @param showHeaders should the header info be shown
+ * @param showRouting show the routing info be shown
+ * @param showMessageHeaders show the msg headers be shown
+ * @return the formated data lists for printing
+ */
+ protected List<List> createMessageData(List<Long> msgids, List<QueueEntry> messages, boolean showHeaders, boolean showRouting,
+ boolean showMessageHeaders)
+ {
+
+ // Currenly exposed message properties
+// //Printing the content Body
+// msg.getContentBodyIterator();
+// //Print the Headers
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getAppId();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getAppIdAsString();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getClusterId();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getContentType();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getCorrelationId();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getDeliveryMode();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getEncoding();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getExpiration();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getHeaders();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getMessageId();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getPriority();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getPropertyFlags();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getReplyTo();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getTimestamp();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getType();
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getUserId();
+//
+// //Print out all the property names
+// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getHeaders().getPropertyNames();
+//
+// msg.getMessageId();
+// msg.getSize();
+// msg.getArrivalTime();
+
+// msg.getDeliveredSubscription();
+// msg.getDeliveredToConsumer();
+// msg.getMessageHandle();
+// msg.getMessageId();
+// msg.getMessagePublishInfo();
+// msg.getPublisher();
+
+// msg.getStoreContext();
+// msg.isAllContentReceived();
+// msg.isPersistent();
+// msg.isRedelivered();
+// msg.isRejectedBy();
+// msg.isTaken();
+
+ //Header setup
+
+ List<List> data = new LinkedList<List>();
+
+ List<String> id = new LinkedList<String>();
+ data.add(id);
+ id.add(Columns.ID.name());
+ id.add(Console.ROW_DIVIDER);
+
+ List<String> exchange = new LinkedList<String>();
+ List<String> routingkey = new LinkedList<String>();
+ List<String> immediate = new LinkedList<String>();
+ List<String> mandatory = new LinkedList<String>();
+ if (showRouting)
+ {
+ data.add(exchange);
+ exchange.add(Columns.Exchange.name());
+ exchange.add(Console.ROW_DIVIDER);
+
+ data.add(routingkey);
+ routingkey.add(Columns.RoutingKey.name());
+ routingkey.add(Console.ROW_DIVIDER);
+
+ data.add(immediate);
+ immediate.add(Columns.isImmediate.name());
+ immediate.add(Console.ROW_DIVIDER);
+
+ data.add(mandatory);
+ mandatory.add(Columns.isMandatory.name());
+ mandatory.add(Console.ROW_DIVIDER);
+ }
+
+ List<String> size = new LinkedList<String>();
+ List<String> appid = new LinkedList<String>();
+ List<String> clusterid = new LinkedList<String>();
+ List<String> contenttype = new LinkedList<String>();
+ List<String> correlationid = new LinkedList<String>();
+ List<String> deliverymode = new LinkedList<String>();
+ List<String> encoding = new LinkedList<String>();
+ List<String> arrival = new LinkedList<String>();
+ List<String> expiration = new LinkedList<String>();
+ List<String> priority = new LinkedList<String>();
+ List<String> propertyflag = new LinkedList<String>();
+ List<String> replyto = new LinkedList<String>();
+ List<String> timestamp = new LinkedList<String>();
+ List<String> type = new LinkedList<String>();
+ List<String> userid = new LinkedList<String>();
+ List<String> ispersitent = new LinkedList<String>();
+ List<String> isredelivered = new LinkedList<String>();
+ List<String> isdelivered = new LinkedList<String>();
+
+ data.add(size);
+ size.add(Columns.Size.name());
+ size.add(Console.ROW_DIVIDER);
+
+ if (showHeaders)
+ {
+ data.add(ispersitent);
+ ispersitent.add(Columns.isPersistent.name());
+ ispersitent.add(Console.ROW_DIVIDER);
+
+ data.add(isredelivered);
+ isredelivered.add(Columns.isRedelivered.name());
+ isredelivered.add(Console.ROW_DIVIDER);
+
+ data.add(isdelivered);
+ isdelivered.add(Columns.isDelivered.name());
+ isdelivered.add(Console.ROW_DIVIDER);
+
+ data.add(appid);
+ appid.add(Columns.App_ID.name());
+ appid.add(Console.ROW_DIVIDER);
+
+ data.add(clusterid);
+ clusterid.add(Columns.Cluster_ID.name());
+ clusterid.add(Console.ROW_DIVIDER);
+
+ data.add(contenttype);
+ contenttype.add(Columns.Content_Type.name());
+ contenttype.add(Console.ROW_DIVIDER);
+
+ data.add(correlationid);
+ correlationid.add(Columns.Correlation_ID.name());
+ correlationid.add(Console.ROW_DIVIDER);
+
+ data.add(deliverymode);
+ deliverymode.add(Columns.Delivery_Mode.name());
+ deliverymode.add(Console.ROW_DIVIDER);
+
+ data.add(encoding);
+ encoding.add(Columns.Encoding.name());
+ encoding.add(Console.ROW_DIVIDER);
+
+ data.add(arrival);
+ expiration.add(Columns.Arrival.name());
+ expiration.add(Console.ROW_DIVIDER);
+
+ data.add(expiration);
+ expiration.add(Columns.Expiration.name());
+ expiration.add(Console.ROW_DIVIDER);
+
+ data.add(priority);
+ priority.add(Columns.Priority.name());
+ priority.add(Console.ROW_DIVIDER);
+
+ data.add(propertyflag);
+ propertyflag.add(Columns.Property_Flag.name());
+ propertyflag.add(Console.ROW_DIVIDER);
+
+ data.add(replyto);
+ replyto.add(Columns.ReplyTo.name());
+ replyto.add(Console.ROW_DIVIDER);
+
+ data.add(timestamp);
+ timestamp.add(Columns.Timestamp.name());
+ timestamp.add(Console.ROW_DIVIDER);
+
+ data.add(type);
+ type.add(Columns.Type.name());
+ type.add(Console.ROW_DIVIDER);
+
+ data.add(userid);
+ userid.add(Columns.UserID.name());
+ userid.add(Console.ROW_DIVIDER);
+ }
+
+ List<String> msgHeaders = new LinkedList<String>();
+ if (showMessageHeaders)
+ {
+ data.add(msgHeaders);
+ msgHeaders.add(Columns.MsgHeaders.name());
+ msgHeaders.add(Console.ROW_DIVIDER);
+ }
+
+ //Add create the table of data
+ for (QueueEntry entry : messages)
+ {
+ AMQMessage msg = entry.getMessage();
+ if (!includeMsg(msg, msgids))
+ {
+ continue;
+ }
+
+ id.add(msg.getMessageId().toString());
+
+ size.add("" + msg.getSize());
+
+ arrival.add("" + msg.getArrivalTime());
+
+ try
+ {
+ ispersitent.add(msg.isPersistent() ? "true" : "false");
+ }
+ catch (AMQException e)
+ {
+ ispersitent.add("n/a");
+ }
+
+ isredelivered.add(msg.isRedelivered() ? "true" : "false");
+
+ isdelivered.add(msg.getDeliveredToConsumer() ? "true" : "false");
+
+// msg.getMessageHandle();
+
+ BasicContentHeaderProperties headers = null;
+
+ try
+ {
+ headers = ((BasicContentHeaderProperties) msg.getContentHeaderBody().properties);
+ }
+ catch (AMQException e)
+ {
+ //ignore
+// commandError("Unable to read properties for message: " + e.getMessage(), null);
+ }
+
+ if (headers != null)
+ {
+ String appidS = headers.getAppIdAsString();
+ appid.add(appidS == null ? "null" : appidS);
+
+ String clusterS = headers.getClusterIdAsString();
+ clusterid.add(clusterS == null ? "null" : clusterS);
+
+ String contentS = headers.getContentTypeAsString();
+ contenttype.add(contentS == null ? "null" : contentS);
+
+ String correlationS = headers.getCorrelationIdAsString();
+ correlationid.add(correlationS == null ? "null" : correlationS);
+
+ deliverymode.add("" + headers.getDeliveryMode());
+
+ AMQShortString encodeSS = headers.getEncoding();
+ encoding.add(encodeSS == null ? "null" : encodeSS.toString());
+
+ expiration.add("" + headers.getExpiration());
+
+ FieldTable headerFT = headers.getHeaders();
+ msgHeaders.add(headerFT == null ? "none" : "" + headerFT.toString());
+
+ priority.add("" + headers.getPriority());
+ propertyflag.add("" + headers.getPropertyFlags());
+
+ AMQShortString replytoSS = headers.getReplyTo();
+ replyto.add(replytoSS == null ? "null" : replytoSS.toString());
+
+ timestamp.add("" + headers.getTimestamp());
+
+ AMQShortString typeSS = headers.getType();
+ type.add(typeSS == null ? "null" : typeSS.toString());
+
+ AMQShortString useridSS = headers.getUserId();
+ userid.add(useridSS == null ? "null" : useridSS.toString());
+
+ MessagePublishInfo info = null;
+ try
+ {
+ info = msg.getMessagePublishInfo();
+ }
+ catch (AMQException e)
+ {
+ //ignore
+ }
+
+ if (info != null)
+ {
+ AMQShortString exchangeSS = info.getExchange();
+ exchange.add(exchangeSS == null ? "null" : exchangeSS.toString());
+
+ AMQShortString routingkeySS = info.getRoutingKey();
+ routingkey.add(routingkeySS == null ? "null" : routingkeySS.toString());
+
+ immediate.add(info.isImmediate() ? "true" : "false");
+ mandatory.add(info.isMandatory() ? "true" : "false");
+ }
+
+// msg.getPublisher(); -- only used in clustering
+// msg.getStoreContext();
+// msg.isAllContentReceived();
+
+ }// if headers!=null
+
+// need to access internal map and do lookups.
+// msg.isTaken();
+// msg.getDeliveredSubscription();
+// msg.isRejectedBy();
+
+ }
+
+ // if id only had the header and the divider in it then we have no data to display
+ if (id.size() == 2)
+ {
+ return null;
+ }
+ return data;
+ }
+
+ protected boolean includeMsg(AMQMessage msg, List<Long> msgids)
+ {
+ if (msgids == null)
+ {
+ return true;
+ }
+
+ Long msgid = msg.getMessageId();
+
+ boolean found = false;
+
+ if (msgids != null)
+ {
+ //check msgid is in msgids
+ for (Long l : msgids)
+ {
+ if (l.equals(msgid))
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+ return found;
+ }
+
+ public enum Columns
+ {
+ ID,
+ Size,
+ Exchange,
+ RoutingKey,
+ isImmediate,
+ isMandatory,
+ isPersistent,
+ isRedelivered,
+ isDelivered,
+ App_ID,
+ Cluster_ID,
+ Content_Type,
+ Correlation_ID,
+ Delivery_Mode,
+ Encoding,
+ Arrival,
+ Expiration,
+ Priority,
+ Property_Flag,
+ ReplyTo,
+ Timestamp,
+ Type,
+ UserID,
+ MsgHeaders
+ }
+}
+
+
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/security/Passwd.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/security/Passwd.java
new file mode 100644
index 0000000000..c27c52eb8e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/security/Passwd.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.security;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.DigestException;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+
+public class Passwd
+{
+ public static void main(String args[]) throws NoSuchAlgorithmException, DigestException, IOException
+ {
+ if (args.length != 2)
+ {
+ System.out.println("Passwd <username> <password>");
+ System.exit(0);
+ }
+
+ byte[] data = args[1].getBytes("utf-8");
+
+ MessageDigest md = MessageDigest.getInstance("MD5");
+
+ for (byte b : data)
+ {
+ md.update(b);
+ }
+
+ byte[] digest = md.digest();
+
+ Base64 b64 = new Base64();
+
+ byte[] encoded = b64.encode(digest);
+
+ output(args[0], encoded);
+ }
+
+ private static void output(String user, byte[] encoded) throws IOException
+ {
+
+// File passwdFile = new File("qpid.passwd");
+
+ PrintStream ps = new PrintStream(System.out);
+
+ user += ":";
+ ps.write(user.getBytes("utf-8"));
+
+ for (byte b : encoded)
+ {
+ ps.write(b);
+ }
+
+ ps.println();
+
+ ps.flush();
+ ps.close();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/CommandParser.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/CommandParser.java
new file mode 100644
index 0000000000..986fea32cc
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/CommandParser.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.utils;
+
+public interface CommandParser
+{
+ /**
+ * If there is more than one command received on the last parse request.
+ *
+ * Subsequent calls to parse will utilise this input rather than reading new data from the input source
+ * @return boolean
+ */
+ boolean more();
+
+ /**
+ * True if the currently parsed command has been requested as a background operation
+ *
+ * @return boolean
+ */
+ boolean isBackground();
+
+ /**
+ * Parses user commands, and groups tokens in the
+ * String[] format that all Java main's love.
+ *
+ * If more than one command is provided in one input line then the more() method will return true.
+ * A subsequent call to parse() will continue to parse that input line before reading new input.
+ *
+ * @return <code>input</code> split in args[] format; null if eof.
+ * @throws java.io.IOException if there is a problem reading from the input stream
+ */
+ String[] parse() throws java.io.IOException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/Console.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/Console.java
new file mode 100644
index 0000000000..cf457d1ea5
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/Console.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.utils;
+
+import java.util.List;
+
+public interface Console
+{
+ public enum CellFormat
+ {
+ CENTRED, LEFT, RIGHT
+ }
+
+ public static String ROW_DIVIDER = "*divider";
+
+ public void print(String... message);
+
+ public void println(String... message);
+
+ public String readln();
+
+ /**
+ * Reads and parses the command line.
+ *
+ *
+ * @return The next command or null
+ */
+ public String[] readCommand();
+
+ public CommandParser getCommandParser();
+
+ public void setCommandParser(CommandParser parser);
+
+ /**
+ *
+ * Prints the list of String nicely.
+ *
+ * +-------------+
+ * | Heading |
+ * +-------------+
+ * | Item 1 |
+ * | Item 2 |
+ * | Item 3 |
+ * +-------------+
+ *
+ * @param hasTitle should list[0] be used as a heading
+ * @param list The list of Strings to display
+ */
+ public void displayList(boolean hasTitle, String... list);
+
+ /**
+ *
+ * Prints the list of String nicely.
+ *
+ * +----------------------------+
+ * | Heading |
+ * +----------------------------+
+ * | title | title | ..
+ * +----------------------------+
+ * | Item 2 | value 2 | ..
+ * +----------------------------+ (*divider)
+ * | Item 3 | value 2 | ..
+ * +----------------------------+
+ *
+ * @param title The title to display if any
+ * @param entries the entries to display in a map.
+ */
+ void printMap(String title, List<List> entries);
+
+
+ public void close();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleCommandParser.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleCommandParser.java
new file mode 100644
index 0000000000..09444ccdd7
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleCommandParser.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.utils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.StringTokenizer;
+
+public class SimpleCommandParser implements CommandParser
+{
+ private static final String COMMAND_SEPERATOR = ";";
+
+ /** Input source of commands */
+ protected BufferedReader _reader;
+
+ /** The next list of commands from the command line */
+ private StringBuilder _nextCommand = null;
+
+ public SimpleCommandParser(BufferedReader reader)
+ {
+ _reader = reader;
+ }
+
+ public boolean more()
+ {
+ return _nextCommand != null;
+ }
+
+ public boolean isBackground()
+ {
+ return false;
+ }
+
+ public String[] parse() throws IOException
+ {
+ String[] commands = null;
+
+ String input = null;
+
+ if (_nextCommand == null)
+ {
+ input = _reader.readLine();
+ }
+ else
+ {
+ input = _nextCommand.toString();
+ _nextCommand = null;
+ }
+
+ if (input == null)
+ {
+ return null;
+ }
+
+ StringTokenizer tok = new StringTokenizer(input, " ");
+
+ int tokenCount = tok.countTokens();
+ int index = 0;
+
+ if (tokenCount > 0)
+ {
+ commands = new String[tokenCount];
+ boolean commandComplete = false;
+
+ while (tok.hasMoreTokens())
+ {
+ String next = tok.nextToken();
+
+ if (next.equals(COMMAND_SEPERATOR))
+ {
+ commandComplete = true;
+ _nextCommand = new StringBuilder();
+ continue;
+ }
+
+ if (commandComplete)
+ {
+ _nextCommand.append(next);
+ _nextCommand.append(" ");
+ }
+ else
+ {
+ commands[index] = next;
+ index++;
+ }
+ }
+
+ }
+
+ //Reduce the String[] if not all the tokens were used in this command.
+ // i.e. there is more than one command on the line.
+ if (index != tokenCount)
+ {
+ String[] shortCommands = new String[index];
+ System.arraycopy(commands, 0, shortCommands, 0, index);
+ return shortCommands;
+ }
+ else
+ {
+ return commands;
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleConsole.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleConsole.java
new file mode 100644
index 0000000000..ec080a4611
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleConsole.java
@@ -0,0 +1,363 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.tools.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+public class SimpleConsole implements Console
+{
+ /** SLF4J Logger. */
+ private static Logger _devlog = LoggerFactory.getLogger(SimpleConsole.class);
+
+ /** Console Writer. */
+ protected static BufferedWriter _consoleWriter;
+
+ /** Console Reader. */
+ protected static BufferedReader _consoleReader;
+
+ /** Parser for command-line input. */
+ protected CommandParser _parser;
+
+ public SimpleConsole(BufferedWriter writer, BufferedReader reader)
+ {
+ _consoleWriter = writer;
+ _consoleReader = reader;
+ _parser = new SimpleCommandParser(_consoleReader);
+ }
+
+ public void print(String... message)
+ {
+ try
+ {
+ for (String s : message)
+ {
+ _consoleWriter.write(s);
+ }
+ _consoleWriter.flush();
+ }
+ catch (IOException e)
+ {
+ _devlog.error(e.getMessage() + ": Occured whilst trying to write:" + message);
+ }
+
+ }
+
+ public void println(String... message)
+ {
+ print(message);
+ print(System.getProperty("line.separator"));
+ }
+
+
+ public String readln()
+ {
+ try
+ {
+ return _consoleReader.readLine();
+ }
+ catch (IOException e)
+ {
+ _devlog.debug("Unable to read input due to:" + e.getMessage());
+ return null;
+ }
+ }
+
+ public String[] readCommand()
+ {
+ try
+ {
+ return _parser.parse();
+ }
+ catch (IOException e)
+ {
+ _devlog.error("Error reading command:" + e.getMessage());
+ return new String[0];
+ }
+ }
+
+ public CommandParser getCommandParser()
+ {
+ return _parser;
+ }
+
+ public void setCommandParser(CommandParser parser)
+ {
+ _parser = parser;
+ }
+
+ public void displayList(boolean hasTitle, String... list)
+ {
+ java.util.List<java.util.List> data = new LinkedList<List>();
+
+ java.util.List<String> values = new LinkedList<String>();
+
+ data.add(values);
+
+ for (String value : list)
+ {
+ values.add(value);
+ }
+
+ if (hasTitle)
+ {
+ values.add(1, "*divider");
+ }
+
+ printMap(null, data);
+ }
+
+ /**
+ *
+ * Prints the list of String nicely.
+ *
+ * +----------------------------+
+ * | Heading |
+ * +----------------------------+
+ * | title | title | ..
+ * +----------------------------+
+ * | Item 2 | value 2 | ..
+ * | Item 3 | value 2 | ..
+ * +----------------------------+
+ *
+ * @param title The title to display if any
+ * @param entries the entries to display in a map.
+ */
+ public void printMap(String title, java.util.List<java.util.List> entries)
+ {
+ try
+ {
+ int columns = entries.size();
+
+ int[] columnWidth = new int[columns];
+
+ // calculate row count
+ int rowMax = 0;
+
+ //the longest item
+ int itemMax = 0;
+
+ for (int i = 0; i < columns; i++)
+ {
+ int columnIRowMax = entries.get(i).size();
+
+ if (columnIRowMax > rowMax)
+ {
+ rowMax = columnIRowMax;
+ }
+ for (Object values : entries.get(i))
+ {
+ if (values.toString().equals(Console.ROW_DIVIDER))
+ {
+ continue;
+ }
+
+ int itemLength = values.toString().length();
+
+ //note for single width
+ if (itemLength > itemMax)
+ {
+ itemMax = itemLength;
+ }
+
+ //note for mulit width
+ if (itemLength > columnWidth[i])
+ {
+ columnWidth[i] = itemLength;
+ }
+
+ }
+ }
+
+ int tableWidth = 0;
+
+
+ for (int i = 0; i < columns; i++)
+ {
+ // plus 2 for the space padding
+ columnWidth[i] += 2;
+ }
+ for (int size : columnWidth)
+ {
+ tableWidth += size;
+ }
+ tableWidth += (columns - 1);
+
+ if (title != null)
+ {
+ if (title.length() > tableWidth)
+ {
+ tableWidth = title.length();
+ }
+
+ printCellRow("+", "-", tableWidth);
+
+ printCell(CellFormat.CENTRED, "|", tableWidth, " " + title + " ", 0);
+ _consoleWriter.newLine();
+
+ }
+
+ //put top line | or bottom of title
+ printCellRow("+", "-", tableWidth);
+
+ //print the table data
+ int row = 0;
+
+ for (; row < rowMax; row++)
+ {
+ for (int i = 0; i < columns; i++)
+ {
+ java.util.List columnData = entries.get(i);
+
+ String value;
+ // does this column have a value for this row
+ if (columnData.size() > row)
+ {
+ value = " " + columnData.get(row).toString() + " ";
+ }
+ else
+ {
+ value = " ";
+ }
+
+ if (i == 0 && value.equals(" " + Console.ROW_DIVIDER + " "))
+ {
+ printCellRow("+", "-", tableWidth);
+ //move on to the next row
+ break;
+ }
+ else
+ {
+ printCell(CellFormat.LEFT, "|", columnWidth[i], value, i);
+ }
+
+ // if it is the last row then do a new line.
+ if (i == columns - 1)
+ {
+ _consoleWriter.newLine();
+ }
+ }
+ }
+
+ printCellRow("+", "-", tableWidth);
+
+ }
+ catch (IOException e)
+ {
+ _devlog.error(e.getMessage() + ": Occured whilst trying to write.");
+ }
+ }
+
+ public void close()
+ {
+
+ try
+ {
+ _consoleReader.close();
+ }
+ catch (IOException e)
+ {
+ _devlog.error(e.getMessage() + ": Occured whilst trying to close reader.");
+ }
+
+ try
+ {
+
+ _consoleWriter.close();
+ }
+ catch (IOException e)
+ {
+ _devlog.error(e.getMessage() + ": Occured whilst trying to close writer.");
+ }
+
+ }
+
+ private void printCell(CellFormat format, String edge, int cellWidth, String cell, int column) throws IOException
+ {
+ int pad = cellWidth - cell.length();
+
+ if (column == 0)
+ {
+ _consoleWriter.write(edge);
+ }
+
+ switch (format)
+ {
+ case CENTRED:
+ printPad(" ", pad / 2);
+ break;
+ case RIGHT:
+ printPad(" ", pad);
+ break;
+ }
+
+ _consoleWriter.write(cell);
+
+
+ switch (format)
+ {
+ case CENTRED:
+ // if pad isn't even put the extra one on the right
+ if (pad % 2 == 0)
+ {
+ printPad(" ", pad / 2);
+ }
+ else
+ {
+ printPad(" ", (pad / 2) + 1);
+ }
+ break;
+ case LEFT:
+ printPad(" ", pad);
+ break;
+ }
+
+
+ _consoleWriter.write(edge);
+
+ }
+
+ private void printCellRow(String edge, String mid, int cellWidth) throws IOException
+ {
+ _consoleWriter.write(edge);
+
+ printPad(mid, cellWidth);
+
+ _consoleWriter.write(edge);
+ _consoleWriter.newLine();
+ }
+
+ private void printPad(String padChar, int count) throws IOException
+ {
+ for (int i = 0; i < count; i++)
+ {
+ _consoleWriter.write(padChar);
+ }
+ }
+
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/AMQBrokerManagerMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/AMQBrokerManagerMBeanTest.java
new file mode 100644
index 0000000000..b94f2ef76f
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/AMQBrokerManagerMBeanTest.java
@@ -0,0 +1,91 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server;
+
+import junit.framework.TestCase;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.management.ManagedBroker;
+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.virtualhost.VirtualHost;
+
+public class AMQBrokerManagerMBeanTest extends TestCase
+{
+ private QueueRegistry _queueRegistry;
+ private ExchangeRegistry _exchangeRegistry;
+
+ public void testExchangeOperations() throws Exception
+ {
+ String exchange1 = "testExchange1_" + System.currentTimeMillis();
+ String exchange2 = "testExchange2_" + System.currentTimeMillis();
+ String exchange3 = "testExchange3_" + System.currentTimeMillis();
+
+ assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange1)) == null);
+ assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange2)) == null);
+ assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange3)) == null);
+
+ VirtualHost vHost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test");
+
+ ManagedBroker mbean = new AMQBrokerManagerMBean((VirtualHost.VirtualHostMBean) vHost.getManagedObject());
+ mbean.createNewExchange(exchange1, "direct", false);
+ mbean.createNewExchange(exchange2, "topic", false);
+ mbean.createNewExchange(exchange3, "headers", false);
+
+ assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange1)) != null);
+ assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange2)) != null);
+ assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange3)) != null);
+
+ mbean.unregisterExchange(exchange1);
+ mbean.unregisterExchange(exchange2);
+ mbean.unregisterExchange(exchange3);
+
+ assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange1)) == null);
+ assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange2)) == null);
+ assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange3)) == null);
+ }
+
+ public void testQueueOperations() throws Exception
+ {
+ String queueName = "testQueue_" + System.currentTimeMillis();
+ VirtualHost vHost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test");
+
+ ManagedBroker mbean = new AMQBrokerManagerMBean((VirtualHost.VirtualHostMBean) vHost.getManagedObject());
+
+ assertTrue(_queueRegistry.getQueue(new AMQShortString(queueName)) == null);
+
+ mbean.createNewQueue(queueName, "test", false);
+ assertTrue(_queueRegistry.getQueue(new AMQShortString(queueName)) != null);
+
+ mbean.deleteQueue(queueName);
+ assertTrue(_queueRegistry.getQueue(new AMQShortString(queueName)) == null);
+ }
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ IApplicationRegistry appRegistry = ApplicationRegistry.getInstance();
+ _queueRegistry = appRegistry.getVirtualHostRegistry().getVirtualHost("test").getQueueRegistry();
+ _exchangeRegistry = appRegistry.getVirtualHostRegistry().getVirtualHost("test").getExchangeRegistry();
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/ExtractResendAndRequeueTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/ExtractResendAndRequeueTest.java
new file mode 100644
index 0000000000..5fbf9484f7
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/ExtractResendAndRequeueTest.java
@@ -0,0 +1,255 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server;
+
+import junit.framework.TestCase;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMapImpl;
+import org.apache.qpid.server.queue.MockQueueEntry;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.queue.SimpleQueueEntryList;
+import org.apache.qpid.server.queue.MockAMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.MockAMQQueue;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.QueueEntryIterator;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.subscription.MockSubscription;
+import org.apache.qpid.AMQException;
+
+import java.util.Map;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Iterator;
+
+/**
+ * QPID-1385 : Race condition between added to unacked map and resending due to a rollback.
+ *
+ * In AMQChannel _unackedMap.clear() was done after the visit. This meant that the clear was not in the same
+ * synchronized block as as the preparation to resend.
+ *
+ * This clearing/prep for resend was done as a result of the rollback call. HOWEVER, the delivery thread was still
+ * in the process of sending messages to the client. It is therefore possible that a message could block on the
+ * _unackedMap lock waiting for the visit to compelete so that it can add the new message to the unackedMap....
+ * which is then cleared by the resend/rollback thread.
+ *
+ * This problem was encountered by the testSend2ThenRollback test.
+ *
+ * To try and increase the chance of the race condition occuring this test will send multiple messages so that the
+ * delivery thread will be in progress while the rollback method is called. Hopefully this will cause the
+ * deliveryTag to be lost
+ */
+public class ExtractResendAndRequeueTest extends TestCase
+{
+
+ UnacknowledgedMessageMapImpl _unacknowledgedMessageMap;
+ private static final int INITIAL_MSG_COUNT = 10;
+ private AMQQueue _queue = new MockAMQQueue();
+ private LinkedList<QueueEntry> _referenceList = new LinkedList<QueueEntry>();
+
+ @Override
+ public void setUp() throws AMQException
+ {
+ _unacknowledgedMessageMap = new UnacknowledgedMessageMapImpl(100);
+
+ long id = 0;
+ SimpleQueueEntryList list = new SimpleQueueEntryList(_queue);
+
+ // Add initial messages to QueueEntryList
+ for (int count = 0; count < INITIAL_MSG_COUNT; count++)
+ {
+ AMQMessage msg = new MockAMQMessage(id);
+
+ list.add(msg);
+
+ //Increment ID;
+ id++;
+ }
+
+ // Iterate through the QueueEntryList and add entries to unacknowledgeMessageMap and referecenList
+ QueueEntryIterator queueEntries = list.iterator();
+ while(queueEntries.advance())
+ {
+ QueueEntry entry = queueEntries.getNode();
+ _unacknowledgedMessageMap.add(entry.getMessage().getMessageId(), entry);
+
+ // Store the entry for future inspection
+ _referenceList.add(entry);
+ }
+
+ assertEquals("Map does not contain correct setup data", INITIAL_MSG_COUNT, _unacknowledgedMessageMap.size());
+ }
+
+ /**
+ * Helper method to create a new subscription and aquire the given messages.
+ *
+ * @param messageList The messages to aquire
+ *
+ * @return Subscription that performed the aquire
+ */
+ private Subscription createSubscriptionAndAquireMessages(LinkedList<QueueEntry> messageList)
+ {
+ Subscription subscription = new MockSubscription();
+
+ // Aquire messages in subscription
+ for (QueueEntry entry : messageList)
+ {
+ entry.acquire(subscription);
+ }
+
+ return subscription;
+ }
+
+ /**
+ * This is the normal consumer rollback method.
+ *
+ * An active consumer that has aquired messages expects those messasges to be reset when rollback is requested.
+ *
+ * This test validates that the msgToResend map includes all the messages and none are left behind.
+ *
+ * @throws AMQException the visit interface throws this
+ */
+ public void testResend() throws AMQException
+ {
+ //We don't need the subscription object here.
+ createSubscriptionAndAquireMessages(_referenceList);
+
+ final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>();
+ final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>();
+
+ // requeueIfUnabletoResend doesn't matter here.
+ _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue,
+ msgToResend, true, new StoreContext()));
+
+ assertEquals("Message count for resend not correct.", INITIAL_MSG_COUNT, msgToResend.size());
+ assertEquals("Message count for requeue not correct.", 0, msgToRequeue.size());
+ assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size());
+ }
+
+ /**
+ * This is the normal consumer close method.
+ *
+ * When a consumer that has aquired messages expects closes the messages that it has aquired should be removed from
+ * the unacknowledgeMap and placed in msgToRequeue
+ *
+ * This test validates that the msgToRequeue map includes all the messages and none are left behind.
+ *
+ * @throws AMQException the visit interface throws this
+ */
+ public void testRequeueDueToSubscriptionClosure() throws AMQException
+ {
+ Subscription subscription = createSubscriptionAndAquireMessages(_referenceList);
+
+ // Close subscription
+ subscription.close();
+
+ final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>();
+ final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>();
+
+ // requeueIfUnabletoResend doesn't matter here.
+ _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue,
+ msgToResend, true, new StoreContext()));
+
+ assertEquals("Message count for resend not correct.", 0, msgToResend.size());
+ assertEquals("Message count for requeue not correct.", INITIAL_MSG_COUNT, msgToRequeue.size());
+ assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size());
+ }
+
+ /**
+ * If the subscription is null, due to message being retrieved via a GET, And we request that messages are requeued
+ * requeueIfUnabletoResend(set to true) then all messages should be sent to the msgToRequeue map.
+ *
+ * @throws AMQException the visit interface throws this
+ */
+
+ public void testRequeueDueToMessageHavingNoConsumerTag() throws AMQException
+ {
+ final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>();
+ final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>();
+
+ // requeueIfUnabletoResend = true so all messages should go to msgToRequeue
+ _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue,
+ msgToResend, true, new StoreContext()));
+
+ assertEquals("Message count for resend not correct.", 0, msgToResend.size());
+ assertEquals("Message count for requeue not correct.", INITIAL_MSG_COUNT, msgToRequeue.size());
+ assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size());
+ }
+
+ /**
+ * If the subscription is null, due to message being retrieved via a GET, And we request that we don't
+ * requeueIfUnabletoResend(set to false) then all messages should be dropped as we do not have a dead letter queue.
+ *
+ * @throws AMQException the visit interface throws this
+ */
+
+ public void testDrop() throws AMQException
+ {
+ final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>();
+ final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>();
+
+ // requeueIfUnabletoResend = false so all messages should be dropped all maps should be empty
+ _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue,
+ msgToResend, false, new StoreContext()));
+
+ assertEquals("Message count for resend not correct.", 0, msgToResend.size());
+ assertEquals("Message count for requeue not correct.", 0, msgToRequeue.size());
+ assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size());
+
+
+ for (QueueEntry entry : _referenceList)
+ {
+ assertTrue("Message was not discarded", entry.isDeleted());
+ }
+
+ }
+
+ /**
+ * If the subscription is null, due to message being retrieved via a GET, AND the queue upon which the message was
+ * delivered has been deleted then it is not possible to requeue. Currently we simply discar the message but in the
+ * future we may wish to dead letter the message.
+ *
+ * Validate that at the end of the visit all Maps are empty and all messages are marked as deleted
+ *
+ * @throws AMQException the visit interface throws this
+ */
+ public void testDiscard() throws AMQException
+ {
+ final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>();
+ final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>();
+
+ _queue.delete();
+
+ // requeueIfUnabletoResend : value doesn't matter here as queue has been deleted
+ _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue,
+ msgToResend, false, new StoreContext()));
+
+ assertEquals("Message count for resend not correct.", 0, msgToResend.size());
+ assertEquals("Message count for requeue not correct.", 0, msgToRequeue.size());
+ assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size());
+
+ for (QueueEntry entry : _referenceList)
+ {
+ assertTrue("Message was not discarded", entry.isDeleted());
+ }
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/RunBrokerWithCommand.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/RunBrokerWithCommand.java
new file mode 100644
index 0000000000..59543874b4
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/RunBrokerWithCommand.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.Level;
+
+import java.io.InputStream;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.IOException;
+
+public class RunBrokerWithCommand
+{
+ public static void main(String[] args)
+ {
+ //Start the broker
+ try
+ {
+ String[] fudge = args.clone();
+
+ // Override the first value which is the command we are going to run later.
+ fudge[0] = "-v";
+ new Main(fudge).startup();
+ }
+ catch (Exception e)
+ {
+ System.err.println("Unable to start broker due to: " + e.getMessage());
+
+ e.printStackTrace();
+ exit(1);
+ }
+
+ Logger.getRootLogger().setLevel(Level.ERROR);
+
+ //run command
+ try
+ {
+ Process task = Runtime.getRuntime().exec(args[0]);
+ System.err.println("Started Proccess: " + args[0]);
+
+ InputStream inputStream = task.getInputStream();
+
+ InputStream errorStream = task.getErrorStream();
+
+ Thread out = new Thread(new Outputter("[OUT]", new BufferedReader(new InputStreamReader(inputStream))));
+ Thread err = new Thread(new Outputter("[ERR]", new BufferedReader(new InputStreamReader(errorStream))));
+
+ out.start();
+ err.start();
+
+ out.join();
+ err.join();
+
+ System.err.println("Waiting for process to exit: " + args[0]);
+ task.waitFor();
+ System.err.println("Done Proccess: " + args[0]);
+
+ }
+ catch (IOException e)
+ {
+ System.err.println("Proccess had problems: " + e.getMessage());
+ e.printStackTrace(System.err);
+ exit(1);
+ }
+ catch (InterruptedException e)
+ {
+ System.err.println("Proccess had problems: " + e.getMessage());
+ e.printStackTrace(System.err);
+
+ exit(1);
+ }
+
+
+ exit(0);
+ }
+
+ private static void exit(int i)
+ {
+ Logger.getRootLogger().setLevel(Level.INFO);
+ System.exit(i);
+ }
+
+ static class Outputter implements Runnable
+ {
+
+ BufferedReader reader;
+ String prefix;
+
+ Outputter(String s, BufferedReader r)
+ {
+ prefix = s;
+ reader = r;
+ }
+
+ public void run()
+ {
+ String line;
+ try
+ {
+ while ((line = reader.readLine()) != null)
+ {
+ System.out.println(prefix + line);
+ }
+ }
+ catch (IOException e)
+ {
+ System.out.println("Error occured reading; " + e.getMessage());
+ }
+ }
+
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/SelectorParserTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/SelectorParserTest.java
new file mode 100644
index 0000000000..a0304a7b01
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/SelectorParserTest.java
@@ -0,0 +1,128 @@
+package org.apache.qpid.server;
+
+import junit.framework.TestCase;
+import org.apache.qpid.server.filter.JMSSelectorFilter;
+import org.apache.qpid.AMQException;/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+public class SelectorParserTest extends TestCase
+{
+ public void testSelectorWithHyphen()
+ {
+ testPass("Cost = 2 AND \"property-with-hyphen\" = 'wibble'");
+ }
+
+ public void testLike()
+ {
+ testFail("Cost LIKE 2");
+ testPass("Cost LIKE 'Hello'");
+ }
+
+ public void testStringQuoted()
+ {
+ testPass("string = 'Test'");
+ }
+
+ public void testProperty()
+ {
+ testPass("prop1 = prop2");
+ }
+
+ public void testPropertyNames()
+ {
+ testPass("$min= TRUE AND _max= FALSE AND Prop_2 = true AND prop$3 = false");
+ }
+
+ public void testProtected()
+ {
+ testFail("NULL = 0 ");
+ testFail("TRUE = 0 ");
+ testFail("FALSE = 0 ");
+ testFail("NOT = 0 ");
+ testFail("AND = 0 ");
+ testFail("OR = 0 ");
+ testFail("BETWEEN = 0 ");
+ testFail("LIKE = 0 ");
+ testFail("IN = 0 ");
+ testFail("IS = 0 ");
+ testFail("ESCAPE = 0 ");
+ }
+
+
+ public void testBoolean()
+ {
+ testPass("min= TRUE AND max= FALSE ");
+ testPass("min= true AND max= false");
+ }
+
+ public void testDouble()
+ {
+ testPass("positive=31E2 AND negative=-31.4E3");
+ testPass("min=" + Double.MIN_VALUE + " AND max=" + Double.MAX_VALUE);
+ }
+
+ public void testLong()
+ {
+ testPass("minLong=" + Long.MIN_VALUE + "L AND maxLong=" + Long.MAX_VALUE + "L");
+ }
+
+ public void testInt()
+ {
+ testPass("minInt=" + Integer.MIN_VALUE + " AND maxInt=" + Integer.MAX_VALUE);
+ }
+
+ public void testSigned()
+ {
+ testPass("negative=-42 AND positive=+42");
+ }
+
+ public void testOctal()
+ {
+ testPass("octal=042");
+ }
+
+
+ private void testPass(String selector)
+ {
+ try
+ {
+ new JMSSelectorFilter(selector);
+ }
+ catch (AMQException e)
+ {
+ fail("Selector '" + selector + "' was not parsed :" + e.getMessage());
+ }
+ }
+
+ private void testFail(String selector)
+ {
+ try
+ {
+ new JMSSelectorFilter(selector);
+ fail("Selector '" + selector + "' was parsed ");
+ }
+ catch (AMQException e)
+ {
+ //normal path
+ }
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/AcknowledgeTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/AcknowledgeTest.java
new file mode 100644
index 0000000000..9ef4af2932
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/AcknowledgeTest.java
@@ -0,0 +1,120 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.ack;
+
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.protocol.InternalTestProtocolSession;
+import org.apache.qpid.server.util.InternalBrokerBaseCase;
+
+import java.util.List;
+
+public class AcknowledgeTest extends InternalBrokerBaseCase
+{
+
+ public void testTransactionalSingleAck() throws AMQException
+ {
+ _channel.setLocalTransactional();
+ runMessageAck(1, 1, 1, false, 0);
+ }
+
+ public void testTransactionalMultiAck() throws AMQException
+ {
+ _channel.setLocalTransactional();
+ runMessageAck(10, 1, 5, true, 5);
+ }
+
+ public void testTransactionalAckAll() throws AMQException
+ {
+ _channel.setLocalTransactional();
+ runMessageAck(10, 1, 0, true, 0);
+ }
+
+ public void testNonTransactionalSingleAck() throws AMQException
+ {
+ runMessageAck(1, 1, 1, false, 0);
+ }
+
+ public void testNonTransactionalMultiAck() throws AMQException
+ {
+ runMessageAck(10, 1, 5, true, 5);
+ }
+
+ public void testNonTransactionalAckAll() throws AMQException
+ {
+ runMessageAck(10, 1, 0, true, 0);
+ }
+
+ protected void runMessageAck(int sendMessageCount, long firstDeliveryTag, long acknowledgeDeliveryTag, boolean acknowldegeMultiple, int remainingUnackedMessages) throws AMQException
+ {
+ //Check store is empty
+ checkStoreContents(0);
+
+ //Send required messsages to the queue
+ publishMessages(_session, _channel, sendMessageCount);
+
+ if (_channel.isTransactional())
+ {
+ _channel.commit();
+ }
+
+ //Ensure they are stored
+ checkStoreContents(sendMessageCount);
+
+ //Check that there are no unacked messages
+ assertEquals("Channel should have no unacked msgs ", 0, _channel.getUnacknowledgedMessageMap().size());
+
+ //Subscribe to the queue
+ AMQShortString subscriber = subscribe(_session, _channel, _queue);
+
+ _queue.deliverAsync();
+
+ //Wait for the messages to be delivered
+ _session.awaitDelivery(sendMessageCount);
+
+ //Check that they are all waiting to be acknoledged
+ assertEquals("Channel should have unacked msgs", sendMessageCount, _channel.getUnacknowledgedMessageMap().size());
+
+ List<InternalTestProtocolSession.DeliveryPair> messages = _session.getDelivers(_channel.getChannelId(), subscriber, sendMessageCount);
+
+ //Double check we received the right number of messages
+ assertEquals(sendMessageCount, messages.size());
+
+ //Check that the first message has the expected deliveryTag
+ assertEquals("First message does not have expected deliveryTag", firstDeliveryTag, messages.get(0).getDeliveryTag());
+
+ //Send required Acknowledgement
+ _channel.acknowledgeMessage(acknowledgeDeliveryTag, acknowldegeMultiple);
+
+ if (_channel.isTransactional())
+ {
+ _channel.commit();
+ }
+
+ // Check Remaining Acknowledgements
+ assertEquals("Channel unacked msgs count incorrect", remainingUnackedMessages, _channel.getUnacknowledgedMessageMap().size());
+
+ //Check store contents are also correct.
+ checkStoreContents(remainingUnackedMessages);
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/TxAckTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/TxAckTest.java
new file mode 100644
index 0000000000..dfbbd56d6f
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/TxAckTest.java
@@ -0,0 +1,283 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.ack;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.MessageHandleFactory;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.queue.AMQMessageHandle;
+import org.apache.qpid.server.queue.AMQQueueFactory;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.store.TestMemoryMessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.store.MemoryMessageStore;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+import org.apache.qpid.server.txn.TransactionalContext;
+
+import java.util.*;
+
+public class TxAckTest extends TestCase
+{
+ private Scenario individual;
+ private Scenario multiple;
+ private Scenario combined;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ //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);
+ }
+
+ @Override
+ protected void tearDown() throws Exception
+ {
+ individual.stop();
+ multiple.stop();
+ combined.stop();
+ }
+
+ public void testPrepare() throws AMQException
+ {
+ individual.prepare();
+ multiple.prepare();
+ combined.prepare();
+ }
+
+ public void testUndoPrepare() throws AMQException
+ {
+ individual.undoPrepare();
+ multiple.undoPrepare();
+ combined.undoPrepare();
+ }
+
+ public void testCommit() throws AMQException
+ {
+ individual.commit();
+ multiple.commit();
+ combined.commit();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(TxAckTest.class);
+ }
+
+ private class Scenario
+ {
+ private final UnacknowledgedMessageMap _map = new UnacknowledgedMessageMapImpl(5000);
+ private final TxAck _op = new TxAck(_map);
+ private final List<Long> _acked;
+ private final List<Long> _unacked;
+ private StoreContext _storeContext = new StoreContext();
+ private AMQQueue _queue;
+
+ Scenario(int messageCount, List<Long> acked, List<Long> unacked) throws Exception
+ {
+ TransactionalContext txnContext = new NonTransactionalContext(new TestMemoryMessageStore(),
+ _storeContext, null,
+ new LinkedList<RequiredDeliveryException>()
+ );
+
+ PropertiesConfiguration env = new PropertiesConfiguration();
+ env.setProperty("name", "test");
+ VirtualHost virtualHost = new VirtualHost(new VirtualHostConfiguration("test", env), null);
+
+ _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("test"), false, null, false,
+ virtualHost, null);
+
+ for (int i = 0; i < messageCount; i++)
+ {
+ long deliveryTag = i + 1;
+
+ MessagePublishInfo info = new MessagePublishInfo()
+ {
+
+ public AMQShortString getExchange()
+ {
+ return null;
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isImmediate()
+ {
+ return false;
+ }
+
+ public boolean isMandatory()
+ {
+ return false;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return null;
+ }
+ };
+
+ TestMessage message = new TestMessage(deliveryTag, i, info, txnContext.getStoreContext());
+ _map.add(deliveryTag, _queue.enqueue(new StoreContext(), message));
+ }
+ _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)
+ {
+ QueueEntry u = _map.get(tag);
+ assertTrue("Message not found for tag " + tag, u != null);
+ ((TestMessage) u.getMessage()).assertCountEquals(expected);
+ }
+ }
+
+ void prepare() throws AMQException
+ {
+ _op.consolidate();
+ _op.prepare(_storeContext);
+
+ assertCount(_acked, -1);
+ assertCount(_unacked, 0);
+
+ }
+
+ void undoPrepare()
+ {
+ _op.consolidate();
+ _op.undoPrepare();
+
+ assertCount(_acked, 1);
+ assertCount(_unacked, 0);
+ }
+
+ void commit()
+ {
+ _op.consolidate();
+ _op.commit(_storeContext);
+
+ //check acked messages are removed from map
+ Set<Long> keys = new HashSet<Long>(_map.getDeliveryTags());
+ 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(_map.getDeliveryTags());
+ assertTrue("Expected messages with following tags to still be in map: " + keys, keys.isEmpty());
+ }
+
+ public void stop()
+ {
+ _queue.stop();
+ }
+ }
+
+ private static AMQMessageHandle createMessageHandle(final long messageId, final MessagePublishInfo publishBody)
+ {
+ final AMQMessageHandle amqMessageHandle = (new MessageHandleFactory()).createMessageHandle(messageId,
+ null,
+ false);
+ try
+ {
+ amqMessageHandle.setPublishAndContentHeaderBody(new StoreContext(),
+ publishBody,
+ new ContentHeaderBody()
+ {
+ public int getSize()
+ {
+ return 1;
+ }
+ });
+ }
+ catch (AMQException e)
+ {
+ // won't happen
+ }
+
+
+ return amqMessageHandle;
+ }
+
+
+ private class TestMessage extends AMQMessage
+ {
+ private final long _tag;
+ private int _count;
+
+ TestMessage(long tag, long messageId, MessagePublishInfo publishBody, StoreContext storeContext)
+ throws AMQException
+ {
+ super(createMessageHandle(messageId, publishBody), storeContext, publishBody);
+ _tag = tag;
+ }
+
+
+ public boolean incrementReference()
+ {
+ _count++;
+ return true;
+ }
+
+ public void decrementReference(StoreContext context)
+ {
+ _count--;
+ }
+
+ void assertCountEquals(int expected)
+ {
+ assertEquals("Wrong count for message with tag " + _tag, expected, _count);
+ }
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java
new file mode 100644
index 0000000000..9692cf2727
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java
@@ -0,0 +1,136 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.configuration;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+
+public class QueueConfigurationTest extends TestCase
+{
+
+ private VirtualHostConfiguration _emptyConf;
+ private PropertiesConfiguration _env;
+ private ServerConfiguration _fullServerConf;
+ private VirtualHostConfiguration _fullHostConf;
+
+ public void setUp() throws Exception
+ {
+ _env = new PropertiesConfiguration();
+ _emptyConf = new VirtualHostConfiguration("test", _env);
+
+ PropertiesConfiguration fullEnv = new PropertiesConfiguration();
+ fullEnv.setProperty("queues.maximumMessageAge", 1);
+ fullEnv.setProperty("queues.maximumQueueDepth", 1);
+ fullEnv.setProperty("queues.maximumMessageSize", 1);
+ fullEnv.setProperty("queues.maximumMessageCount", 1);
+ fullEnv.setProperty("queues.minimumAlertRepeatGap", 1);
+
+ _fullHostConf = new VirtualHostConfiguration("test", fullEnv);
+
+ }
+
+ public void testGetMaximumMessageAge()
+ {
+ // Check default value
+ QueueConfiguration qConf = new QueueConfiguration("test", _env, _emptyConf);
+ assertEquals(0, qConf.getMaximumMessageAge());
+
+ // Check explicit value
+ PropertiesConfiguration fullEnv = new PropertiesConfiguration();
+ fullEnv.setProperty("maximumMessageAge", 2);
+ qConf = new QueueConfiguration("test", fullEnv, _fullHostConf);
+ assertEquals(2, qConf.getMaximumMessageAge());
+
+ // Check inherited value
+ qConf = new QueueConfiguration("test", _env, _fullHostConf);
+ assertEquals(1, qConf.getMaximumMessageAge());
+ }
+
+ public void testGetMaximumQueueDepth()
+ {
+ // Check default value
+ QueueConfiguration qConf = new QueueConfiguration("test", _env, _emptyConf);
+ assertEquals(0, qConf.getMaximumQueueDepth());
+
+ // Check explicit value
+ PropertiesConfiguration fullEnv = new PropertiesConfiguration();
+ fullEnv.setProperty("maximumQueueDepth", 2);
+ qConf = new QueueConfiguration("test", fullEnv, _fullHostConf);
+ assertEquals(2, qConf.getMaximumQueueDepth());
+
+ // Check inherited value
+ qConf = new QueueConfiguration("test", _env, _fullHostConf);
+ assertEquals(1, qConf.getMaximumQueueDepth());
+ }
+
+ public void testGetMaximumMessageSize()
+ {
+ // Check default value
+ QueueConfiguration qConf = new QueueConfiguration("test", _env, _emptyConf);
+ assertEquals(0, qConf.getMaximumMessageSize());
+
+ // Check explicit value
+ PropertiesConfiguration fullEnv = new PropertiesConfiguration();
+ fullEnv.setProperty("maximumMessageSize", 2);
+ qConf = new QueueConfiguration("test", fullEnv, _fullHostConf);
+ assertEquals(2, qConf.getMaximumMessageSize());
+
+ // Check inherited value
+ qConf = new QueueConfiguration("test", _env, _fullHostConf);
+ assertEquals(1, qConf.getMaximumMessageSize());
+ }
+
+ public void testGetMaximumMessageCount()
+ {
+ // Check default value
+ QueueConfiguration qConf = new QueueConfiguration("test", _env, _emptyConf);
+ assertEquals(0, qConf.getMaximumMessageCount());
+
+ // Check explicit value
+ PropertiesConfiguration fullEnv = new PropertiesConfiguration();
+ fullEnv.setProperty("maximumMessageCount", 2);
+ qConf = new QueueConfiguration("test", fullEnv, _fullHostConf);
+ assertEquals(2, qConf.getMaximumMessageCount());
+
+ // Check inherited value
+ qConf = new QueueConfiguration("test", _env, _fullHostConf);
+ assertEquals(1, qConf.getMaximumMessageCount());
+ }
+
+ public void testGetMinimumAlertRepeatGap()
+ {
+ // Check default value
+ QueueConfiguration qConf = new QueueConfiguration("test", _env, _emptyConf);
+ assertEquals(0, qConf.getMinimumAlertRepeatGap());
+
+ // Check explicit value
+ PropertiesConfiguration fullEnv = new PropertiesConfiguration();
+ fullEnv.setProperty("minimumAlertRepeatGap", 2);
+ qConf = new QueueConfiguration("test", fullEnv, _fullHostConf);
+ assertEquals(2, qConf.getMinimumAlertRepeatGap());
+
+ // Check inherited value
+ qConf = new QueueConfiguration("test", _env, _fullHostConf);
+ assertEquals(1, qConf.getMinimumAlertRepeatGap());
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java
new file mode 100644
index 0000000000..2c39d006b9
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java
@@ -0,0 +1,867 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.configuration;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.List;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.commons.configuration.SystemConfiguration;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.server.protocol.AMQMinaProtocolSession;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.TestIoSession;
+import org.apache.qpid.server.queue.MockProtocolSession;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry;
+import org.apache.qpid.server.security.access.ACLManager;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+
+import junit.framework.TestCase;
+
+public class ServerConfigurationTest extends TestCase
+{
+
+ private XMLConfiguration _config;
+
+ @Override
+ public void setUp()
+ {
+ _config = new XMLConfiguration();
+ }
+
+ @Override
+ public void tearDown()
+ {
+ ApplicationRegistry.removeAll();
+ }
+
+ public void testSetJMXManagementPort() throws ConfigurationException
+ {
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ serverConfig.setJMXManagementPort(23);
+ assertEquals(23, serverConfig.getJMXManagementPort());
+ }
+
+ public void testGetJMXManagementPort() throws ConfigurationException
+ {
+ _config.setProperty("management.jmxport", 42);
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(42, serverConfig.getJMXManagementPort());
+ }
+
+ public void testGetPlatformMbeanserver() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getPlatformMbeanserver());
+
+ // Check value we set
+ _config.setProperty("management.platform-mbeanserver", false);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getPlatformMbeanserver());
+ }
+
+ public void testGetPluginDirectory() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(null, serverConfig.getPluginDirectory());
+
+ // Check value we set
+ _config.setProperty("plugin-directory", "/path/to/plugins");
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals("/path/to/plugins", serverConfig.getPluginDirectory());
+ }
+
+ public void testGetPrincipalDatabaseNames() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(0, serverConfig.getPrincipalDatabaseNames().size());
+
+ // Check value we set
+ _config.setProperty("security.principal-databases.principal-database(0).name", "a");
+ _config.setProperty("security.principal-databases.principal-database(1).name", "b");
+ serverConfig = new ServerConfiguration(_config);
+ List<String> dbs = serverConfig.getPrincipalDatabaseNames();
+ assertEquals(2, dbs.size());
+ assertEquals("a", dbs.get(0));
+ assertEquals("b", dbs.get(1));
+ }
+
+ public void testGetPrincipalDatabaseClass() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(0, serverConfig.getPrincipalDatabaseClass().size());
+
+ // Check value we set
+ _config.setProperty("security.principal-databases.principal-database(0).class", "a");
+ _config.setProperty("security.principal-databases.principal-database(1).class", "b");
+ serverConfig = new ServerConfiguration(_config);
+ List<String> dbs = serverConfig.getPrincipalDatabaseClass();
+ assertEquals(2, dbs.size());
+ assertEquals("a", dbs.get(0));
+ assertEquals("b", dbs.get(1));
+ }
+
+ public void testGetPrincipalDatabaseAttributeNames() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(0, serverConfig.getPrincipalDatabaseAttributeNames(1).size());
+
+ // Check value we set
+ _config.setProperty("security.principal-databases.principal-database(0).attributes(0).attribute.name", "a");
+ _config.setProperty("security.principal-databases.principal-database(0).attributes(1).attribute.name", "b");
+ serverConfig = new ServerConfiguration(_config);
+ List<String> dbs = serverConfig.getPrincipalDatabaseAttributeNames(0);
+ assertEquals(2, dbs.size());
+ assertEquals("a", dbs.get(0));
+ assertEquals("b", dbs.get(1));
+ }
+
+ public void testGetPrincipalDatabaseAttributeValues() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(0, serverConfig.getPrincipalDatabaseAttributeValues(1).size());
+
+ // Check value we set
+ _config.setProperty("security.principal-databases.principal-database(0).attributes(0).attribute.value", "a");
+ _config.setProperty("security.principal-databases.principal-database(0).attributes(1).attribute.value", "b");
+ serverConfig = new ServerConfiguration(_config);
+ List<String> dbs = serverConfig.getPrincipalDatabaseAttributeValues(0);
+ assertEquals(2, dbs.size());
+ assertEquals("a", dbs.get(0));
+ assertEquals("b", dbs.get(1));
+ }
+
+ public void testGetManagementAccessList() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(0, serverConfig.getManagementAccessList().size());
+
+ // Check value we set
+ _config.setProperty("security.jmx.access(0)", "a");
+ _config.setProperty("security.jmx.access(1)", "b");
+ serverConfig = new ServerConfiguration(_config);
+ List<String> dbs = serverConfig.getManagementAccessList();
+ assertEquals(2, dbs.size());
+ assertEquals("a", dbs.get(0));
+ assertEquals("b", dbs.get(1));
+ }
+
+ public void testGetFrameSize() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(65536, serverConfig.getFrameSize());
+
+ // Check value we set
+ _config.setProperty("advanced.framesize", "23");
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(23, serverConfig.getFrameSize());
+ }
+
+ public void testGetProtectIOEnabled() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getProtectIOEnabled());
+
+ // Check value we set
+ _config.setProperty("broker.connector.protectio.enabled", true);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getProtectIOEnabled());
+ }
+
+ public void testGetBufferReadLimit() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(262144, serverConfig.getBufferReadLimit());
+
+ // Check value we set
+ _config.setProperty("broker.connector.protectio.readBufferLimitSize", 23);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(23, serverConfig.getBufferReadLimit());
+ }
+
+ public void testGetBufferWriteLimit() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(262144, serverConfig.getBufferWriteLimit());
+
+ // Check value we set
+ _config.setProperty("broker.connector.protectio.writeBufferLimitSize", 23);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(23, serverConfig.getBufferWriteLimit());
+ }
+
+ public void testGetSynchedClocks() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getSynchedClocks());
+
+ // Check value we set
+ _config.setProperty("advanced.synced-clocks", true);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getSynchedClocks());
+ }
+
+ public void testGetMsgAuth() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getMsgAuth());
+
+ // Check value we set
+ _config.setProperty("security.msg-auth", true);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getMsgAuth());
+ }
+
+ public void testGetJMXPrincipalDatabase() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(null, serverConfig.getJMXPrincipalDatabase());
+
+ // Check value we set
+ _config.setProperty("security.jmx.principal-database", "a");
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals("a", serverConfig.getJMXPrincipalDatabase());
+ }
+
+ public void testGetManagementKeyStorePath() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(null, serverConfig.getManagementKeyStorePath());
+
+ // Check value we set
+ _config.setProperty("management.ssl.keyStorePath", "a");
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals("a", serverConfig.getManagementKeyStorePath());
+ }
+
+ public void testGetManagementSSLEnabled() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getManagementSSLEnabled());
+
+ // Check value we set
+ _config.setProperty("management.ssl.enabled", false);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getManagementSSLEnabled());
+ }
+
+ public void testGetManagementKeyStorePassword() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(null, serverConfig.getManagementKeyStorePassword());
+
+ // Check value we set
+ _config.setProperty("management.ssl.keyStorePassword", "a");
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals("a", serverConfig.getManagementKeyStorePassword());
+ }
+
+ public void testGetQueueAutoRegister() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getQueueAutoRegister());
+
+ // Check value we set
+ _config.setProperty("queue.auto_register", false);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getQueueAutoRegister());
+ }
+
+ public void testGetManagementEnabled() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getManagementEnabled());
+
+ // Check value we set
+ _config.setProperty("management.enabled", false);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getManagementEnabled());
+ }
+
+ public void testSetManagementEnabled() throws ConfigurationException
+ {
+ // Check value we set
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ serverConfig.setManagementEnabled(false);
+ assertEquals(false, serverConfig.getManagementEnabled());
+ }
+
+ public void testGetHeartBeatDelay() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(5, serverConfig.getHeartBeatDelay());
+
+ // Check value we set
+ _config.setProperty("heartbeat.delay", 23);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(23, serverConfig.getHeartBeatDelay());
+ }
+
+ public void testGetHeartBeatTimeout() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(2.0, serverConfig.getHeartBeatTimeout());
+
+ // Check value we set
+ _config.setProperty("heartbeat.timeoutFactor", 2.3);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(2.3, serverConfig.getHeartBeatTimeout());
+ }
+
+ public void testGetMaximumMessageAge() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(0, serverConfig.getMaximumMessageAge());
+
+ // Check value we set
+ _config.setProperty("maximumMessageAge", 10L);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(10, serverConfig.getMaximumMessageAge());
+ }
+
+ public void testGetMaximumMessageCount() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(0, serverConfig.getMaximumMessageCount());
+
+ // Check value we set
+ _config.setProperty("maximumMessageCount", 10L);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(10, serverConfig.getMaximumMessageCount());
+ }
+
+ public void testGetMaximumQueueDepth() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(0, serverConfig.getMaximumQueueDepth());
+
+ // Check value we set
+ _config.setProperty("maximumQueueDepth", 10L);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(10, serverConfig.getMaximumQueueDepth());
+ }
+
+ public void testGetMaximumMessageSize() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(0, serverConfig.getMaximumMessageSize());
+
+ // Check value we set
+ _config.setProperty("maximumMessageSize", 10L);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(10, serverConfig.getMaximumMessageSize());
+ }
+
+ public void testGetMinimumAlertRepeatGap() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(0, serverConfig.getMinimumAlertRepeatGap());
+
+ // Check value we set
+ _config.setProperty("minimumAlertRepeatGap", 10L);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(10, serverConfig.getMinimumAlertRepeatGap());
+ }
+
+ public void testGetProcessors() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(4, serverConfig.getProcessors());
+
+ // Check value we set
+ _config.setProperty("connector.processors", 10);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(10, serverConfig.getProcessors());
+ }
+
+ public void testGetPort() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(5672, serverConfig.getPort());
+
+ // Check value we set
+ _config.setProperty("connector.port", 10);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(10, serverConfig.getPort());
+ }
+
+ public void testGetBind() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals("wildcard", serverConfig.getBind());
+
+ // Check value we set
+ _config.setProperty("connector.bind", "a");
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals("a", serverConfig.getBind());
+ }
+
+ public void testGetReceiveBufferSize() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(32767, serverConfig.getReceiveBufferSize());
+
+ // Check value we set
+ _config.setProperty("connector.socketReceiveBuffer", "23");
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(23, serverConfig.getReceiveBufferSize());
+ }
+
+ public void testGetWriteBufferSize() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(32767, serverConfig.getWriteBufferSize());
+
+ // Check value we set
+ _config.setProperty("connector.socketWriteBuffer", "23");
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(23, serverConfig.getWriteBufferSize());
+ }
+
+ public void testGetTcpNoDelay() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getTcpNoDelay());
+
+ // Check value we set
+ _config.setProperty("connector.tcpNoDelay", false);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getTcpNoDelay());
+ }
+
+ public void testGetEnableExecutorPool() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getEnableExecutorPool());
+
+ // Check value we set
+ _config.setProperty("advanced.filterchain[@enableExecutorPool]", true);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getEnableExecutorPool());
+ }
+
+ public void testGetEnablePooledAllocator() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getEnablePooledAllocator());
+
+ // Check value we set
+ _config.setProperty("advanced.enablePooledAllocator", true);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getEnablePooledAllocator());
+ }
+
+ public void testGetEnableDirectBuffers() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getEnableDirectBuffers());
+
+ // Check value we set
+ _config.setProperty("advanced.enableDirectBuffers", true);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getEnableDirectBuffers());
+ }
+
+ public void testGetEnableSSL() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getEnableSSL());
+
+ // Check value we set
+ _config.setProperty("connector.ssl.enabled", true);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getEnableSSL());
+ }
+
+ public void testGetSSLOnly() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getSSLOnly());
+
+ // Check value we set
+ _config.setProperty("connector.ssl.sslOnly", false);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getSSLOnly());
+ }
+
+ public void testGetSSLPort() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(8672, serverConfig.getSSLPort());
+
+ // Check value we set
+ _config.setProperty("connector.ssl.port", 23);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(23, serverConfig.getSSLPort());
+ }
+
+ public void testGetKeystorePath() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals("none", serverConfig.getKeystorePath());
+
+ // Check value we set
+ _config.setProperty("connector.ssl.keystorePath", "a");
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals("a", serverConfig.getKeystorePath());
+ }
+
+ public void testGetKeystorePassword() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals("none", serverConfig.getKeystorePassword());
+
+ // Check value we set
+ _config.setProperty("connector.ssl.keystorePassword", "a");
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals("a", serverConfig.getKeystorePassword());
+ }
+
+ public void testGetCertType() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals("SunX509", serverConfig.getCertType());
+
+ // Check value we set
+ _config.setProperty("connector.ssl.certType", "a");
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals("a", serverConfig.getCertType());
+ }
+
+ public void testGetQpidNIO() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getQpidNIO());
+
+ // Check value we set
+ _config.setProperty("connector.qpidnio", true);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getQpidNIO());
+ }
+
+ public void testGetUseBiasedWrites() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(false, serverConfig.getUseBiasedWrites());
+
+ // Check value we set
+ _config.setProperty("advanced.useWriteBiasedPool", true);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(true, serverConfig.getUseBiasedWrites());
+ }
+
+ public void testGetHousekeepingExpiredMessageCheckPeriod() throws ConfigurationException
+ {
+ // Check default
+ ServerConfiguration serverConfig = new ServerConfiguration(_config);
+ assertEquals(30000, serverConfig.getHousekeepingCheckPeriod());
+
+ // Check value we set
+ _config.setProperty("housekeeping.expiredMessageCheckPeriod", 23L);
+ serverConfig = new ServerConfiguration(_config);
+ assertEquals(23, serverConfig.getHousekeepingCheckPeriod());
+ serverConfig.setHousekeepingExpiredMessageCheckPeriod(42L);
+ assertEquals(42, serverConfig.getHousekeepingCheckPeriod());
+ }
+
+ public void testSingleConfiguration() throws IOException, ConfigurationException
+ {
+ File fileA = File.createTempFile(getClass().getName(), null);
+ fileA.deleteOnExit();
+ FileWriter out = new FileWriter(fileA);
+ out.write("<broker><connector><port>2342</port><ssl><port>4235</port></ssl></connector></broker>");
+ out.close();
+ ServerConfiguration conf = new ServerConfiguration(fileA);
+ assertEquals(4235, conf.getSSLPort());
+ }
+
+ public void testCombinedConfiguration() throws IOException, ConfigurationException
+ {
+ File mainFile = File.createTempFile(getClass().getName(), null);
+ File fileA = File.createTempFile(getClass().getName(), null);
+ File fileB = File.createTempFile(getClass().getName(), null);
+
+ mainFile.deleteOnExit();
+ fileA.deleteOnExit();
+ fileB.deleteOnExit();
+
+ FileWriter out = new FileWriter(mainFile);
+ out.write("<configuration><system/>");
+ out.write("<xml fileName=\"" + fileA.getAbsolutePath() + "\"/>");
+ out.write("<xml fileName=\"" + fileB.getAbsolutePath() + "\"/>");
+ out.write("</configuration>");
+ out.close();
+
+ out = new FileWriter(fileA);
+ out.write("<broker><connector><port>2342</port><ssl><port>4235</port></ssl></connector></broker>");
+ out.close();
+
+ out = new FileWriter(fileB);
+ out.write("<broker><connector><ssl><port>2345</port></ssl><qpidnio>true</qpidnio></connector></broker>");
+ out.close();
+
+ ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile());
+ assertEquals(4235, config.getSSLPort()); // From first file, not
+ // overriden by second
+ assertEquals(2342, config.getPort()); // From the first file, not
+ // present in the second
+ assertEquals(true, config.getQpidNIO()); // From the second file, not
+ // present in the first
+ }
+
+ public void testVariableInterpolation() throws Exception
+ {
+ File mainFile = File.createTempFile(getClass().getName(), null);
+
+ mainFile.deleteOnExit();
+
+ FileWriter out = new FileWriter(mainFile);
+ out.write("<broker>\n");
+ out.write("\t<work>foo</work>\n");
+ out.write("\t<management><ssl><keyStorePath>${work}</keyStorePath></ssl></management>\n");
+ out.write("</broker>\n");
+ out.close();
+
+ ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile());
+ assertEquals("Did not get correct interpolated value",
+ "foo", config.getManagementKeyStorePath());
+ }
+
+ public void testCombinedConfigurationFirewall() throws Exception
+ {
+ // Write out config
+ File mainFile = File.createTempFile(getClass().getName(), null);
+ File fileA = File.createTempFile(getClass().getName(), null);
+ File fileB = File.createTempFile(getClass().getName(), null);
+
+ mainFile.deleteOnExit();
+ fileA.deleteOnExit();
+ fileB.deleteOnExit();
+
+ FileWriter out = new FileWriter(mainFile);
+ out.write("<configuration><system/>");
+ out.write("<xml fileName=\"" + fileA.getAbsolutePath() + "\"/>");
+ out.write("</configuration>");
+ out.close();
+
+ out = new FileWriter(fileA);
+ out.write("<broker>\n");
+ out.write("\t<management><enabled>false</enabled></management>\n");
+ out.write("\t<security>\n");
+ out.write("\t\t<principal-databases>\n");
+ out.write("\t\t\t<principal-database>\n");
+ out.write("\t\t\t\t<name>passwordfile</name>\n");
+ out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n");
+ out.write("\t\t\t\t<attributes>\n");
+ out.write("\t\t\t\t\t<attribute>\n");
+ out.write("\t\t\t\t\t\t<name>passwordFile</name>\n");
+ out.write("\t\t\t\t\t\t<value>/dev/null</value>\n");
+ out.write("\t\t\t\t\t</attribute>\n");
+ out.write("\t\t\t\t</attributes>\n");
+ out.write("\t\t\t</principal-database>\n");
+ out.write("\t\t</principal-databases>\n");
+ out.write("\t\t<jmx>\n");
+ out.write("\t\t\t<access>/dev/null</access>\n");
+ out.write("\t\t\t<principal-database>passwordfile</principal-database>\n");
+ out.write("\t\t</jmx>\n");
+ out.write("\t\t<firewall>\n");
+ out.write("\t\t\t<xml fileName=\"" + fileB.getAbsolutePath() + "\"/>");
+ out.write("\t\t</firewall>\n");
+ out.write("\t</security>\n");
+ out.write("\t<virtualhosts>\n");
+ out.write("\t\t<virtualhost>\n");
+ out.write("\t\t\t<name>test</name>\n");
+ out.write("\t\t</virtualhost>\n");
+ out.write("\t</virtualhosts>\n");
+ out.write("</broker>\n");
+ out.close();
+
+ out = new FileWriter(fileB);
+ out.write("<firewall>\n");
+ out.write("\t<rule access=\"deny\" network=\"127.0.0.1\"/>");
+ out.write("</firewall>\n");
+ out.close();
+
+ // Load config
+ ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile);
+ ApplicationRegistry.initialise(reg, 1);
+
+ // Test config
+ TestIoSession iosession = new TestIoSession();
+ iosession.setAddress("127.0.0.1");
+ VirtualHostRegistry virtualHostRegistry = reg.getVirtualHostRegistry();
+ VirtualHost virtualHost = virtualHostRegistry.getVirtualHost("test");
+ AMQCodecFactory codecFactory = new AMQCodecFactory(true);
+ AMQProtocolSession session = new AMQMinaProtocolSession(iosession, virtualHostRegistry, codecFactory);
+ assertFalse(reg.getAccessManager().authoriseConnect(session, virtualHost));
+ }
+
+ public void testCombinedConfigurationFirewallReload() throws Exception
+ {
+ // Write out config
+ File mainFile = File.createTempFile(getClass().getName(), null);
+ File fileA = File.createTempFile(getClass().getName(), null);
+ File fileB = File.createTempFile(getClass().getName(), null);
+
+ mainFile.deleteOnExit();
+ fileA.deleteOnExit();
+ fileB.deleteOnExit();
+
+ FileWriter out = new FileWriter(mainFile);
+ out.write("<configuration><system/>");
+ out.write("<xml fileName=\"" + fileA.getAbsolutePath() + "\"/>");
+ out.write("</configuration>");
+ out.close();
+
+ out = new FileWriter(fileA);
+ out.write("<broker>\n");
+ out.write("\t<management><enabled>false</enabled></management>\n");
+ out.write("\t<security>\n");
+ out.write("\t\t<principal-databases>\n");
+ out.write("\t\t\t<principal-database>\n");
+ out.write("\t\t\t\t<name>passwordfile</name>\n");
+ out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n");
+ out.write("\t\t\t\t<attributes>\n");
+ out.write("\t\t\t\t\t<attribute>\n");
+ out.write("\t\t\t\t\t\t<name>passwordFile</name>\n");
+ out.write("\t\t\t\t\t\t<value>/dev/null</value>\n");
+ out.write("\t\t\t\t\t</attribute>\n");
+ out.write("\t\t\t\t</attributes>\n");
+ out.write("\t\t\t</principal-database>\n");
+ out.write("\t\t</principal-databases>\n");
+ out.write("\t\t<jmx>\n");
+ out.write("\t\t\t<access>/dev/null</access>\n");
+ out.write("\t\t\t<principal-database>passwordfile</principal-database>\n");
+ out.write("\t\t</jmx>\n");
+ out.write("\t\t<firewall>\n");
+ out.write("\t\t\t<xml fileName=\"" + fileB.getAbsolutePath() + "\"/>");
+ out.write("\t\t</firewall>\n");
+ out.write("\t</security>\n");
+ out.write("\t<virtualhosts>\n");
+ out.write("\t\t<virtualhost>\n");
+ out.write("\t\t\t<name>test</name>\n");
+ out.write("\t\t</virtualhost>\n");
+ out.write("\t</virtualhosts>\n");
+ out.write("</broker>\n");
+ out.close();
+
+ out = new FileWriter(fileB);
+ out.write("<firewall>\n");
+ out.write("\t<rule access=\"deny\" network=\"127.0.0.1\"/>");
+ out.write("</firewall>\n");
+ out.close();
+
+ // Load config
+ ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile);
+ ApplicationRegistry.initialise(reg, 1);
+
+ // Test config
+ TestIoSession iosession = new TestIoSession();
+ iosession.setAddress("127.0.0.1");
+ VirtualHostRegistry virtualHostRegistry = reg.getVirtualHostRegistry();
+ VirtualHost virtualHost = virtualHostRegistry.getVirtualHost("test");
+ AMQCodecFactory codecFactory = new AMQCodecFactory(true);
+ AMQProtocolSession session = new AMQMinaProtocolSession(iosession, virtualHostRegistry, codecFactory);
+ assertFalse(reg.getAccessManager().authoriseConnect(session, virtualHost));
+
+ RandomAccessFile fileBRandom = new RandomAccessFile(fileB, "rw");
+ fileBRandom.setLength(0);
+ fileBRandom.seek(0);
+ fileBRandom.close();
+
+ out = new FileWriter(fileB);
+ out.write("<firewall>\n");
+ out.write("\t<rule access=\"allow\" network=\"127.0.0.1\"/>");
+ out.write("</firewall>\n");
+ out.close();
+
+ reg.getConfiguration().reparseConfigFile();
+
+ assertTrue(reg.getAccessManager().authoriseConnect(session, virtualHost));
+
+ fileBRandom = new RandomAccessFile(fileB, "rw");
+ fileBRandom.setLength(0);
+ fileBRandom.seek(0);
+ fileBRandom.close();
+
+ out = new FileWriter(fileB);
+ out.write("<firewall>\n");
+ out.write("\t<rule access=\"deny\" network=\"127.0.0.1\"/>");
+ out.write("</firewall>\n");
+ out.close();
+
+ reg.getConfiguration().reparseConfigFile();
+
+ assertFalse(reg.getAccessManager().authoriseConnect(session, virtualHost));
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java
new file mode 100644
index 0000000000..3b83190e42
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java
@@ -0,0 +1,50 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.configuration;
+
+import org.apache.qpid.configuration.PropertyException;
+import org.apache.qpid.configuration.PropertyUtils;
+
+import junit.framework.TestCase;
+
+// TODO: This belongs in the "common" module.
+public class TestPropertyUtils extends TestCase
+{
+ public void testSimpleExpansion() throws PropertyException
+ {
+ System.setProperty("banana", "fruity");
+ String expandedProperty = PropertyUtils.replaceProperties("${banana}");
+ assertEquals(expandedProperty, "fruity");
+ }
+
+ 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 junit.framework.TestSuite(TestPropertyUtils.class);
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java
new file mode 100644
index 0000000000..ba504d3064
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.configuration;
+
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.queue.AMQPriorityQueue;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class VirtualHostConfigurationTest extends TestCase
+{
+
+ private VirtualHostConfiguration vhostConfig;
+ private XMLConfiguration configXml;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ // Fill config file with stuff
+ configXml = new XMLConfiguration();
+ configXml.setRootElementName("virtualhosts");
+ configXml.addProperty("virtualhost(-1).name", "test");
+ }
+
+ public void testQueuePriority() throws Exception
+ {
+ // Set up queue with 5 priorities
+ configXml.addProperty("virtualhost.test.queues(-1).queue(-1).name(-1)",
+ "atest");
+ configXml.addProperty("virtualhost.test.queues.queue.atest(-1).exchange",
+ "amq.direct");
+ configXml.addProperty("virtualhost.test.queues.queue.atest.priorities",
+ "5");
+
+ // Set up queue with JMS style priorities
+ configXml.addProperty("virtualhost.test.queues(-1).queue(-1).name(-1)",
+ "ptest");
+ configXml.addProperty("virtualhost.test.queues.queue.ptest(-1).exchange",
+ "amq.direct");
+ configXml.addProperty("virtualhost.test.queues.queue.ptest.priority",
+ "true");
+
+ // Set up queue with no priorities
+ configXml.addProperty("virtualhost.test.queues(-1).queue(-1).name(-1)",
+ "ntest");
+ configXml.addProperty("virtualhost.test.queues.queue.ntest(-1).exchange",
+ "amq.direct");
+ configXml.addProperty("virtualhost.test.queues.queue.ntest.priority",
+ "false");
+
+ VirtualHost vhost = new VirtualHost(new VirtualHostConfiguration("test", configXml.subset("virtualhost.test")));
+
+ // Check that atest was a priority queue with 5 priorities
+ AMQQueue atest = vhost.getQueueRegistry().getQueue(new AMQShortString("atest"));
+ assertTrue(atest instanceof AMQPriorityQueue);
+ assertEquals(5, ((AMQPriorityQueue) atest).getPriorities());
+
+ // Check that ptest was a priority queue with 10 priorities
+ AMQQueue ptest = vhost.getQueueRegistry().getQueue(new AMQShortString("ptest"));
+ assertTrue(ptest instanceof AMQPriorityQueue);
+ assertEquals(10, ((AMQPriorityQueue) ptest).getPriorities());
+
+ // Check that ntest wasn't a priority queue
+ AMQQueue ntest = vhost.getQueueRegistry().getQueue(new AMQShortString("ntest"));
+ assertFalse(ntest instanceof AMQPriorityQueue);
+ }
+
+ public void testQueueAlerts() throws Exception
+ {
+ // Set up queue with 5 priorities
+ configXml.addProperty("virtualhost.test.queues.exchange", "amq.topic");
+ configXml.addProperty("virtualhost.test.queues.maximumQueueDepth", "1");
+ configXml.addProperty("virtualhost.test.queues.maximumMessageSize", "2");
+ configXml.addProperty("virtualhost.test.queues.maximumMessageAge", "3");
+
+ configXml.addProperty("virtualhost.test.queues(-1).queue(1).name(1)", "atest");
+ configXml.addProperty("virtualhost.test.queues.queue.atest(-1).exchange", "amq.direct");
+ configXml.addProperty("virtualhost.test.queues.queue.atest(-1).maximumQueueDepth", "4");
+ configXml.addProperty("virtualhost.test.queues.queue.atest(-1).maximumMessageSize", "5");
+ configXml.addProperty("virtualhost.test.queues.queue.atest(-1).maximumMessageAge", "6");
+
+ configXml.addProperty("virtualhost.test.queues(-1).queue(-1).name(-1)", "btest");
+
+ VirtualHost vhost = new VirtualHost(new VirtualHostConfiguration("test", configXml.subset("virtualhost.test")));
+
+ // Check specifically configured values
+ AMQQueue aTest = vhost.getQueueRegistry().getQueue(new AMQShortString("atest"));
+ assertEquals(4, aTest.getMaximumQueueDepth());
+ assertEquals(5, aTest.getMaximumMessageSize());
+ assertEquals(6, aTest.getMaximumMessageAge());
+
+ // Check default values
+ AMQQueue bTest = vhost.getQueueRegistry().getQueue(new AMQShortString("btest"));
+ assertEquals(1, bTest.getMaximumQueueDepth());
+ assertEquals(2, bTest.getMaximumMessageSize());
+ assertEquals(3, bTest.getMaximumMessageAge());
+
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java
new file mode 100644
index 0000000000..6dcb187a37
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java
@@ -0,0 +1,562 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import junit.framework.TestCase;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.server.queue.*;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.SkeletonMessageStore;
+import org.apache.qpid.server.store.MemoryMessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.log4j.Logger;
+
+import java.util.*;
+
+public class AbstractHeadersExchangeTestBase extends TestCase
+{
+ private static final Logger _log = Logger.getLogger(AbstractHeadersExchangeTestBase.class);
+
+ private final HeadersExchange exchange = new HeadersExchange();
+ protected final Set<TestQueue> queues = new HashSet<TestQueue>();
+
+ /**
+ * Not used in this test, just there to stub out the routing calls
+ */
+ private MessageStore _store = new MemoryMessageStore();
+
+ private StoreContext _storeContext = new StoreContext();
+
+ private MessageHandleFactory _handleFactory = new MessageHandleFactory();
+
+ private int count;
+
+ public void testDoNothing()
+ {
+ // this is here only to make junit under Eclipse happy
+ }
+
+ 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(new AMQShortString(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);
+ m.getIncomingMessage().routingComplete(_store, _handleFactory);
+ if(m.getIncomingMessage().allContentReceived())
+ {
+ m.getIncomingMessage().deliverToQueues();
+ }
+ }
+
+ protected void routeAndTest(Message m, TestQueue... expected) throws AMQException
+ {
+ routeAndTest(m, false, Arrays.asList(expected));
+ }
+
+ protected void routeAndTest(Message m, boolean expectReturn, TestQueue... expected) throws AMQException
+ {
+ routeAndTest(m, expectReturn, Arrays.asList(expected));
+ }
+
+ protected void routeAndTest(Message m, List<TestQueue> expected) throws AMQException
+ {
+ routeAndTest(m, false, expected);
+ }
+
+ protected void routeAndTest(Message m, boolean expectReturn, List<TestQueue> expected) throws AMQException
+ {
+ try
+ {
+ route(m);
+ assertFalse("Expected "+m+" to be returned due to manadatory flag, and lack of routing",expectReturn);
+ for (TestQueue q : queues)
+ {
+ if (expected.contains(q))
+ {
+ assertTrue("Expected " + m + " to be delivered to " + q, q.isInQueue(m));
+ //assert m.isInQueue(q) : "Expected " + m + " to be delivered to " + q;
+ }
+ else
+ {
+ assertFalse("Did not expect " + m + " to be delivered to " + q, q.isInQueue(m));
+ //assert !m.isInQueue(q) : "Did not expect " + m + " to be delivered to " + q;
+ }
+ }
+ }
+
+ catch (NoRouteException ex)
+ {
+ assertTrue("Expected "+m+" not to be returned",expectReturn);
+ }
+
+ }
+
+ static FieldTable getHeaders(String... entries)
+ {
+ FieldTable headers = FieldTableFactory.newFieldTable();
+ for (String s : entries)
+ {
+ String[] parts = s.split("=", 2);
+ headers.setObject(parts[0], parts.length > 1 ? parts[1] : "");
+ }
+ return headers;
+ }
+
+
+ static final class MessagePublishInfoImpl implements MessagePublishInfo
+ {
+ private AMQShortString _exchange;
+ private boolean _immediate;
+ private boolean _mandatory;
+ private AMQShortString _routingKey;
+
+ public MessagePublishInfoImpl(AMQShortString routingKey)
+ {
+ _routingKey = routingKey;
+ }
+
+ public MessagePublishInfoImpl(AMQShortString exchange, boolean immediate, boolean mandatory, AMQShortString routingKey)
+ {
+ _exchange = exchange;
+ _immediate = immediate;
+ _mandatory = mandatory;
+ _routingKey = routingKey;
+ }
+
+ public AMQShortString getExchange()
+ {
+ return _exchange;
+ }
+
+ public boolean isImmediate()
+ {
+ return _immediate;
+
+ }
+
+ public boolean isMandatory()
+ {
+ return _mandatory;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return _routingKey;
+ }
+
+
+ public void setExchange(AMQShortString exchange)
+ {
+ _exchange = exchange;
+ }
+
+ public void setImmediate(boolean immediate)
+ {
+ _immediate = immediate;
+ }
+
+ public void setMandatory(boolean mandatory)
+ {
+ _mandatory = mandatory;
+ }
+
+ public void setRoutingKey(AMQShortString routingKey)
+ {
+ _routingKey = routingKey;
+ }
+ }
+
+ static MessagePublishInfo getPublishRequest(final String id)
+ {
+ return new MessagePublishInfoImpl(null, false, false, new AMQShortString(id));
+ }
+
+ 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 SimpleAMQQueue
+ {
+ final List<HeadersExchangeTest.Message> messages = new ArrayList<HeadersExchangeTest.Message>();
+
+ public TestQueue(AMQShortString name) throws AMQException
+ {
+ super(name, false, new AMQShortString("test"), true, ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test"));
+ ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test").getQueueRegistry().registerQueue(this);
+ }
+
+ /**
+ * We override this method so that the default behaviour, which attempts to use a delivery manager, is
+ * not invoked. It is unnecessary since for this test we only care to know whether the message was
+ * sent to the queue; the queue processing logic is not being tested.
+ * @param msg
+ * @throws AMQException
+ */
+ @Override
+ public QueueEntry enqueue(StoreContext context, AMQMessage msg) throws AMQException
+ {
+ messages.add( new HeadersExchangeTest.Message(msg));
+ return new QueueEntry()
+ {
+
+ public AMQQueue getQueue()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public AMQMessage getMessage()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getSize()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean getDeliveredToConsumer()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean expired() throws AMQException
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isAcquired()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean acquire()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean acquire(Subscription sub)
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean delete()
+ {
+ return false;
+ }
+
+ public boolean isDeleted()
+ {
+ return false;
+ }
+
+ public boolean acquiredBySubscription()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setDeliveredToSubscription()
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void release()
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public String debugIdentity()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean immediateAndNotDelivered()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setRedelivered(boolean b)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public Subscription getDeliveredSubscription()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void reject()
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void reject(Subscription subscription)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isRejectedBy(Subscription subscription)
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void requeue(StoreContext storeContext) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void dequeue(final StoreContext storeContext) throws FailedDequeueException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void dispose(final StoreContext storeContext) throws MessageCleanupException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void restoreCredit()
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void discard(StoreContext storeContext) throws FailedDequeueException, MessageCleanupException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isQueueDeleted()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void addStateChangeListener(StateChangeListener listener)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean removeStateChangeListener(StateChangeListener listener)
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public int compareTo(final QueueEntry o)
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+ };
+ }
+
+ boolean isInQueue(Message msg)
+ {
+ return messages.contains(msg);
+ }
+
+ }
+
+ /**
+ * Just add some extra utility methods to AMQMessage to aid testing.
+ */
+ static class Message extends AMQMessage
+ {
+ private class TestIncomingMessage extends IncomingMessage
+ {
+
+ public TestIncomingMessage(final long messageId,
+ final MessagePublishInfo info,
+ final TransactionalContext txnContext,
+ final AMQProtocolSession publisher)
+ {
+ super(messageId, info, txnContext, publisher);
+ }
+
+
+ public AMQMessage getUnderlyingMessage()
+ {
+ return Message.this;
+ }
+
+
+ public ContentHeaderBody getContentHeaderBody()
+ {
+ try
+ {
+ return Message.this.getContentHeaderBody();
+ }
+ catch (AMQException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private IncomingMessage _incoming;
+
+ private static MessageStore _messageStore = new SkeletonMessageStore();
+
+ private static StoreContext _storeContext = new StoreContext();
+
+
+ private static TransactionalContext _txnContext = new NonTransactionalContext(_messageStore, _storeContext,
+ null,
+ new LinkedList<RequiredDeliveryException>()
+ );
+
+ Message(String id, String... headers) throws AMQException
+ {
+ this(id, getHeaders(headers));
+ }
+
+ Message(String id, FieldTable headers) throws AMQException
+ {
+ this(_messageStore.getNewMessageId(),getPublishRequest(id), getContentHeader(headers), null);
+ }
+
+ public IncomingMessage getIncomingMessage()
+ {
+ return _incoming;
+ }
+
+ private Message(long messageId,
+ MessagePublishInfo publish,
+ ContentHeaderBody header,
+ List<ContentBody> bodies) throws AMQException
+ {
+ super(createMessageHandle(messageId, publish, header), _txnContext.getStoreContext(), publish);
+
+
+
+ _incoming = new TestIncomingMessage(getMessageId(),publish,_txnContext,new MockProtocolSession(_messageStore));
+ _incoming.setContentHeaderBody(header);
+
+
+ }
+
+ private static AMQMessageHandle createMessageHandle(final long messageId,
+ final MessagePublishInfo publish,
+ final ContentHeaderBody header)
+ {
+
+ final AMQMessageHandle amqMessageHandle = (new MessageHandleFactory()).createMessageHandle(messageId,
+ _messageStore,
+ true);
+
+ try
+ {
+ amqMessageHandle.setPublishAndContentHeaderBody(new StoreContext(),publish,header);
+ }
+ catch (AMQException e)
+ {
+
+ }
+ return amqMessageHandle;
+ }
+
+ private Message(AMQMessage msg) throws AMQException
+ {
+ super(msg);
+ }
+
+
+
+ void route(Exchange exchange) throws AMQException
+ {
+ exchange.route(_incoming);
+ }
+
+
+ public int hashCode()
+ {
+ return getKey().hashCode();
+ }
+
+ public boolean equals(Object o)
+ {
+ return o instanceof HeadersExchangeTest.Message && equals((HeadersExchangeTest.Message) o);
+ }
+
+ private boolean equals(HeadersExchangeTest.Message m)
+ {
+ return getKey().equals(m.getKey());
+ }
+
+ public String toString()
+ {
+ return getKey().toString();
+ }
+
+ private Object getKey()
+ {
+ try
+ {
+ return getMessagePublishInfo().getRoutingKey();
+ }
+ catch (AMQException e)
+ {
+ _log.error("Error getting routing key: " + e, e);
+ return null;
+ }
+ }
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.java
new file mode 100644
index 0000000000..aa25e207a9
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.java
@@ -0,0 +1,595 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import junit.framework.TestCase;
+import junit.framework.Assert;
+import org.apache.qpid.server.queue.*;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.MemoryMessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.protocol.InternalTestProtocolSession;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+
+import java.util.LinkedList;
+
+public class DestWildExchangeTest extends TestCase
+{
+
+ TopicExchange _exchange;
+
+ VirtualHost _vhost;
+ MessageStore _store;
+ StoreContext _context;
+
+ InternalTestProtocolSession _protocolSession;
+
+
+ public void setUp() throws AMQException
+ {
+ _exchange = new TopicExchange();
+ _vhost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHosts().iterator().next();
+ _store = new MemoryMessageStore();
+ _context = new StoreContext();
+ _protocolSession = new InternalTestProtocolSession();
+ }
+
+ public void tearDown()
+ {
+ ApplicationRegistry.remove(1);
+ }
+
+
+ public void testNoRoute() throws AMQException
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a*#b"), false, null, false, _vhost, null);
+ _exchange.registerQueue(new AMQShortString("a.*.#.b"), queue, null);
+
+
+ MessagePublishInfo info = new PublishInfo(new AMQShortString("a.b"));
+
+ IncomingMessage message = new IncomingMessage(0L, info, null, _protocolSession);
+
+ _exchange.route(message);
+
+ Assert.assertEquals(0, queue.getMessageCount());
+ }
+
+ public void testDirectMatch() throws AMQException
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("ab"), false, null, false, _vhost, null);
+ _exchange.registerQueue(new AMQShortString("a.b"), queue, null);
+
+
+ IncomingMessage message = createMessage("a.b");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a.c");
+
+ try
+ {
+ routeMessage(message);
+ fail("Message has no route and should fail to be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+ }
+
+
+ public void testStarMatch() throws AMQException
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a*"), false, null, false, _vhost, null);
+ _exchange.registerQueue(new AMQShortString("a.*"), queue, null);
+
+
+ IncomingMessage message = createMessage("a.b");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a.c");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a");
+
+ try
+ {
+ routeMessage(message);
+ fail("Message has no route and should fail to be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+ }
+
+ public void testHashMatch() throws AMQException
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a#"), false, null, false, _vhost, null);
+ _exchange.registerQueue(new AMQShortString("a.#"), queue, null);
+
+
+ IncomingMessage message = createMessage("a.b.c");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a.b");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a.c");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ message = createMessage("a");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("b");
+
+ try
+ {
+ routeMessage(message);
+ fail("Message has no route and should fail to be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+ }
+
+
+ public void testMidHash() throws AMQException
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a"), false, null, false, _vhost, null);
+ _exchange.registerQueue(new AMQShortString("a.*.#.b"), queue, null);
+
+
+ IncomingMessage message = createMessage("a.c.d.b");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has no route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ message = createMessage("a.c.b");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has no route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+ public void testMatchafterHash() throws AMQException
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a#"), false, null, false, _vhost, null);
+ _exchange.registerQueue(new AMQShortString("a.*.#.b.c"), queue, null);
+
+
+ IncomingMessage message = createMessage("a.c.b.b");
+
+ try
+ {
+ routeMessage(message);
+ fail("Message has route and should not be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a.a.b.c");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has no route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ message = createMessage("a.b.c.b");
+
+ try
+ {
+ routeMessage(message);
+ fail("Message has route and should not be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ message = createMessage("a.b.c.b.c");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has no route and should be routed");
+
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+
+ public void testHashAfterHash() throws AMQException
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a#"), false, null, false, _vhost, null);
+ _exchange.registerQueue(new AMQShortString("a.*.#.b.c.#.d"), queue, null);
+
+
+ IncomingMessage message = createMessage("a.c.b.b.c");
+
+ try
+ {
+ routeMessage(message);
+ fail("Message has route and should not be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a.a.b.c.d");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has no route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+ public void testHashHash() throws AMQException
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a#"), false, null, false, _vhost, null);
+ _exchange.registerQueue(new AMQShortString("a.#.*.#.d"), queue, null);
+
+
+ IncomingMessage message = createMessage("a.c.b.b.c");
+
+ try
+ {
+ routeMessage(message);
+ fail("Message has route and should not be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ message = createMessage("a.a.b.c.d");
+
+ try
+ {
+ routeMessage(message);
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has no route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+ public void testSubMatchFails() throws AMQException
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a"), false, null, false, _vhost, null);
+ _exchange.registerQueue(new AMQShortString("a.b.c.d"), queue, null);
+
+
+ IncomingMessage message = createMessage("a.b.c");
+
+ try
+ {
+ routeMessage(message);
+ fail("Message has route and should not be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+ private void routeMessage(final IncomingMessage message)
+ throws AMQException
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, new MessageHandleFactory());
+ message.deliverToQueues();
+ }
+
+ public void testMoreRouting() throws AMQException
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a"), false, null, false, _vhost, null);
+ _exchange.registerQueue(new AMQShortString("a.b"), queue, null);
+
+
+ IncomingMessage message = createMessage("a.b.c");
+
+ try
+ {
+ routeMessage(message);
+ fail("Message has route and should not be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+ public void testMoreQueue() throws AMQException
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a"), false, null, false, _vhost, null);
+ _exchange.registerQueue(new AMQShortString("a.b"), queue, null);
+
+
+ IncomingMessage message = createMessage("a");
+
+ try
+ {
+ routeMessage(message);
+ fail("Message has route and should not be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+ private IncomingMessage createMessage(String s) throws AMQException
+ {
+ MessagePublishInfo info = new PublishInfo(new AMQShortString(s));
+
+ TransactionalContext trancontext = new NonTransactionalContext(_store, _context, null,
+ new LinkedList<RequiredDeliveryException>()
+ );
+
+ IncomingMessage message = new IncomingMessage(0L, info, trancontext,_protocolSession);
+ message.setContentHeaderBody( new ContentHeaderBody());
+
+
+ return message;
+ }
+
+
+ class PublishInfo implements MessagePublishInfo
+ {
+ AMQShortString _routingkey;
+
+ PublishInfo(AMQShortString routingkey)
+ {
+ _routingkey = routingkey;
+ }
+
+ public AMQShortString getExchange()
+ {
+ return null;
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+
+ }
+
+ public boolean isImmediate()
+ {
+ return false;
+ }
+
+ public boolean isMandatory()
+ {
+ return true;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return _routingkey;
+ }
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/ExchangeMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/ExchangeMBeanTest.java
new file mode 100644
index 0000000000..8ce7b4c0e1
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/ExchangeMBeanTest.java
@@ -0,0 +1,145 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import junit.framework.TestCase;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.AMQQueueFactory;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+
+import javax.management.openmbean.TabularData;
+import java.util.ArrayList;
+
+/**
+ * Unit test class for testing different Exchange MBean operations
+ */
+public class ExchangeMBeanTest extends TestCase
+{
+ private AMQQueue _queue;
+ private QueueRegistry _queueRegistry;
+ private VirtualHost _virtualHost;
+
+ /**
+ * Test for direct exchange mbean
+ * @throws Exception
+ */
+
+ public void testDirectExchangeMBean() throws Exception
+ {
+ DirectExchange exchange = new DirectExchange();
+ exchange.initialise(_virtualHost, ExchangeDefaults.DIRECT_EXCHANGE_NAME, false, 0, true);
+ ManagedObject managedObj = exchange.getManagedObject();
+ ManagedExchange mbean = (ManagedExchange)managedObj;
+
+ mbean.createNewBinding(_queue.getName().toString(), "binding1");
+ mbean.createNewBinding(_queue.getName().toString(), "binding2");
+
+ TabularData data = mbean.bindings();
+ ArrayList<Object> list = new ArrayList<Object>(data.values());
+ assertTrue(list.size() == 2);
+
+ // test general exchange properties
+ assertEquals(mbean.getName(), "amq.direct");
+ assertEquals(mbean.getExchangeType(), "direct");
+ assertTrue(mbean.getTicketNo() == 0);
+ assertTrue(!mbean.isDurable());
+ assertTrue(mbean.isAutoDelete());
+ }
+
+ /**
+ * Test for "topic" exchange mbean
+ * @throws Exception
+ */
+
+ public void testTopicExchangeMBean() throws Exception
+ {
+ TopicExchange exchange = new TopicExchange();
+ exchange.initialise(_virtualHost,ExchangeDefaults.TOPIC_EXCHANGE_NAME, false, 0, true);
+ ManagedObject managedObj = exchange.getManagedObject();
+ ManagedExchange mbean = (ManagedExchange)managedObj;
+
+ mbean.createNewBinding(_queue.getName().toString(), "binding1");
+ mbean.createNewBinding(_queue.getName().toString(), "binding2");
+
+ TabularData data = mbean.bindings();
+ ArrayList<Object> list = new ArrayList<Object>(data.values());
+ assertTrue(list.size() == 2);
+
+ // test general exchange properties
+ assertEquals(mbean.getName(), "amq.topic");
+ assertEquals(mbean.getExchangeType(), "topic");
+ assertTrue(mbean.getTicketNo() == 0);
+ assertTrue(!mbean.isDurable());
+ assertTrue(mbean.isAutoDelete());
+ }
+
+ /**
+ * Test for "Headers" exchange mbean
+ * @throws Exception
+ */
+
+ public void testHeadersExchangeMBean() throws Exception
+ {
+ HeadersExchange exchange = new HeadersExchange();
+ exchange.initialise(_virtualHost,ExchangeDefaults.HEADERS_EXCHANGE_NAME, false, 0, true);
+ ManagedObject managedObj = exchange.getManagedObject();
+ ManagedExchange mbean = (ManagedExchange)managedObj;
+
+ mbean.createNewBinding(_queue.getName().toString(), "key1=binding1,key2=binding2");
+ mbean.createNewBinding(_queue.getName().toString(), "key3=binding3");
+
+ TabularData data = mbean.bindings();
+ ArrayList<Object> list = new ArrayList<Object>(data.values());
+ assertTrue(list.size() == 2);
+
+ // test general exchange properties
+ assertEquals(mbean.getName(), "amq.match");
+ assertEquals(mbean.getExchangeType(), "headers");
+ assertTrue(mbean.getTicketNo() == 0);
+ assertTrue(!mbean.isDurable());
+ assertTrue(mbean.isAutoDelete());
+ }
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ IApplicationRegistry applicationRegistry = ApplicationRegistry.getInstance(1);
+ _virtualHost = applicationRegistry.getVirtualHostRegistry().getVirtualHost("test");
+ _queueRegistry = _virtualHost.getQueueRegistry();
+ _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue"), false, new AMQShortString("ExchangeMBeanTest"), false, _virtualHost,
+ null);
+ _queueRegistry.registerQueue(_queue);
+ }
+
+ protected void tearDown()
+ {
+ ApplicationRegistry.remove(1);
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java
new file mode 100644
index 0000000000..86ba96bf5d
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java
@@ -0,0 +1,199 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import junit.framework.TestCase;
+import org.apache.qpid.framing.FieldTable;
+
+/**
+ */
+public class HeadersBindingTest extends TestCase
+{
+ private FieldTable bindHeaders = new FieldTable();
+ private FieldTable matchHeaders = new FieldTable();
+
+ public void testDefault_1()
+ {
+ bindHeaders.setString("A", "Value of A");
+
+ matchHeaders.setString("A", "Value of A");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testDefault_2()
+ {
+ bindHeaders.setString("A", "Value of A");
+
+ matchHeaders.setString("A", "Value of A");
+ matchHeaders.setString("B", "Value of B");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testDefault_3()
+ {
+ bindHeaders.setString("A", "Value of A");
+
+ matchHeaders.setString("A", "Altered value of A");
+
+ assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testAll_1()
+ {
+ bindHeaders.setString("X-match", "all");
+ bindHeaders.setString("A", "Value of A");
+
+ matchHeaders.setString("A", "Value of A");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testAll_2()
+ {
+ bindHeaders.setString("X-match", "all");
+ bindHeaders.setString("A", "Value of A");
+ bindHeaders.setString("B", "Value of B");
+
+ matchHeaders.setString("A", "Value of A");
+
+ assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testAll_3()
+ {
+ bindHeaders.setString("X-match", "all");
+ bindHeaders.setString("A", "Value of A");
+ bindHeaders.setString("B", "Value of B");
+
+ matchHeaders.setString("A", "Value of A");
+ matchHeaders.setString("B", "Value of B");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testAll_4()
+ {
+ bindHeaders.setString("X-match", "all");
+ bindHeaders.setString("A", "Value of A");
+ bindHeaders.setString("B", "Value of B");
+
+ matchHeaders.setString("A", "Value of A");
+ matchHeaders.setString("B", "Value of B");
+ matchHeaders.setString("C", "Value of C");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testAll_5()
+ {
+ bindHeaders.setString("X-match", "all");
+ bindHeaders.setString("A", "Value of A");
+ bindHeaders.setString("B", "Value of B");
+
+ matchHeaders.setString("A", "Value of A");
+ matchHeaders.setString("B", "Altered value of B");
+ matchHeaders.setString("C", "Value of C");
+
+ assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testAny_1()
+ {
+ bindHeaders.setString("X-match", "any");
+ bindHeaders.setString("A", "Value of A");
+
+ matchHeaders.setString("A", "Value of A");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testAny_2()
+ {
+ bindHeaders.setString("X-match", "any");
+ bindHeaders.setString("A", "Value of A");
+ bindHeaders.setString("B", "Value of B");
+
+ matchHeaders.setString("A", "Value of A");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testAny_3()
+ {
+ bindHeaders.setString("X-match", "any");
+ bindHeaders.setString("A", "Value of A");
+ bindHeaders.setString("B", "Value of B");
+
+ matchHeaders.setString("A", "Value of A");
+ matchHeaders.setString("B", "Value of B");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testAny_4()
+ {
+ bindHeaders.setString("X-match", "any");
+ bindHeaders.setString("A", "Value of A");
+ bindHeaders.setString("B", "Value of B");
+
+ matchHeaders.setString("A", "Value of A");
+ matchHeaders.setString("B", "Value of B");
+ matchHeaders.setString("C", "Value of C");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testAny_5()
+ {
+ bindHeaders.setString("X-match", "any");
+ bindHeaders.setString("A", "Value of A");
+ bindHeaders.setString("B", "Value of B");
+
+ matchHeaders.setString("A", "Value of A");
+ matchHeaders.setString("B", "Altered value of B");
+ matchHeaders.setString("C", "Value of C");
+
+ assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public void testAny_6()
+ {
+ bindHeaders.setString("X-match", "any");
+ bindHeaders.setString("A", "Value of A");
+ bindHeaders.setString("B", "Value of B");
+
+ matchHeaders.setString("A", "Altered value of A");
+ matchHeaders.setString("B", "Altered value of B");
+ matchHeaders.setString("C", "Value of C");
+
+ assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders));
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(HeadersBindingTest.class);
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java
new file mode 100644
index 0000000000..fd11ddeae2
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java
@@ -0,0 +1,106 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.util.NullApplicationRegistry;
+import org.apache.qpid.framing.BasicPublishBody;
+
+public class HeadersExchangeTest extends AbstractHeadersExchangeTestBase
+{
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ ApplicationRegistry.initialise(new NullApplicationRegistry(), 1);
+ }
+
+ protected void tearDown()
+ {
+ ApplicationRegistry.remove(1);
+ }
+
+ public void testSimple() 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");
+
+ 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"));
+
+ Message m7 = new Message("Message7", "XXXXX");
+
+ MessagePublishInfoImpl pb7 = (MessagePublishInfoImpl) (m7.getMessagePublishInfo());
+ pb7.setMandatory(true);
+ routeAndTest(m7,true);
+
+ Message m8 = new Message("Message8", "F0000");
+ MessagePublishInfoImpl pb8 = (MessagePublishInfoImpl)(m8.getMessagePublishInfo());
+ pb8.setMandatory(true);
+ routeAndTest(m8,false,q1);
+
+
+ }
+
+ public void testAny() 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 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 void testMandatory() throws AMQException
+ {
+ bindDefault("F0000");
+ Message m1 = new Message("Message1", "XXXXX");
+ Message m2 = new Message("Message2", "F0000");
+ MessagePublishInfoImpl pb1 = (MessagePublishInfoImpl) (m1.getMessagePublishInfo());
+ pb1.setMandatory(true);
+ MessagePublishInfoImpl pb2 = (MessagePublishInfoImpl) (m2.getMessagePublishInfo());
+ pb2.setMandatory(true);
+ routeAndTest(m1,true);
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(HeadersExchangeTest.class);
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/management/LoggingManagementMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/management/LoggingManagementMBeanTest.java
new file mode 100644
index 0000000000..40153be331
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/management/LoggingManagementMBeanTest.java
@@ -0,0 +1,413 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.logging.management;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.management.JMException;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.TabularDataSupport;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+
+import junit.framework.TestCase;
+
+public class LoggingManagementMBeanTest extends TestCase
+{
+ private static final String TEST_LOGGER = "LoggingManagementMBeanTestLogger";
+ private static final String TEST_LOGGER_CHILD1 = "LoggingManagementMBeanTestLogger.child1";
+ private static final String TEST_LOGGER_CHILD2 = "LoggingManagementMBeanTestLogger.child2";
+
+ private static final String CATEGORY_PRIORITY = "LogManMBeanTest.category.priority";
+ private static final String CATEGORY_LEVEL = "LogManMBeanTest.category.level";
+ private static final String LOGGER_LEVEL = "LogManMBeanTest.logger.level";
+
+ private static final String NAME_INDEX = LoggingManagement.COMPOSITE_ITEM_NAMES[0];
+ private static final String LEVEL_INDEX = LoggingManagement.COMPOSITE_ITEM_NAMES[1];
+
+ private static final String NEWLINE = System.getProperty("line.separator");
+
+ private File _testConfigFile;
+
+ protected void setUp() throws Exception
+ {
+ _testConfigFile = createTempTestLog4JConfig();
+ }
+
+ private File createTempTestLog4JConfig()
+ {
+ File tmpFile = null;
+ try
+ {
+ tmpFile = File.createTempFile("LogManMBeanTestLog4jConfig", ".tmp");
+ tmpFile.deleteOnExit();
+
+ FileWriter fstream = new FileWriter(tmpFile);
+ BufferedWriter writer = new BufferedWriter(fstream);
+
+ writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+NEWLINE);
+ writer.write("<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\">"+NEWLINE);
+
+ writer.write("<log4j:configuration xmlns:log4j=\"http://jakarta.apache.org/log4j/\" debug=\"null\" " +
+ "threshold=\"null\">"+NEWLINE);
+
+ writer.write(" <appender class=\"org.apache.log4j.ConsoleAppender\" name=\"STDOUT\">"+NEWLINE);
+ writer.write(" <layout class=\"org.apache.log4j.PatternLayout\">"+NEWLINE);
+ writer.write(" <param name=\"ConversionPattern\" value=\"%d %-5p [%t] %C{2} (%F:%L) - %m%n\"/>"+NEWLINE);
+ writer.write(" </layout>"+NEWLINE);
+ writer.write(" </appender>"+NEWLINE);
+
+ //Example of a 'category' with a 'priority'
+ writer.write(" <category additivity=\"true\" name=\"" + CATEGORY_PRIORITY +"\">"+NEWLINE);
+ writer.write(" <priority value=\"info\"/>"+NEWLINE);
+ writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE);
+ writer.write(" </category>"+NEWLINE);
+
+ //Example of a 'category' with a 'level'
+ writer.write(" <category additivity=\"true\" name=\"" + CATEGORY_LEVEL +"\">"+NEWLINE);
+ writer.write(" <level value=\"warn\"/>"+NEWLINE);
+ writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE);
+ writer.write(" </category>"+NEWLINE);
+
+ //Example of a 'logger' with a 'level'
+ writer.write(" <logger additivity=\"true\" name=\"" + LOGGER_LEVEL + "\">"+NEWLINE);
+ writer.write(" <level value=\"error\"/>"+NEWLINE);
+ writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE);
+ writer.write(" </logger>"+NEWLINE);
+
+ //'root' logger
+ writer.write(" <root>"+NEWLINE);
+ writer.write(" <priority value=\"info\"/>"+NEWLINE);
+ writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE);
+ writer.write(" </root>"+NEWLINE);
+
+ writer.write("</log4j:configuration>"+NEWLINE);
+
+ writer.flush();
+ writer.close();
+ }
+ catch (IOException e)
+ {
+ fail("Unable to create temporary test log4j configuration");
+ }
+
+ return tmpFile;
+ }
+
+
+
+ //******* Test Methods ******* //
+
+ public void testSetRuntimeLoggerLevel()
+ {
+ LoggingManagementMBean lm = null;
+ try
+ {
+ lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0);
+ }
+ catch (JMException e)
+ {
+ fail("Could not create test LoggingManagementMBean");
+ }
+
+ //create a parent test logger, set its level explicitly
+ Logger log = Logger.getLogger(TEST_LOGGER);
+ log.setLevel(Level.toLevel("info"));
+
+ //create child1 test logger, check its *effective* level is the same as the parent, "info"
+ Logger log1 = Logger.getLogger(TEST_LOGGER_CHILD1);
+ assertTrue("Test logger's level was not the expected value",
+ log1.getEffectiveLevel().toString().equalsIgnoreCase("info"));
+
+ //now change its level to "warn"
+ assertTrue("Failed to set logger level", lm.setRuntimeLoggerLevel(TEST_LOGGER_CHILD1, "warn"));
+
+ //check the change, see its actual level is "warn
+ assertTrue("Test logger's level was not the expected value",
+ log1.getLevel().toString().equalsIgnoreCase("warn"));
+
+ //try an invalid level
+ assertFalse("Trying to set an invalid level succeded", lm.setRuntimeLoggerLevel(TEST_LOGGER_CHILD1, "made.up.level"));
+ }
+
+ public void testSetRuntimeRootLoggerLevel()
+ {
+ LoggingManagementMBean lm = null;
+ try
+ {
+ lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0);
+ }
+ catch (JMException e)
+ {
+ fail("Could not create test LoggingManagementMBean");
+ }
+
+ Logger log = Logger.getRootLogger();
+
+ //get current root logger level
+ Level origLevel = log.getLevel();
+
+ //change level twice to ensure a new level is actually selected
+
+ //set root loggers level to info
+ assertTrue("Failed to set root logger level", lm.setRuntimeRootLoggerLevel("debug"));
+ //check it is now actually info
+ Level currentLevel = log.getLevel();
+ assertTrue("Logger level was not expected value", currentLevel.equals(Level.toLevel("debug")));
+
+ //try an invalid level
+ assertFalse("Trying to set an invalid level succeded", lm.setRuntimeRootLoggerLevel("made.up.level"));
+
+ //set root loggers level to warn
+ assertTrue("Failed to set logger level", lm.setRuntimeRootLoggerLevel("info"));
+ //check it is now actually warn
+ currentLevel = log.getLevel();
+ assertTrue("Logger level was not expected value", currentLevel.equals(Level.toLevel("info")));
+
+ //restore original level
+ log.setLevel(origLevel);
+ }
+
+ public void testGetRuntimeRootLoggerLevel()
+ {
+ LoggingManagementMBean lm = null;
+ try
+ {
+ lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0);
+ }
+ catch (JMException e)
+ {
+ fail("Could not create test LoggingManagementMBean");
+ }
+
+ Logger log = Logger.getRootLogger();
+
+ //get current root logger level
+ Level origLevel = log.getLevel();
+
+ //change level twice to ensure a new level is actually selected
+
+ //set root loggers level to debug
+ log.setLevel(Level.toLevel("debug"));
+ //check it is now actually debug
+ assertTrue("Logger level was not expected value", lm.getRuntimeRootLoggerLevel().equalsIgnoreCase("debug"));
+
+
+ //set root loggers level to warn
+ log.setLevel(Level.toLevel("info"));
+ //check it is now actually warn
+ assertTrue("Logger level was not expected value", lm.getRuntimeRootLoggerLevel().equalsIgnoreCase("info"));
+
+ //restore original level
+ log.setLevel(origLevel);
+ }
+
+ public void testViewEffectiveRuntimeLoggerLevels()
+ {
+ LoggingManagementMBean lm = null;
+ try
+ {
+ lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0);
+ }
+ catch (JMException e)
+ {
+ fail("Could not create test LoggingManagementMBean");
+ }
+
+ //(re)create a parent test logger, set its level explicitly
+ Logger log = Logger.getLogger(TEST_LOGGER);
+ log.setLevel(Level.toLevel("info"));
+
+ //retrieve the current effective runtime logger level values
+ TabularDataSupport levels = (TabularDataSupport) lm.viewEffectiveRuntimeLoggerLevels();
+ Collection<Object> records = levels.values();
+ Map<String,String> list = new HashMap<String,String>();
+ for (Object o : records)
+ {
+ CompositeData data = (CompositeData) o;
+ list.put(data.get(NAME_INDEX).toString(), data.get(LEVEL_INDEX).toString());
+ }
+
+ //check child2 does not exist already
+ assertFalse("Did not expect this logger to exist already", list.containsKey(TEST_LOGGER_CHILD2));
+
+ //create child2 test logger
+ Logger log2 = Logger.getLogger(TEST_LOGGER_CHILD2);
+
+ //retrieve the current effective runtime logger level values
+ levels = (TabularDataSupport) lm.viewEffectiveRuntimeLoggerLevels();
+ records = levels.values();
+ list = new HashMap<String,String>();
+ for (Object o : records)
+ {
+ CompositeData data = (CompositeData) o;
+ list.put(data.get(NAME_INDEX).toString(), data.get(LEVEL_INDEX).toString());
+ }
+
+ //verify the parent and child2 loggers are present in returned values
+ assertTrue(TEST_LOGGER + " logger was not in the returned list", list.containsKey(TEST_LOGGER));
+ assertTrue(TEST_LOGGER_CHILD2 + " logger was not in the returned list", list.containsKey(TEST_LOGGER_CHILD2));
+
+ //check child2's effective level is the same as the parent, "info"
+ assertTrue("Test logger's level was not the expected value",
+ list.get(TEST_LOGGER_CHILD2).equalsIgnoreCase("info"));
+
+ //now change its level explicitly to "warn"
+ log2.setLevel(Level.toLevel("warn"));
+
+ //retrieve the current effective runtime logger level values
+ levels = (TabularDataSupport) lm.viewEffectiveRuntimeLoggerLevels();
+ records = levels.values();
+ list = new HashMap<String,String>();
+ for (Object o : records)
+ {
+ CompositeData data = (CompositeData) o;
+ list.put(data.get(NAME_INDEX).toString(), data.get(LEVEL_INDEX).toString());
+ }
+
+ //check child2's effective level is now "warn"
+ assertTrue("Test logger's level was not the expected value",
+ list.get(TEST_LOGGER_CHILD2).equalsIgnoreCase("warn"));
+ }
+
+ public void testViewAndSetConfigFileLoggerLevel() throws Exception
+ {
+ LoggingManagementMBean lm =null;
+ try
+ {
+ lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0);
+ }
+ catch (JMException e)
+ {
+ fail("Could not create test LoggingManagementMBean");
+ }
+
+ //retrieve the current values
+ TabularDataSupport levels = (TabularDataSupport) lm.viewConfigFileLoggerLevels();
+ Collection<Object> records = levels.values();
+ Map<String,String> list = new HashMap<String,String>();
+ for (Object o : records)
+ {
+ CompositeData data = (CompositeData) o;
+ list.put(data.get(NAME_INDEX).toString(), data.get(LEVEL_INDEX).toString());
+ }
+
+ //check the 3 different types of logger definition are successfully retrieved before update
+ assertTrue("Wrong number of items in returned list", list.size() == 3);
+ assertTrue(CATEGORY_PRIORITY + " logger was not in the returned list", list.containsKey(CATEGORY_PRIORITY));
+ assertTrue(CATEGORY_LEVEL + " logger was not in the returned list", list.containsKey(CATEGORY_LEVEL));
+ assertTrue(LOGGER_LEVEL + " logger was not in the returned list", list.containsKey(LOGGER_LEVEL));
+
+ //check that their level is as expected
+ assertTrue(CATEGORY_PRIORITY + " logger's level was incorrect", list.get(CATEGORY_PRIORITY).equalsIgnoreCase("info"));
+ assertTrue(CATEGORY_LEVEL + " logger's level was incorrect", list.get(CATEGORY_LEVEL).equalsIgnoreCase("warn"));
+ assertTrue(LOGGER_LEVEL + " logger's level was incorrect", list.get(LOGGER_LEVEL).equalsIgnoreCase("error"));
+
+ //increase their levels a notch to test the 3 different types of logger definition are successfully updated
+ //change the category+priority to warn
+ assertTrue("failed to set new level", lm.setConfigFileLoggerLevel(CATEGORY_PRIORITY, "warn"));
+ //change the category+level to error
+ assertTrue("failed to set new level", lm.setConfigFileLoggerLevel(CATEGORY_LEVEL, "error"));
+ //change the logger+level to trace
+ assertTrue("failed to set new level", lm.setConfigFileLoggerLevel(LOGGER_LEVEL, "trace"));
+
+ //try an invalid level
+ assertFalse("Use of an invalid logger level was successfull", lm.setConfigFileLoggerLevel(LOGGER_LEVEL, "made.up.level"));
+
+ //try an invalid logger name
+ assertFalse("Use of an invalid logger name was successfull", lm.setConfigFileLoggerLevel("made.up.logger.name", "info"));
+
+ //retrieve the new values from the file and check them
+ levels = (TabularDataSupport) lm.viewConfigFileLoggerLevels();
+ records = levels.values();
+ list = new HashMap<String,String>();
+ for (Object o : records)
+ {
+ CompositeData data = (CompositeData) o;
+ list.put(data.get(NAME_INDEX).toString(), data.get(LEVEL_INDEX).toString());
+ }
+
+ //check the 3 different types of logger definition are successfully retrieved after update
+ assertTrue("Wrong number of items in returned list", list.size() == 3);
+ assertTrue(CATEGORY_PRIORITY + " logger was not in the returned list", list.containsKey(CATEGORY_PRIORITY));
+ assertTrue(CATEGORY_LEVEL + " logger was not in the returned list", list.containsKey(CATEGORY_LEVEL));
+ assertTrue(LOGGER_LEVEL + " logger was not in the returned list", list.containsKey(LOGGER_LEVEL));
+
+ //check that their level is as expected after the changes
+ assertTrue(CATEGORY_PRIORITY + " logger's level was incorrect", list.get(CATEGORY_PRIORITY).equalsIgnoreCase("warn"));
+ assertTrue(CATEGORY_LEVEL + " logger's level was incorrect", list.get(CATEGORY_LEVEL).equalsIgnoreCase("error"));
+ assertTrue(LOGGER_LEVEL + " logger's level was incorrect", list.get(LOGGER_LEVEL).equalsIgnoreCase("trace"));
+ }
+
+ public void testGetAndSetConfigFileRootLoggerLevel() throws Exception
+ {
+ LoggingManagementMBean lm =null;
+ try
+ {
+ lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0);
+ }
+ catch (JMException e)
+ {
+ fail("Could not create test LoggingManagementMBean");
+ }
+
+ //retrieve the current value
+ String level = lm.getConfigFileRootLoggerLevel();
+
+ //check the value was successfully retrieved before update
+ assertTrue("Retrieved RootLogger level was incorrect", level.equalsIgnoreCase("info"));
+
+ //try an invalid level
+ assertFalse("Use of an invalid RootLogger level was successfull", lm.setConfigFileRootLoggerLevel("made.up.level"));
+
+ //change the level to warn
+ assertTrue("Failed to set new RootLogger level", lm.setConfigFileRootLoggerLevel("warn"));
+
+ //retrieve the current value
+ level = lm.getConfigFileRootLoggerLevel();
+
+ //check the value was successfully retrieved after update
+ assertTrue("Retrieved RootLogger level was incorrect", level.equalsIgnoreCase("warn"));
+ }
+
+ public void testGetLog4jLogWatchInterval()
+ {
+ LoggingManagementMBean lm =null;
+ try
+ {
+ lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 5000);
+ }
+ catch (JMException e)
+ {
+ fail("Could not create test LoggingManagementMBean");
+ }
+
+ assertTrue("Wrong value returned for logWatch period", lm.getLog4jLogWatchInterval() == 5000);
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/MockPluginManager.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/MockPluginManager.java
new file mode 100644
index 0000000000..9599848dde
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/MockPluginManager.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.server.plugins;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.qpid.server.exchange.ExchangeType;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.ACLPluginFactory;
+import org.apache.qpid.server.security.access.QueueDenier;
+
+public class MockPluginManager extends PluginManager
+{
+
+ private Map<String, ACLPluginFactory> _securityPlugins = new HashMap<String, ACLPluginFactory>();
+
+ public MockPluginManager(String plugindir) throws Exception
+ {
+ super(plugindir);
+ _securityPlugins.put("org.apache.qpid.server.security.access.QueueDenier", QueueDenier.FACTORY);
+ }
+
+ @Override
+ public Map<String, ExchangeType<?>> getExchanges()
+ {
+ return null;
+ }
+
+ @Override
+ public Map<String, ACLPluginFactory> getSecurityPlugins()
+ {
+ return _securityPlugins;
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/PluginTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/PluginTest.java
new file mode 100644
index 0000000000..11d6105704
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/PluginTest.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.plugins;
+
+import java.util.Map;
+
+import org.apache.qpid.server.exchange.ExchangeType;
+
+import junit.framework.TestCase;
+
+public class PluginTest extends TestCase
+{
+
+ private static final String TEST_EXCHANGE_CLASS = "org.apache.qpid.extras.exchanges.example.TestExchangeType";
+ private static final String PLUGIN_DIRECTORY = System.getProperty("example.plugin.target");
+
+ public void testLoadExchanges() throws Exception
+ {
+ PluginManager manager = new PluginManager(PLUGIN_DIRECTORY);
+ Map<String, ExchangeType<?>> exchanges = manager.getExchanges();
+ assertNotNull("No exchanges found in "+PLUGIN_DIRECTORY, exchanges);
+ assertEquals("Wrong number of exchanges found in "+PLUGIN_DIRECTORY,
+ 2, exchanges.size());
+ assertNotNull("Wrong exchange found in "+PLUGIN_DIRECTORY,
+ exchanges.get(TEST_EXCHANGE_CLASS));
+ }
+
+ public void testNoExchanges() throws Exception
+ {
+ PluginManager manager = new PluginManager("/path/to/nowhere");
+ Map<String, ExchangeType<?>> exchanges = manager.getExchanges();
+ assertEquals("Exchanges found", 0, exchanges.size());
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java
new file mode 100644
index 0000000000..d5db87350b
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java
@@ -0,0 +1,124 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol;
+
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.queue.AMQQueueFactory;
+import org.apache.qpid.server.queue.AMQQueue;
+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 javax.management.JMException;
+
+/**
+ * Test class to test MBean operations for AMQMinaProtocolSession.
+ */
+public class AMQProtocolSessionMBeanTest extends TestCase
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(AMQProtocolSessionMBeanTest.class);
+
+ private MessageStore _messageStore = new SkeletonMessageStore();
+ private AMQMinaProtocolSession _protocolSession;
+ private AMQChannel _channel;
+ private AMQProtocolSessionMBean _mbean;
+
+ public void testChannels() throws Exception
+ {
+ // check the channel count is correct
+ int channelCount = _mbean.channels().size();
+ assertTrue(channelCount == 1);
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue_" + System.currentTimeMillis()),
+ false,
+ new AMQShortString("test"),
+ true,
+ _protocolSession.getVirtualHost(), null);
+ AMQChannel channel = new AMQChannel(_protocolSession,2, _messageStore);
+ channel.setDefaultQueue(queue);
+ _protocolSession.addChannel(channel);
+ channelCount = _mbean.channels().size();
+ assertTrue(channelCount == 2);
+
+ // general properties test
+ _mbean.setMaximumNumberOfChannels(1000L);
+ assertTrue(_mbean.getMaximumNumberOfChannels() == 1000L);
+
+ // check APIs
+ AMQChannel channel3 = new AMQChannel(_protocolSession, 3, _messageStore);
+ channel3.setLocalTransactional();
+ _protocolSession.addChannel(channel3);
+ _mbean.rollbackTransactions(2);
+ _mbean.rollbackTransactions(3);
+ _mbean.commitTransactions(2);
+ _mbean.commitTransactions(3);
+
+ // This should throw exception, because the channel does't exist
+ try
+ {
+ _mbean.commitTransactions(4);
+ fail();
+ }
+ catch (JMException ex)
+ {
+ log.debug("expected exception is thrown :" + ex.getMessage());
+ }
+
+ // check if closing of session works
+ _protocolSession.addChannel(new AMQChannel(_protocolSession, 5, _messageStore));
+ _mbean.closeConnection();
+ try
+ {
+ channelCount = _mbean.channels().size();
+ assertTrue(channelCount == 0);
+ // session is now closed so adding another channel should throw an exception
+ _protocolSession.addChannel(new AMQChannel(_protocolSession, 6, _messageStore));
+ fail();
+ }
+ catch (AMQException ex)
+ {
+ log.debug("expected exception is thrown :" + ex.getMessage());
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ IApplicationRegistry appRegistry = ApplicationRegistry.getInstance();
+ _protocolSession =
+ new AMQMinaProtocolSession(new TestIoSession(), appRegistry.getVirtualHostRegistry(), new AMQCodecFactory(true),
+ null);
+ _protocolSession.setVirtualHost(appRegistry.getVirtualHostRegistry().getVirtualHost("test"));
+ _channel = new AMQChannel(_protocolSession, 1, _messageStore);
+ _protocolSession.addChannel(_channel);
+ _mbean = (AMQProtocolSessionMBean) _protocolSession.getManagedObject();
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java
new file mode 100644
index 0000000000..da35ddc594
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java
@@ -0,0 +1,180 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.AMQChannel;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class InternalTestProtocolSession extends AMQMinaProtocolSession implements ProtocolOutputConverter
+{
+ // ChannelID(LIST) -> LinkedList<Pair>
+ final Map<Integer, Map<AMQShortString, LinkedList<DeliveryPair>>> _channelDelivers;
+ private AtomicInteger _deliveryCount = new AtomicInteger(0);
+
+ public InternalTestProtocolSession() throws AMQException
+ {
+ super(new TestIoSession(),
+ ApplicationRegistry.getInstance().getVirtualHostRegistry(),
+ new AMQCodecFactory(true));
+
+ _channelDelivers = new HashMap<Integer, Map<AMQShortString, LinkedList<DeliveryPair>>>();
+
+ }
+
+ public ProtocolOutputConverter getProtocolOutputConverter()
+ {
+ return this;
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return (byte) 8;
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return (byte) 0;
+ }
+
+ // ***
+
+ public List<DeliveryPair> getDelivers(int channelId, AMQShortString consumerTag, int count)
+ {
+ synchronized (_channelDelivers)
+ {
+ List<DeliveryPair> all =_channelDelivers.get(channelId).get(consumerTag);
+
+ if (all == null)
+ {
+ return new ArrayList<DeliveryPair>(0);
+ }
+
+ List<DeliveryPair> msgs = all.subList(0, count);
+
+ List<DeliveryPair> response = new ArrayList<DeliveryPair>(msgs);
+
+ //Remove the msgs from the receivedList.
+ msgs.clear();
+
+ return response;
+ }
+ }
+
+ // *** ProtocolOutputConverter Implementation
+ public void writeReturn(AMQMessage message, int channelId, int replyCode, AMQShortString replyText) throws AMQException
+ {
+ }
+
+ public void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag)
+ {
+ }
+
+ public void writeDeliver(AMQMessage message, int channelId, long deliveryTag, AMQShortString consumerTag) throws AMQException
+ {
+ _deliveryCount.incrementAndGet();
+
+ synchronized (_channelDelivers)
+ {
+ Map<AMQShortString, LinkedList<DeliveryPair>> consumers = _channelDelivers.get(channelId);
+
+ if (consumers == null)
+ {
+ consumers = new HashMap<AMQShortString, LinkedList<DeliveryPair>>();
+ _channelDelivers.put(channelId, consumers);
+ }
+
+ LinkedList<DeliveryPair> consumerDelivers = consumers.get(consumerTag);
+
+ if (consumerDelivers == null)
+ {
+ consumerDelivers = new LinkedList<DeliveryPair>();
+ consumers.put(consumerTag, consumerDelivers);
+ }
+
+ consumerDelivers.add(new DeliveryPair(deliveryTag, message));
+ }
+ }
+
+ public void writeGetOk(AMQMessage message, int channelId, long deliveryTag, int queueSize) throws AMQException
+ {
+ }
+
+ public void awaitDelivery(int msgs)
+ {
+ while (msgs > _deliveryCount.get())
+ {
+ try
+ {
+ Thread.sleep(100);
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public class DeliveryPair
+ {
+ private long _deliveryTag;
+ private AMQMessage _message;
+
+ public DeliveryPair(long deliveryTag, AMQMessage message)
+ {
+ _deliveryTag = deliveryTag;
+ _message = message;
+ }
+
+ public AMQMessage getMessage()
+ {
+ return _message;
+ }
+
+ public long getDeliveryTag()
+ {
+ return _deliveryTag;
+ }
+ }
+
+ public boolean isClosed()
+ {
+ return _closed;
+ }
+
+ public void closeProtocolSession(boolean waitLast)
+ {
+ // Override as we don't have a real IOSession to close.
+ // The alternative is to fully implement the TestIOSession to return a CloseFuture from close();
+ // Then the AMQMinaProtocolSession can join on the returning future without a NPE.
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/MaxChannelsTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/MaxChannelsTest.java
new file mode 100644
index 0000000000..1bdabf345b
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/MaxChannelsTest.java
@@ -0,0 +1,86 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol;
+
+import junit.framework.TestCase;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+
+/** Test class to test MBean operations for AMQMinaProtocolSession. */
+public class MaxChannelsTest extends TestCase
+{
+ private IApplicationRegistry _appRegistry;
+ private AMQMinaProtocolSession _session;
+
+ public void testChannels() throws Exception
+ {
+ _session = new AMQMinaProtocolSession(new TestIoSession(), _appRegistry
+ .getVirtualHostRegistry(), new AMQCodecFactory(true), null);
+ _session.setVirtualHost(_appRegistry.getVirtualHostRegistry().getVirtualHost("test"));
+
+ // check the channel count is correct
+ int channelCount = _session.getChannels().size();
+ assertEquals("Initial channel count wrong", 0, channelCount);
+
+ long maxChannels = 10L;
+ _session.setMaximumNumberOfChannels(maxChannels);
+ assertEquals("Number of channels not correctly set.", new Long(maxChannels), _session.getMaximumNumberOfChannels());
+
+
+ try
+ {
+ for (long currentChannel = 0L; currentChannel < maxChannels; currentChannel++)
+ {
+ _session.addChannel(new AMQChannel(_session, (int) currentChannel, null));
+ }
+ }
+ catch (AMQException e)
+ {
+ assertEquals("Wrong exception recevied.", e.getErrorCode(), AMQConstant.NOT_ALLOWED);
+ }
+ assertEquals("Maximum number of channels not set.", new Long(maxChannels), new Long(_session.getChannels().size()));
+ }
+
+ @Override
+ public void setUp()
+ {
+ _appRegistry = ApplicationRegistry.getInstance(1);
+ }
+
+ @Override
+ public void tearDown()
+ {
+ try {
+ _session.closeSession();
+ } catch (AMQException e) {
+ // Yikes
+ fail(e.getMessage());
+ }
+ ApplicationRegistry.remove(1);
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/TestIoSession.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/TestIoSession.java
new file mode 100644
index 0000000000..211f491867
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/TestIoSession.java
@@ -0,0 +1,328 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.mina.common.CloseFuture;
+import org.apache.mina.common.IdleStatus;
+import org.apache.mina.common.IoFilterChain;
+import org.apache.mina.common.IoHandler;
+import org.apache.mina.common.IoService;
+import org.apache.mina.common.IoServiceConfig;
+import org.apache.mina.common.IoSession;
+import org.apache.mina.common.IoSessionConfig;
+import org.apache.mina.common.ThreadModel;
+import org.apache.mina.common.TrafficMask;
+import org.apache.mina.common.TransportType;
+import org.apache.mina.common.WriteFuture;
+import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
+import org.apache.qpid.pool.ReadWriteThreadModel;
+
+/**
+ * Test implementation of IoSession, which is required for some tests. Methods not being used are not implemented,
+ * so if this class is being used and some methods are to be used, then please update those.
+ */
+public class TestIoSession implements IoSession
+{
+ private final ConcurrentMap attributes = new ConcurrentHashMap();
+ private String _address = "127.0.0.1";
+ private int _port = 1;
+
+ public TestIoSession()
+ {
+ }
+
+ public IoService getService()
+ {
+ return null;
+ }
+
+ public IoServiceConfig getServiceConfig()
+ {
+ return new TestIoConfig();
+ }
+
+ public IoHandler getHandler()
+ {
+ return null;
+ }
+
+ public IoSessionConfig getConfig()
+ {
+ return null;
+ }
+
+ public IoFilterChain getFilterChain()
+ {
+ return null;
+ }
+
+ public WriteFuture write(Object message)
+ {
+ return null;
+ }
+
+ public CloseFuture close()
+ {
+ return null;
+ }
+
+ public Object getAttachment()
+ {
+ return getAttribute("");
+ }
+
+ public Object setAttachment(Object attachment)
+ {
+ return setAttribute("",attachment);
+ }
+
+ public Object getAttribute(String key)
+ {
+ return attributes.get(key);
+ }
+
+ public Object setAttribute(String key, Object value)
+ {
+ return attributes.put(key,value);
+ }
+
+ public Object setAttribute(String key)
+ {
+ return attributes.put(key, Boolean.TRUE);
+ }
+
+ public Object removeAttribute(String key)
+ {
+ return attributes.remove(key);
+ }
+
+ public boolean containsAttribute(String key)
+ {
+ return attributes.containsKey(key);
+ }
+
+ public Set getAttributeKeys()
+ {
+ return attributes.keySet();
+ }
+
+ public TransportType getTransportType()
+ {
+ return null;
+ }
+
+ public boolean isConnected()
+ {
+ return false;
+ }
+
+ public boolean isClosing()
+ {
+ return false;
+ }
+
+ public CloseFuture getCloseFuture()
+ {
+ return null;
+ }
+
+ public SocketAddress getRemoteAddress()
+ {
+ return new InetSocketAddress(getAddress(), getPort());
+ }
+
+ public SocketAddress getLocalAddress()
+ {
+ return null;
+ }
+
+ public SocketAddress getServiceAddress()
+ {
+ return null;
+ }
+
+ public int getIdleTime(IdleStatus status)
+ {
+ return 0;
+ }
+
+ public long getIdleTimeInMillis(IdleStatus status)
+ {
+ return 0;
+ }
+
+ public void setIdleTime(IdleStatus status, int idleTime)
+ {
+
+ }
+
+ public int getWriteTimeout()
+ {
+ return 0;
+ }
+
+ public long getWriteTimeoutInMillis()
+ {
+ return 0;
+ }
+
+ public void setWriteTimeout(int writeTimeout)
+ {
+
+ }
+
+ public TrafficMask getTrafficMask()
+ {
+ return null;
+ }
+
+ public void setTrafficMask(TrafficMask trafficMask)
+ {
+
+ }
+
+ public void suspendRead()
+ {
+
+ }
+
+ public void suspendWrite()
+ {
+
+ }
+
+ public void resumeRead()
+ {
+
+ }
+
+ public void resumeWrite()
+ {
+
+ }
+
+ public long getReadBytes()
+ {
+ return 0;
+ }
+
+ public long getWrittenBytes()
+ {
+ return 0;
+ }
+
+ public long getReadMessages()
+ {
+ return 0;
+ }
+
+ public long getWrittenMessages()
+ {
+ return 0;
+ }
+
+ public long getWrittenWriteRequests()
+ {
+ return 0;
+ }
+
+ public int getScheduledWriteRequests()
+ {
+ return 0;
+ }
+
+ public int getScheduledWriteBytes()
+ {
+ return 0;
+ }
+
+ public long getCreationTime()
+ {
+ return 0;
+ }
+
+ public long getLastIoTime()
+ {
+ return 0;
+ }
+
+ public long getLastReadTime()
+ {
+ return 0;
+ }
+
+ public long getLastWriteTime()
+ {
+ return 0;
+ }
+
+ public boolean isIdle(IdleStatus status)
+ {
+ return false;
+ }
+
+ public int getIdleCount(IdleStatus status)
+ {
+ return 0;
+ }
+
+ public long getLastIdleTime(IdleStatus status)
+ {
+ return 0;
+ }
+
+ public void setAddress(String string)
+ {
+ this._address = string;
+ }
+
+ public String getAddress()
+ {
+ return _address;
+ }
+
+ public void setPort(int _port)
+ {
+ this._port = _port;
+ }
+
+ public int getPort()
+ {
+ return _port;
+ }
+
+ /**
+ * Test implementation of IoServiceConfig
+ */
+ private class TestIoConfig extends SocketAcceptorConfig
+ {
+ public ThreadModel getThreadModel()
+ {
+ return ReadWriteThreadModel.getInstance();
+ }
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java
new file mode 100644
index 0000000000..aff7af6952
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java
@@ -0,0 +1,107 @@
+package org.apache.qpid.server.queue;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+import java.util.ArrayList;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.FieldTable;
+import junit.framework.AssertionFailedError;
+
+public class AMQPriorityQueueTest extends SimpleAMQQueueTest
+{
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ _arguments = new FieldTable();
+ _arguments.put(AMQQueueFactory.X_QPID_PRIORITIES, 3);
+ super.setUp();
+ }
+
+ public void testPriorityOrdering() throws AMQException, InterruptedException
+ {
+
+ // Enqueue messages in order
+ _queue.enqueue(null, createMessage(1L, (byte) 10));
+ _queue.enqueue(null, createMessage(2L, (byte) 4));
+ _queue.enqueue(null, createMessage(3L, (byte) 0));
+
+ // Enqueue messages in reverse order
+ _queue.enqueue(null, createMessage(4L, (byte) 0));
+ _queue.enqueue(null, createMessage(5L, (byte) 4));
+ _queue.enqueue(null, createMessage(6L, (byte) 10));
+
+ // Enqueue messages out of order
+ _queue.enqueue(null, createMessage(7L, (byte) 4));
+ _queue.enqueue(null, createMessage(8L, (byte) 10));
+ _queue.enqueue(null, createMessage(9L, (byte) 0));
+
+ // Register subscriber
+ _queue.registerSubscription(_subscription, false);
+ Thread.sleep(150);
+
+ ArrayList<QueueEntry> msgs = _subscription.getMessages();
+ try
+ {
+ assertEquals(new Long(1L), msgs.get(0).getMessage().getMessageId());
+ assertEquals(new Long(6L), msgs.get(1).getMessage().getMessageId());
+ assertEquals(new Long(8L), msgs.get(2).getMessage().getMessageId());
+
+ assertEquals(new Long(2L), msgs.get(3).getMessage().getMessageId());
+ assertEquals(new Long(5L), msgs.get(4).getMessage().getMessageId());
+ assertEquals(new Long(7L), msgs.get(5).getMessage().getMessageId());
+
+ assertEquals(new Long(3L), msgs.get(6).getMessage().getMessageId());
+ assertEquals(new Long(4L), msgs.get(7).getMessage().getMessageId());
+ assertEquals(new Long(9L), msgs.get(8).getMessage().getMessageId());
+ }
+ catch (AssertionFailedError afe)
+ {
+ // Show message order on failure.
+ int index = 1;
+ for (QueueEntry qe : msgs)
+ {
+ System.err.println(index + ":" + qe.getMessage().getMessageId());
+ index++;
+ }
+
+ throw afe;
+ }
+
+ }
+
+ protected AMQMessage createMessage(Long id, byte i) throws AMQException
+ {
+ AMQMessage msg = super.createMessage(id);
+ BasicContentHeaderProperties props = new BasicContentHeaderProperties();
+ props.setPriority(i);
+ msg.getContentHeaderBody().properties = props;
+ return msg;
+ }
+
+ protected AMQMessage createMessage(Long id) throws AMQException
+ {
+ return createMessage(id, (byte) 0);
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java
new file mode 100644
index 0000000000..6589f46c7b
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java
@@ -0,0 +1,354 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import junit.framework.TestCase;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.MemoryMessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.subscription.SubscriptionFactoryImpl;
+import org.apache.qpid.server.protocol.AMQMinaProtocolSession;
+import org.apache.qpid.server.protocol.InternalTestProtocolSession;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.mina.common.ByteBuffer;
+
+import javax.management.Notification;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Collections;
+import java.util.Set;
+
+/** This class tests all the alerts an AMQQueue can throw based on threshold values of different parameters */
+public class AMQQueueAlertTest extends TestCase
+{
+ private final static long MAX_MESSAGE_COUNT = 50;
+ private final static long MAX_MESSAGE_AGE = 250; // 0.25 sec
+ private final static long MAX_MESSAGE_SIZE = 2000; // 2 KB
+ private final static long MAX_QUEUE_DEPTH = 10000; // 10 KB
+ private AMQQueue _queue;
+ private AMQQueueMBean _queueMBean;
+ private VirtualHost _virtualHost;
+ private AMQMinaProtocolSession _protocolSession;
+ private MessageStore _messageStore = new MemoryMessageStore();
+ private StoreContext _storeContext = new StoreContext();
+ private TransactionalContext _transactionalContext = new NonTransactionalContext(_messageStore, _storeContext,
+ null,
+ new LinkedList<RequiredDeliveryException>()
+ );
+ private static final SubscriptionFactoryImpl SUBSCRIPTION_FACTORY = SubscriptionFactoryImpl.INSTANCE;
+
+ /**
+ * Tests if the alert gets thrown when message count increases the threshold limit
+ *
+ * @throws Exception
+ */
+ public void testMessageCountAlert() throws Exception
+ {
+ _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue1"), false, new AMQShortString("AMQueueAlertTest"),
+ false, _virtualHost,
+ null);
+ _queueMBean = (AMQQueueMBean) _queue.getManagedObject();
+
+ _queueMBean.setMaximumMessageCount(MAX_MESSAGE_COUNT);
+
+ sendMessages(MAX_MESSAGE_COUNT, 256l);
+ assertTrue(_queueMBean.getMessageCount() == MAX_MESSAGE_COUNT);
+
+ Notification lastNotification = _queueMBean.getLastNotification();
+ assertNotNull(lastNotification);
+
+ String notificationMsg = lastNotification.getMessage();
+ assertTrue(notificationMsg.startsWith(NotificationCheck.MESSAGE_COUNT_ALERT.name()));
+ }
+
+ /**
+ * Tests if the Message Size alert gets thrown when message of higher than threshold limit is sent
+ *
+ * @throws Exception
+ */
+ public void testMessageSizeAlert() throws Exception
+ {
+ _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue2"), false, new AMQShortString("AMQueueAlertTest"),
+ false, _virtualHost,
+ null);
+ _queueMBean = (AMQQueueMBean) _queue.getManagedObject();
+ _queueMBean.setMaximumMessageCount(MAX_MESSAGE_COUNT);
+ _queueMBean.setMaximumMessageSize(MAX_MESSAGE_SIZE);
+
+ sendMessages(1, MAX_MESSAGE_SIZE * 2);
+ assertTrue(_queueMBean.getMessageCount() == 1);
+
+ Notification lastNotification = _queueMBean.getLastNotification();
+ assertNotNull(lastNotification);
+
+ String notificationMsg = lastNotification.getMessage();
+ assertTrue(notificationMsg.startsWith(NotificationCheck.MESSAGE_SIZE_ALERT.name()));
+ }
+
+ /**
+ * Tests if Queue Depth alert is thrown when queue depth reaches the threshold value
+ *
+ * Based on FT-402 subbmitted by client
+ *
+ * @throws Exception
+ */
+ public void testQueueDepthAlertNoSubscriber() throws Exception
+ {
+ _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue3"), false, new AMQShortString("AMQueueAlertTest"),
+ false, _virtualHost,
+ null);
+ _queueMBean = (AMQQueueMBean) _queue.getManagedObject();
+ _queueMBean.setMaximumMessageCount(MAX_MESSAGE_COUNT);
+ _queueMBean.setMaximumQueueDepth(MAX_QUEUE_DEPTH);
+
+ while (_queue.getQueueDepth() < MAX_QUEUE_DEPTH)
+ {
+ sendMessages(1, MAX_MESSAGE_SIZE);
+ }
+
+ Notification lastNotification = _queueMBean.getLastNotification();
+ assertNotNull(lastNotification);
+
+ String notificationMsg = lastNotification.getMessage();
+ assertTrue(notificationMsg.startsWith(NotificationCheck.QUEUE_DEPTH_ALERT.name()));
+ }
+
+ /**
+ * Tests if MESSAGE AGE alert is thrown, when a message is in the queue for time higher than threshold value of
+ * message age
+ *
+ * Alternative test to FT-401 provided by client
+ *
+ * @throws Exception
+ */
+ public void testMessageAgeAlert() throws Exception
+ {
+ _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue4"), false, new AMQShortString("AMQueueAlertTest"),
+ false, _virtualHost,
+ null);
+ _queueMBean = (AMQQueueMBean) _queue.getManagedObject();
+ _queueMBean.setMaximumMessageCount(MAX_MESSAGE_COUNT);
+ _queueMBean.setMaximumMessageAge(MAX_MESSAGE_AGE);
+
+ sendMessages(1, MAX_MESSAGE_SIZE);
+
+ // Ensure message sits on queue long enough to age.
+ Thread.sleep(MAX_MESSAGE_AGE * 2);
+
+ Notification lastNotification = _queueMBean.getLastNotification();
+ assertNotNull(lastNotification);
+
+ String notificationMsg = lastNotification.getMessage();
+ assertTrue(notificationMsg.startsWith(NotificationCheck.MESSAGE_AGE_ALERT.name()));
+ }
+
+ /*
+ This test sends some messages to the queue with subscribers needing message to be acknowledged.
+ The messages will not be acknowledged and will be required twice. Why we are checking this is because
+ the bug reported said that the queueDepth keeps increasing when messages are requeued.
+ // TODO - queue depth now includes unacknowledged messages so does not go down when messages are delivered
+
+ The QueueDepth should decrease when messages are delivered from the queue (QPID-408)
+ */
+ public void testQueueDepthAlertWithSubscribers() throws Exception
+ {
+ _protocolSession = new InternalTestProtocolSession();
+ AMQChannel channel = new AMQChannel(_protocolSession, 2, _messageStore);
+ _protocolSession.addChannel(channel);
+
+ // Create queue
+ _queue = getNewQueue();
+ Subscription subscription =
+ SUBSCRIPTION_FACTORY.createSubscription(channel.getChannelId(), _protocolSession, new AMQShortString("consumer_tag"), true, null, false, channel.getCreditManager());
+
+ _queue.registerSubscription(
+ subscription, false);
+
+ _queueMBean = (AMQQueueMBean) _queue.getManagedObject();
+ _queueMBean.setMaximumMessageCount(9999l); // Set a high value, because this is not being tested
+ _queueMBean.setMaximumQueueDepth(MAX_QUEUE_DEPTH);
+
+ // Send messages(no of message to be little more than what can cause a Queue_Depth alert)
+ int messageCount = Math.round(MAX_QUEUE_DEPTH / MAX_MESSAGE_SIZE) + 10;
+ long totalSize = (messageCount * MAX_MESSAGE_SIZE);
+ sendMessages(messageCount, MAX_MESSAGE_SIZE);
+
+ // Check queueDepth. There should be no messages on the queue and as the subscriber is listening
+ // so there should be no Queue_Deoth alert raised
+ assertEquals(new Long(totalSize), new Long(_queueMBean.getQueueDepth()));
+ Notification lastNotification = _queueMBean.getLastNotification();
+// assertNull(lastNotification);
+
+ // Kill the subscriber and check for the queue depth values.
+ // Messages are unacknowledged, so those should get requeued. All messages should be on the Queue
+ _queue.unregisterSubscription(subscription);
+ channel.requeue();
+
+ assertEquals(new Long(totalSize), new Long(_queueMBean.getQueueDepth()));
+
+ lastNotification = _queueMBean.getLastNotification();
+ assertNotNull(lastNotification);
+ String notificationMsg = lastNotification.getMessage();
+ assertTrue(notificationMsg.startsWith(NotificationCheck.QUEUE_DEPTH_ALERT.name()));
+
+ // Connect a consumer again and check QueueDepth values. The queue should get emptied.
+ // Messages will get delivered but still are unacknowledged.
+ Subscription subscription2 =
+ SUBSCRIPTION_FACTORY.createSubscription(channel.getChannelId(), _protocolSession, new AMQShortString("consumer_tag"), true, null, false, channel.getCreditManager());
+
+ _queue.registerSubscription(
+ subscription2, false);
+
+ while (_queue.getUndeliveredMessageCount()!= 0)
+ {
+ Thread.sleep(100);
+ }
+// assertEquals(new Long(0), new Long(_queueMBean.getQueueDepth()));
+
+ // Kill the subscriber again. Now those messages should get requeued again. Check if the queue depth
+ // value is correct.
+ _queue.unregisterSubscription(subscription2);
+ channel.requeue();
+
+ assertEquals(new Long(totalSize), new Long(_queueMBean.getQueueDepth()));
+ _protocolSession.closeSession();
+
+ // Check the clear queue
+ _queueMBean.clearQueue();
+ assertEquals(new Long(0), new Long(_queueMBean.getQueueDepth()));
+ }
+
+ protected IncomingMessage message(final boolean immediate, long size) throws AMQException
+ {
+ MessagePublishInfo publish = new MessagePublishInfo()
+ {
+
+ public AMQShortString getExchange()
+ {
+ return null;
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isImmediate()
+ {
+ return immediate;
+ }
+
+ public boolean isMandatory()
+ {
+ return false;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return null;
+ }
+ };
+
+ ContentHeaderBody contentHeaderBody = new ContentHeaderBody();
+ contentHeaderBody.bodySize = size; // in bytes
+ IncomingMessage message = new IncomingMessage(_messageStore.getNewMessageId(), publish, _transactionalContext, _protocolSession);
+ message.setContentHeaderBody(contentHeaderBody);
+
+ return message;
+ }
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ IApplicationRegistry applicationRegistry = ApplicationRegistry.getInstance(1);
+ _virtualHost = applicationRegistry.getVirtualHostRegistry().getVirtualHost("test");
+ _protocolSession = new InternalTestProtocolSession();
+
+ }
+
+ protected void tearDown()
+ {
+ ApplicationRegistry.remove(1);
+ }
+
+
+ private void sendMessages(long messageCount, final long size) throws AMQException
+ {
+ IncomingMessage[] messages = new IncomingMessage[(int) messageCount];
+ for (int i = 0; i < messages.length; i++)
+ {
+ messages[i] = message(false, size);
+ ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>();
+ qs.add(_queue);
+ messages[i].enqueue(qs);
+ messages[i].routingComplete(_messageStore, new MessageHandleFactory());
+
+ }
+
+ for (int i = 0; i < messageCount; i++)
+ {
+ messages[i].addContentBodyFrame(new ContentChunk(){
+
+ ByteBuffer _data = ByteBuffer.allocate((int)size);
+
+ public int getSize()
+ {
+ return (int) size;
+ }
+
+ public ByteBuffer getData()
+ {
+ return _data;
+ }
+
+ public void reduceToFit()
+ {
+
+ }
+ });
+ messages[i].deliverToQueues();
+ }
+ }
+
+ private AMQQueue getNewQueue() throws AMQException
+ {
+ return AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue" + Math.random()),
+ false,
+ new AMQShortString("AMQueueAlertTest"),
+ false,
+ _virtualHost, null);
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java
new file mode 100644
index 0000000000..520e49c56a
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java
@@ -0,0 +1,85 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import junit.framework.TestCase;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.AMQException;
+
+public class AMQQueueFactoryTest extends TestCase
+{
+ QueueRegistry _queueRegistry;
+ VirtualHost _virtualHost;
+
+ public void setUp()
+ {
+ ApplicationRegistry registry = (ApplicationRegistry) ApplicationRegistry.getInstance();
+
+ _virtualHost = registry.getVirtualHostRegistry().getVirtualHost("test");
+
+ _queueRegistry = _virtualHost.getQueueRegistry();
+
+ assertEquals("Queues registered on an empty virtualhost", 0, _queueRegistry.getQueues().size());
+ }
+
+ public void tearDown()
+ {
+ assertEquals("Queue was not registered in virtualhost", 1, _queueRegistry.getQueues().size());
+ ApplicationRegistry.remove(1);
+ }
+
+
+ public void testPriorityQueueRegistration()
+ {
+ FieldTable fieldTable = new FieldTable();
+ fieldTable.put(new AMQShortString(AMQQueueFactory.X_QPID_PRIORITIES), 5);
+
+ try
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testPriorityQueue"), false, new AMQShortString("owner"), false,
+ _virtualHost, fieldTable);
+
+ assertEquals("Queue not a priorty queue", AMQPriorityQueue.class, queue.getClass());
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+ }
+
+
+ public void testSimpleQueueRegistration()
+ {
+ try
+ {
+ AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue"), false, new AMQShortString("owner"), false,
+ _virtualHost, null);
+ assertEquals("Queue not a simple queue", SimpleAMQQueue.class, queue.getClass());
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java
new file mode 100644
index 0000000000..ce986cf55b
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java
@@ -0,0 +1,349 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import junit.framework.TestCase;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.subscription.SubscriptionFactory;
+import org.apache.qpid.server.subscription.SubscriptionFactoryImpl;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.InternalTestProtocolSession;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.store.MemoryMessageStore;
+import org.apache.qpid.server.store.TestableMemoryMessageStore;
+import org.apache.mina.common.ByteBuffer;
+
+import javax.management.JMException;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Collections;
+
+/**
+ * Test class to test AMQQueueMBean attribtues and operations
+ */
+public class AMQQueueMBeanTest extends TestCase
+{
+ private static long MESSAGE_SIZE = 1000;
+ private AMQQueue _queue;
+ private AMQQueueMBean _queueMBean;
+ private MessageStore _messageStore;
+ private StoreContext _storeContext = new StoreContext();
+ private TransactionalContext _transactionalContext;
+ private VirtualHost _virtualHost;
+ private AMQProtocolSession _protocolSession;
+ private static final SubscriptionFactoryImpl SUBSCRIPTION_FACTORY = SubscriptionFactoryImpl.INSTANCE;
+
+ public void testMessageCountTransient() throws Exception
+ {
+ int messageCount = 10;
+ sendMessages(messageCount, false);
+ assertTrue(_queueMBean.getMessageCount() == messageCount);
+ assertTrue(_queueMBean.getReceivedMessageCount() == messageCount);
+ long queueDepth = (messageCount * MESSAGE_SIZE);
+ assertTrue(_queueMBean.getQueueDepth() == queueDepth);
+
+ _queueMBean.deleteMessageFromTop();
+ assertTrue(_queueMBean.getMessageCount() == (messageCount - 1));
+ assertTrue(_queueMBean.getReceivedMessageCount() == messageCount);
+
+ _queueMBean.clearQueue();
+ assertEquals(0,(int)_queueMBean.getMessageCount());
+ assertTrue(_queueMBean.getReceivedMessageCount() == messageCount);
+
+ //Ensure that the data has been removed from the Store
+ verifyBrokerState();
+ }
+
+ public void testMessageCountPersistent() throws Exception
+ {
+ int messageCount = 10;
+ sendMessages(messageCount, true);
+ assertEquals("", messageCount, _queueMBean.getMessageCount().intValue());
+ assertTrue(_queueMBean.getReceivedMessageCount() == messageCount);
+ long queueDepth = (messageCount * MESSAGE_SIZE);
+ assertTrue(_queueMBean.getQueueDepth() == queueDepth);
+
+ _queueMBean.deleteMessageFromTop();
+ assertTrue(_queueMBean.getMessageCount() == (messageCount - 1));
+ assertTrue(_queueMBean.getReceivedMessageCount() == messageCount);
+
+ _queueMBean.clearQueue();
+ assertTrue(_queueMBean.getMessageCount() == 0);
+ assertTrue(_queueMBean.getReceivedMessageCount() == messageCount);
+
+ //Ensure that the data has been removed from the Store
+ verifyBrokerState();
+ }
+
+ // todo: collect to a general testing class -duplicated from Systest/MessageReturntest
+ private void verifyBrokerState()
+ {
+
+ TestableMemoryMessageStore store = new TestableMemoryMessageStore((MemoryMessageStore) _virtualHost.getMessageStore());
+
+ // Unlike MessageReturnTest there is no need for a delay as there this thread does the clean up.
+ assertNotNull("ContentBodyMap should not be null", store.getContentBodyMap());
+ assertEquals("Expected the store to have no content:" + store.getContentBodyMap(), 0, store.getContentBodyMap().size());
+ assertNotNull("MessageMetaDataMap should not be null", store.getMessageMetaDataMap());
+ assertEquals("Expected the store to have no metadata:" + store.getMessageMetaDataMap(), 0, store.getMessageMetaDataMap().size());
+ }
+
+ public void testConsumerCount() throws AMQException
+ {
+
+ assertTrue(_queue.getActiveConsumerCount() == 0);
+ assertTrue(_queueMBean.getActiveConsumerCount() == 0);
+
+
+ InternalTestProtocolSession protocolSession = new InternalTestProtocolSession();
+ AMQChannel channel = new AMQChannel(protocolSession, 1, _messageStore);
+ protocolSession.addChannel(channel);
+
+ Subscription subscription =
+ SUBSCRIPTION_FACTORY.createSubscription(channel.getChannelId(), protocolSession, new AMQShortString("test"), false, null, false, channel.getCreditManager());
+
+ _queue.registerSubscription(subscription, false);
+ assertEquals(1,(int)_queueMBean.getActiveConsumerCount());
+
+
+ SubscriptionFactory subscriptionFactory = SUBSCRIPTION_FACTORY;
+ Subscription s1 = subscriptionFactory.createSubscription(channel.getChannelId(),
+ protocolSession,
+ new AMQShortString("S1"),
+ false,
+ null,
+ true,
+ channel.getCreditManager());
+
+ Subscription s2 = subscriptionFactory.createSubscription(channel.getChannelId(),
+ protocolSession,
+ new AMQShortString("S2"),
+ false,
+ null,
+ true,
+ channel.getCreditManager());
+ _queue.registerSubscription(s1,false);
+ _queue.registerSubscription(s2,false);
+ assertTrue(_queueMBean.getActiveConsumerCount() == 3);
+ assertTrue(_queueMBean.getConsumerCount() == 3);
+
+ s1.close();
+ assertEquals(2, (int) _queueMBean.getActiveConsumerCount());
+ assertTrue(_queueMBean.getConsumerCount() == 3);
+ }
+
+ public void testGeneralProperties()
+ {
+ long maxQueueDepth = 1000; // in bytes
+ _queueMBean.setMaximumMessageCount(50000l);
+ _queueMBean.setMaximumMessageSize(2000l);
+ _queueMBean.setMaximumQueueDepth(maxQueueDepth);
+
+ assertTrue(_queueMBean.getMaximumMessageCount() == 50000);
+ assertTrue(_queueMBean.getMaximumMessageSize() == 2000);
+ assertTrue(_queueMBean.getMaximumQueueDepth() == (maxQueueDepth));
+
+ assertTrue(_queueMBean.getName().equals("testQueue"));
+ assertTrue(_queueMBean.getOwner().equals("AMQueueMBeanTest"));
+ assertFalse(_queueMBean.isAutoDelete());
+ assertFalse(_queueMBean.isDurable());
+ }
+
+ public void testExceptions() throws Exception
+ {
+ try
+ {
+ _queueMBean.viewMessages(0, 3);
+ fail();
+ }
+ catch (JMException ex)
+ {
+
+ }
+
+ try
+ {
+ _queueMBean.viewMessages(2, 1);
+ fail();
+ }
+ catch (JMException ex)
+ {
+
+ }
+
+ try
+ {
+ _queueMBean.viewMessages(-1, 1);
+ fail();
+ }
+ catch (JMException ex)
+ {
+
+ }
+
+ IncomingMessage msg = message(false, false);
+ long id = msg.getMessageId();
+ _queue.clearQueue(_storeContext);
+ ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>();
+ qs.add(_queue);
+ msg.enqueue(qs);
+ msg.routingComplete(_messageStore, new MessageHandleFactory());
+
+ msg.addContentBodyFrame(new ContentChunk()
+ {
+ ByteBuffer _data = ByteBuffer.allocate((int)MESSAGE_SIZE);
+
+ public int getSize()
+ {
+ return (int) MESSAGE_SIZE;
+ }
+
+ public ByteBuffer getData()
+ {
+ return _data;
+ }
+
+ public void reduceToFit()
+ {
+
+ }
+ });
+ msg.deliverToQueues();
+// _queue.process(_storeContext, new QueueEntry(_queue, msg), false);
+ _queueMBean.viewMessageContent(id);
+ try
+ {
+ _queueMBean.viewMessageContent(id + 1);
+ fail();
+ }
+ catch (JMException ex)
+ {
+
+ }
+ }
+
+ private IncomingMessage message(final boolean immediate, boolean persistent) throws AMQException
+ {
+ MessagePublishInfo publish = new MessagePublishInfo()
+ {
+
+ public AMQShortString getExchange()
+ {
+ return null;
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isImmediate()
+ {
+ return immediate;
+ }
+
+ public boolean isMandatory()
+ {
+ return false;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return null;
+ }
+ };
+
+ ContentHeaderBody contentHeaderBody = new ContentHeaderBody();
+ contentHeaderBody.bodySize = MESSAGE_SIZE; // in bytes
+ contentHeaderBody.properties = new BasicContentHeaderProperties();
+ ((BasicContentHeaderProperties) contentHeaderBody.properties).setDeliveryMode((byte) (persistent ? 2 : 1));
+ IncomingMessage msg = new IncomingMessage(_messageStore.getNewMessageId(), publish, _transactionalContext, _protocolSession);
+ msg.setContentHeaderBody(contentHeaderBody);
+ return msg;
+
+ }
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ IApplicationRegistry applicationRegistry = ApplicationRegistry.getInstance(1);
+ _virtualHost = applicationRegistry.getVirtualHostRegistry().getVirtualHost("test");
+ _messageStore = _virtualHost.getMessageStore();
+
+ _transactionalContext = new NonTransactionalContext(_messageStore, _storeContext,
+ null,
+ new LinkedList<RequiredDeliveryException>()
+ );
+
+ _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue"), false, new AMQShortString("AMQueueMBeanTest"), false, _virtualHost,
+ null);
+ _queueMBean = new AMQQueueMBean(_queue);
+
+ _protocolSession = new InternalTestProtocolSession();
+ }
+
+ public void tearDown()
+ {
+ ApplicationRegistry.remove(1);
+ }
+
+ private void sendMessages(int messageCount, boolean persistent) throws AMQException
+ {
+ for (int i = 0; i < messageCount; i++)
+ {
+ IncomingMessage currentMessage = message(false, persistent);
+ ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>();
+ qs.add(_queue);
+ currentMessage.enqueue(qs);
+
+ // route header
+ currentMessage.routingComplete(_messageStore, new MessageHandleFactory());
+
+ // Add the body so we have somthing to test later
+ currentMessage.addContentBodyFrame(
+ _protocolSession.getMethodRegistry()
+ .getProtocolVersionMethodConverter()
+ .convertToContentChunk(
+ new ContentBody(ByteBuffer.allocate((int) MESSAGE_SIZE),
+ MESSAGE_SIZE)));
+ currentMessage.deliverToQueues();
+
+
+ }
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AckTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AckTest.java
new file mode 100644
index 0000000000..9c2932c5e2
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AckTest.java
@@ -0,0 +1,420 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import junit.framework.TestCase;
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.subscription.SubscriptionFactoryImpl;
+import org.apache.qpid.server.flow.LimitlessCreditManager;
+import org.apache.qpid.server.flow.Pre0_10CreditManager;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.store.TestMemoryMessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.server.util.NullApplicationRegistry;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.Collections;
+
+/**
+ * Tests that acknowledgements are handled correctly.
+ */
+public class AckTest extends TestCase
+{
+ private static final Logger _log = Logger.getLogger(AckTest.class);
+
+ private Subscription _subscription;
+
+ private MockProtocolSession _protocolSession;
+
+ private TestMemoryMessageStore _messageStore;
+
+ private StoreContext _storeContext = new StoreContext();
+
+ private AMQChannel _channel;
+
+ private AMQQueue _queue;
+
+ private static final AMQShortString DEFAULT_CONSUMER_TAG = new AMQShortString("conTag");
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ ApplicationRegistry.initialise(new NullApplicationRegistry(), 1);
+
+ _messageStore = new TestMemoryMessageStore();
+ _protocolSession = new MockProtocolSession(_messageStore);
+ _channel = new AMQChannel(_protocolSession,5, _messageStore /*dont need exchange registry*/);
+
+ _protocolSession.addChannel(_channel);
+
+ _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("myQ"), false, new AMQShortString("guest"), true, ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test"),
+ null);
+ }
+
+ protected void tearDown()
+ {
+ ApplicationRegistry.remove(1);
+ }
+
+ private void publishMessages(int count) throws AMQException
+ {
+ publishMessages(count, false);
+ }
+
+ private void publishMessages(int count, boolean persistent) throws AMQException
+ {
+ TransactionalContext txnContext = new NonTransactionalContext(_messageStore, _storeContext, null,
+ new LinkedList<RequiredDeliveryException>()
+ );
+ _queue.registerSubscription(_subscription,false);
+ MessageHandleFactory factory = new MessageHandleFactory();
+ for (int i = 1; i <= count; i++)
+ {
+ // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0)
+ // TODO: Establish some way to determine the version for the test.
+ MessagePublishInfo publishBody = new MessagePublishInfo()
+ {
+
+ public AMQShortString getExchange()
+ {
+ return new AMQShortString("someExchange");
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isImmediate()
+ {
+ return false;
+ }
+
+ public boolean isMandatory()
+ {
+ return false;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return new AMQShortString("rk");
+ }
+ };
+ IncomingMessage msg = new IncomingMessage(_messageStore.getNewMessageId(), publishBody, txnContext,_protocolSession);
+ //IncomingMessage msg2 = null;
+ if (persistent)
+ {
+ BasicContentHeaderProperties b = new BasicContentHeaderProperties();
+ //This is DeliveryMode.PERSISTENT
+ b.setDeliveryMode((byte) 2);
+ ContentHeaderBody cb = new ContentHeaderBody();
+ cb.properties = b;
+ msg.setContentHeaderBody(cb);
+ }
+ else
+ {
+ msg.setContentHeaderBody(new ContentHeaderBody());
+ }
+ // we increment the reference here since we are not delivering the messaging to any queues, which is where
+ // the reference is normally incremented. The test is easier to construct if we have direct access to the
+ // subscription
+ ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>();
+ qs.add(_queue);
+ msg.enqueue(qs);
+ msg.routingComplete(_messageStore, factory);
+ if(msg.allContentReceived())
+ {
+ msg.deliverToQueues();
+ }
+ // we manually send the message to the subscription
+ //_subscription.send(new QueueEntry(_queue,msg), _queue);
+ }
+ }
+
+ /**
+ * Tests that the acknowledgements are correctly associated with a channel and
+ * order is preserved when acks are enabled
+ */
+ public void testAckChannelAssociationTest() throws AMQException
+ {
+ _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true, null, false, new LimitlessCreditManager());
+ final int msgCount = 10;
+ publishMessages(msgCount, true);
+
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == msgCount);
+ assertTrue(_messageStore.getMessageMetaDataMap().size() == msgCount);
+
+ Set<Long> deliveryTagSet = map.getDeliveryTags();
+ int i = 1;
+ for (long deliveryTag : deliveryTagSet)
+ {
+ assertTrue(deliveryTag == i);
+ i++;
+ QueueEntry unackedMsg = map.get(deliveryTag);
+ assertTrue(unackedMsg.getQueue() == _queue);
+ }
+
+ assertTrue(map.size() == msgCount);
+ assertTrue(_messageStore.getMessageMetaDataMap().size() == msgCount);
+ }
+
+ /**
+ * Tests that in no-ack mode no messages are retained
+ */
+ public void testNoAckMode() throws AMQException
+ {
+ // false arg means no acks expected
+ _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, false, null, false, new LimitlessCreditManager());
+ final int msgCount = 10;
+ publishMessages(msgCount);
+
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == 0);
+ assertTrue(_messageStore.getMessageMetaDataMap().size() == 0);
+ assertTrue(_messageStore.getContentBodyMap().size() == 0);
+
+ }
+
+ /**
+ * Tests that in no-ack mode no messages are retained
+ */
+ public void testPersistentNoAckMode() throws AMQException
+ {
+ // false arg means no acks expected
+ _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, false,null,false, new LimitlessCreditManager());
+ final int msgCount = 10;
+ publishMessages(msgCount, true);
+
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == 0);
+ assertTrue(_messageStore.getMessageMetaDataMap().size() == 0);
+ assertTrue(_messageStore.getContentBodyMap().size() == 0);
+
+ }
+
+ /**
+ * Tests that a single acknowledgement is handled correctly (i.e multiple flag not
+ * set case)
+ */
+ public void testSingleAckReceivedTest() throws AMQException
+ {
+ _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager());
+ final int msgCount = 10;
+ publishMessages(msgCount);
+
+ _channel.acknowledgeMessage(5, false);
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == msgCount - 1);
+
+ Set<Long> deliveryTagSet = map.getDeliveryTags();
+ int i = 1;
+ for (long deliveryTag : deliveryTagSet)
+ {
+ assertTrue(deliveryTag == i);
+ QueueEntry unackedMsg = map.get(deliveryTag);
+ assertTrue(unackedMsg.getQueue() == _queue);
+ // 5 is the delivery tag of the message that *should* be removed
+ if (++i == 5)
+ {
+ ++i;
+ }
+ }
+ }
+
+ /**
+ * Tests that a single acknowledgement is handled correctly (i.e multiple flag not
+ * set case)
+ */
+ public void testMultiAckReceivedTest() throws AMQException
+ {
+ _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager());
+ final int msgCount = 10;
+ publishMessages(msgCount);
+
+ _channel.acknowledgeMessage(5, true);
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == 5);
+
+ Set<Long> deliveryTagSet = map.getDeliveryTags();
+ int i = 1;
+ for (long deliveryTag : deliveryTagSet)
+ {
+ assertTrue(deliveryTag == i + 5);
+ QueueEntry unackedMsg = map.get(deliveryTag);
+ assertTrue(unackedMsg.getQueue() == _queue);
+ ++i;
+ }
+ }
+
+ /**
+ * Tests that a multiple acknowledgement is handled correctly. When ack'ing all pending msgs.
+ */
+ public void testMultiAckAllReceivedTest() throws AMQException
+ {
+ _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager());
+ final int msgCount = 10;
+ publishMessages(msgCount);
+
+ _channel.acknowledgeMessage(0, true);
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == 0);
+
+ Set<Long> deliveryTagSet = map.getDeliveryTags();
+ int i = 1;
+ for (long deliveryTag : deliveryTagSet)
+ {
+ assertTrue(deliveryTag == i + 5);
+ QueueEntry unackedMsg = map.get(deliveryTag);
+ assertTrue(unackedMsg.getQueue() == _queue);
+ ++i;
+ }
+ }
+
+ /**
+ * A regression fixing QPID-1136 showed this up
+ *
+ * @throws Exception
+ */
+ public void testMessageDequeueRestoresCreditTest() throws Exception
+ {
+ // Send 10 messages
+ Pre0_10CreditManager creditManager = new Pre0_10CreditManager(0l, 1);
+
+ _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession,
+ DEFAULT_CONSUMER_TAG, true, null, false, creditManager);
+ final int msgCount = 1;
+ publishMessages(msgCount);
+
+ _queue.deliverAsync(_subscription);
+
+ _channel.acknowledgeMessage(1, false);
+
+ // Check credit available
+ assertTrue("No credit available", creditManager.hasCredit());
+
+ }
+
+
+/*
+ public void testPrefetchHighLow() throws AMQException
+ {
+ int lowMark = 5;
+ int highMark = 10;
+
+ _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager());
+ _channel.setPrefetchLowMarkCount(lowMark);
+ _channel.setPrefetchHighMarkCount(highMark);
+
+ assertTrue(_channel.getPrefetchLowMarkCount() == lowMark);
+ assertTrue(_channel.getPrefetchHighMarkCount() == highMark);
+
+ publishMessages(highMark);
+
+ // at this point we should have sent out only highMark messages
+ // which have not bee received so will be queued up in the channel
+ // which should be suspended
+ assertTrue(_subscription.isSuspended());
+ UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap();
+ assertTrue(map.size() == highMark);
+
+ //acknowledge messages so we are just above lowMark
+ _channel.acknowledgeMessage(lowMark - 1, true);
+
+ //we should still be suspended
+ assertTrue(_subscription.isSuspended());
+ assertTrue(map.size() == lowMark + 1);
+
+ //acknowledge one more message
+ _channel.acknowledgeMessage(lowMark, true);
+
+ //and suspension should be lifted
+ assertTrue(!_subscription.isSuspended());
+
+ //pubilsh more msgs so we are just below the limit
+ publishMessages(lowMark - 1);
+
+ //we should not be suspended
+ assertTrue(!_subscription.isSuspended());
+
+ //acknowledge all messages
+ _channel.acknowledgeMessage(0, true);
+ try
+ {
+ Thread.sleep(3000);
+ }
+ catch (InterruptedException e)
+ {
+ _log.error("Error: " + e, e);
+ }
+ //map will be empty
+ assertTrue(map.size() == 0);
+ }
+
+*/
+/*
+ public void testPrefetch() throws AMQException
+ {
+ _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager());
+ _channel.setMessageCredit(5);
+
+ assertTrue(_channel.getPrefetchCount() == 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 now 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 junit.framework.TestSuite(AckTest.class);
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessage.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessage.java
new file mode 100644
index 0000000000..355ba6a362
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessage.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+
+public class MockAMQMessage extends AMQMessage
+{
+ public MockAMQMessage(long messageId)
+ throws AMQException
+ {
+ super(new MockAMQMessageHandle(messageId) ,
+ (StoreContext)null,
+ (MessagePublishInfo)new MockMessagePublishInfo());
+ }
+
+ protected MockAMQMessage(AMQMessage msg)
+ throws AMQException
+ {
+ super(msg);
+ }
+
+
+ @Override
+ public long getSize()
+ {
+ return 0l;
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessageHandle.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessageHandle.java
new file mode 100644
index 0000000000..bdb0707c27
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessageHandle.java
@@ -0,0 +1,37 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.server.store.StoreContext;
+
+public class MockAMQMessageHandle extends InMemoryMessageHandle
+{
+ public MockAMQMessageHandle(final Long messageId)
+ {
+ super(messageId);
+ }
+
+ @Override
+ public long getBodySize(StoreContext store)
+ {
+ return 0l;
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java
new file mode 100644
index 0000000000..20503bf15c
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java
@@ -0,0 +1,335 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.configuration.QueueConfiguration;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.AMQException;
+import org.apache.commons.configuration.Configuration;
+
+import java.util.List;
+import java.util.Set;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+public class MockAMQQueue implements AMQQueue
+{
+ private boolean _deleted = false;
+ private AMQShortString _name;
+
+ public MockAMQQueue(String name)
+ {
+ _name = new AMQShortString(name);
+ }
+
+ public MockAMQQueue()
+ {
+
+ }
+
+ public AMQShortString getName()
+ {
+ return _name;
+ }
+
+ public boolean isDurable()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isAutoDelete()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public AMQShortString getOwner()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public VirtualHost getVirtualHost()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void bind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void unBind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public List<ExchangeBinding> getExchangeBindings()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void registerSubscription(Subscription subscription, boolean exclusive) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void unregisterSubscription(Subscription subscription) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public int getConsumerCount()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public int getActiveConsumerCount()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isUnused()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isEmpty()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public int getMessageCount()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public int getUndeliveredMessageCount()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getQueueDepth()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getReceivedMessageCount()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getOldestMessageArrivalTime()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isDeleted()
+ {
+ return _deleted;
+ }
+
+ public int delete() throws AMQException
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public QueueEntry enqueue(StoreContext storeContext, AMQMessage message) throws AMQException
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void requeue(StoreContext storeContext, QueueEntry entry) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void dequeue(StoreContext storeContext, QueueEntry entry) throws FailedDequeueException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean resend(QueueEntry entry, Subscription subscription) throws AMQException
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void addQueueDeleteTask(Task task)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public List<QueueEntry> getMessagesOnTheQueue()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public List<QueueEntry> getMessagesOnTheQueue(long fromMessageId, long toMessageId)
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public List<Long> getMessagesOnTheQueue(int num)
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public List<Long> getMessagesOnTheQueue(int num, int offest)
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public QueueEntry getMessageOnTheQueue(long messageId)
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void moveMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName, StoreContext storeContext)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void copyMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName, StoreContext storeContext)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void removeMessagesFromQueue(long fromMessageId, long toMessageId, StoreContext storeContext)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getMaximumMessageSize()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setMaximumMessageSize(long value)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getMaximumMessageCount()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setMaximumMessageCount(long value)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getMaximumQueueDepth()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setMaximumQueueDepth(long value)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getMaximumMessageAge()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setMaximumMessageAge(long maximumMessageAge)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getMinimumAlertRepeatGap()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void deleteMessageFromTop(StoreContext storeContext) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long clearQueue(StoreContext storeContext) throws AMQException
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void checkMessageStatus() throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public Set<NotificationCheck> getNotificationChecks()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void flushSubscription(Subscription sub) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void deliverAsync(Subscription sub)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void deliverAsync()
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void stop()
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public ManagedObject getManagedObject()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public int compareTo(AMQQueue o)
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void setMinimumAlertRepeatGap(long value)
+ {
+
+ }
+
+ public void configure(QueueConfiguration config)
+ {
+
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockMessagePublishInfo.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockMessagePublishInfo.java
new file mode 100644
index 0000000000..5a5ffaa14d
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockMessagePublishInfo.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.AMQShortString;
+
+public class MockMessagePublishInfo implements MessagePublishInfo
+{
+ public AMQShortString getExchange()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isImmediate()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isMandatory()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockProtocolSession.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockProtocolSession.java
new file mode 100644
index 0000000000..99c88fac3e
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockProtocolSession.java
@@ -0,0 +1,262 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.server.output.ProtocolOutputConverterRegistry;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.transport.Sender;
+
+import javax.security.sasl.SaslServer;
+import java.util.HashMap;
+import java.util.Map;
+import java.security.Principal;
+
+/**
+ * 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 AMQShortString getContextKey()
+ {
+ return null;
+ }
+
+ public void setContextKey(AMQShortString 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 closeChannelOk(int channelId)
+ {
+
+ }
+
+ public boolean channelAwaitingClosure(int channelId)
+ {
+ return false;
+ }
+
+ public void removeChannel(int channelId)
+ {
+ _channelMap.remove(channelId);
+ }
+
+ public void initHeartbeats(int delay)
+ {
+ }
+
+ public void closeSession() throws AMQException
+ {
+ }
+
+ public void closeConnection(int channelId, AMQConnectionException e, boolean closeIoSession) throws AMQException
+ {
+ }
+
+ public Object getKey()
+ {
+ return null;
+ }
+
+ public String getLocalFQDN()
+ {
+ return null;
+ }
+
+ public SaslServer getSaslServer()
+ {
+ return null;
+ }
+
+ public void setSaslServer(SaslServer saslServer)
+ {
+ }
+
+ public FieldTable getClientProperties()
+ {
+ return null;
+ }
+
+ public void setClientProperties(FieldTable clientProperties)
+ {
+ }
+
+ public Object getClientIdentifier()
+ {
+ return null;
+ }
+
+ public VirtualHost getVirtualHost()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setVirtualHost(VirtualHost virtualHost)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void addSessionCloseTask(Task task)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void removeSessionCloseTask(Task task)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public ProtocolOutputConverter getProtocolOutputConverter()
+ {
+ return ProtocolOutputConverterRegistry.getConverter(this);
+ }
+
+ public void setAuthorizedID(Principal authorizedID)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public Principal getAuthorizedID()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public MethodRegistry getMethodRegistry()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void methodFrameReceived(int channelId, AMQMethodBody body)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void contentHeaderReceived(int channelId, ContentHeaderBody body)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void contentBodyReceived(int channelId, ContentBody body)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void heartbeatBodyReceived(int channelId, HeartbeatBody body)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public MethodDispatcher getMethodDispatcher()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public ProtocolSessionIdentifier getSessionIdentifier()
+ {
+ return null;
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return getProtocolVersion().getMajorVersion();
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return getProtocolVersion().getMinorVersion();
+ }
+
+
+ public ProtocolVersion getProtocolVersion()
+ {
+ return ProtocolVersion.getLatestSupportedVersion(); //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+
+ public VersionSpecificRegistry getRegistry()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setSender(Sender<java.nio.ByteBuffer> sender)
+ {
+ // FIXME AS TODO
+
+ }
+
+ public void init()
+ {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java
new file mode 100644
index 0000000000..37f91e7464
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java
@@ -0,0 +1,197 @@
+/*
+*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.subscription.Subscription;
+
+public class MockQueueEntry implements QueueEntry
+{
+
+ private AMQMessage _message;
+
+ public boolean acquire()
+ {
+ return false;
+ }
+
+ public boolean acquire(Subscription sub)
+ {
+ return false;
+ }
+
+ public boolean acquiredBySubscription()
+ {
+ return false;
+ }
+
+ public void addStateChangeListener(StateChangeListener listener)
+ {
+
+ }
+
+ public String debugIdentity()
+ {
+ return null;
+ }
+
+ public boolean delete()
+ {
+ return false;
+ }
+
+ public void dequeue(StoreContext storeContext) throws FailedDequeueException
+ {
+
+ }
+
+ public void discard(StoreContext storeContext) throws FailedDequeueException, MessageCleanupException
+ {
+
+ }
+
+ public void dispose(StoreContext storeContext) throws MessageCleanupException
+ {
+
+ }
+
+ public boolean expired() throws AMQException
+ {
+ return false;
+ }
+
+ public Subscription getDeliveredSubscription()
+ {
+ return null;
+ }
+
+ public boolean getDeliveredToConsumer()
+ {
+ return false;
+ }
+
+ public AMQMessage getMessage()
+ {
+ return _message;
+ }
+
+ public AMQQueue getQueue()
+ {
+ return null;
+ }
+
+ public long getSize()
+ {
+ return 0;
+ }
+
+ public boolean immediateAndNotDelivered()
+ {
+ return false;
+ }
+
+ public boolean isAcquired()
+ {
+ return false;
+ }
+
+ public boolean isDeleted()
+ {
+ return false;
+ }
+
+
+ public boolean isQueueDeleted()
+ {
+
+ return false;
+ }
+
+
+ public boolean isRejectedBy(Subscription subscription)
+ {
+
+ return false;
+ }
+
+
+ public void reject()
+ {
+
+
+ }
+
+
+ public void reject(Subscription subscription)
+ {
+
+
+ }
+
+
+ public void release()
+ {
+
+
+ }
+
+
+ public boolean removeStateChangeListener(StateChangeListener listener)
+ {
+
+ return false;
+ }
+
+
+ public void requeue(StoreContext storeContext) throws AMQException
+ {
+
+
+ }
+
+
+ public void setDeliveredToSubscription()
+ {
+
+
+ }
+
+
+ public void setRedelivered(boolean b)
+ {
+
+
+ }
+
+
+ public int compareTo(QueueEntry o)
+ {
+
+ return 0;
+ }
+
+ public void setMessage(AMQMessage msg)
+ {
+ _message = msg;
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java
new file mode 100644
index 0000000000..3084dc7fa1
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java
@@ -0,0 +1,435 @@
+package org.apache.qpid.server.queue;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.ContentHeaderProperties;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.exchange.DirectExchange;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.store.TestableMemoryMessageStore;
+import org.apache.qpid.server.subscription.MockSubscription;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.subscription.SubscriptionImpl;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class SimpleAMQQueueTest extends TestCase
+{
+
+ protected SimpleAMQQueue _queue;
+ protected VirtualHost _virtualHost;
+ protected TestableMemoryMessageStore _store = new TestableMemoryMessageStore();
+ protected AMQShortString _qname = new AMQShortString("qname");
+ protected AMQShortString _owner = new AMQShortString("owner");
+ protected AMQShortString _routingKey = new AMQShortString("routing key");
+ protected DirectExchange _exchange = new DirectExchange();
+ protected MockSubscription _subscription = new MockSubscription();
+ protected FieldTable _arguments = null;
+
+ MessagePublishInfo info = new MessagePublishInfo()
+ {
+
+ public AMQShortString getExchange()
+ {
+ return null;
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isImmediate()
+ {
+ return false;
+ }
+
+ public boolean isMandatory()
+ {
+ return false;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return null;
+ }
+ };
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ //Create Application Registry for test
+ ApplicationRegistry applicationRegistry = (ApplicationRegistry)ApplicationRegistry.getInstance(1);
+
+ PropertiesConfiguration env = new PropertiesConfiguration();
+ _virtualHost = new VirtualHost(new VirtualHostConfiguration(getClass().getName(), env), _store);
+ applicationRegistry.getVirtualHostRegistry().registerVirtualHost(_virtualHost);
+
+ _queue = (SimpleAMQQueue) AMQQueueFactory.createAMQQueueImpl(_qname, false, _owner, false, _virtualHost, _arguments);
+ }
+
+ @Override
+ protected void tearDown()
+ {
+ _queue.stop();
+ ApplicationRegistry.remove(1);
+ }
+
+ public void testCreateQueue() throws AMQException
+ {
+ _queue.stop();
+ try {
+ _queue = (SimpleAMQQueue) AMQQueueFactory.createAMQQueueImpl(null, false, _owner, false, _virtualHost, _arguments );
+ assertNull("Queue was created", _queue);
+ }
+ catch (IllegalArgumentException e)
+ {
+ assertTrue("Exception was not about missing name",
+ e.getMessage().contains("name"));
+ }
+
+ try {
+ _queue = new SimpleAMQQueue(_qname, false, _owner, false, null);
+ assertNull("Queue was created", _queue);
+ }
+ catch (IllegalArgumentException e)
+ {
+ assertTrue("Exception was not about missing vhost",
+ e.getMessage().contains("Host"));
+ }
+
+ _queue = (SimpleAMQQueue) AMQQueueFactory.createAMQQueueImpl(_qname, false, _owner, false,
+ _virtualHost, _arguments);
+ assertNotNull("Queue was not created", _queue);
+ }
+
+ public void testGetVirtualHost()
+ {
+ assertEquals("Virtual host was wrong", _virtualHost, _queue.getVirtualHost());
+ }
+
+ public void testBinding()
+ {
+ try
+ {
+ _queue.bind(_exchange, _routingKey, null);
+ assertTrue("Routing key was not bound",
+ _exchange.getBindings().containsKey(_routingKey));
+ assertEquals("Queue was not bound to key",
+ _exchange.getBindings().get(_routingKey).get(0),
+ _queue);
+ assertEquals("Exchange binding count", 1,
+ _queue.getExchangeBindings().size());
+ assertEquals("Wrong exchange bound", _routingKey,
+ _queue.getExchangeBindings().get(0).getRoutingKey());
+ assertEquals("Wrong exchange bound", _exchange,
+ _queue.getExchangeBindings().get(0).getExchange());
+
+ _queue.unBind(_exchange, _routingKey, null);
+ assertFalse("Routing key was still bound",
+ _exchange.getBindings().containsKey(_routingKey));
+ assertNull("Routing key was not empty",
+ _exchange.getBindings().get(_routingKey));
+ }
+ catch (AMQException e)
+ {
+ assertNull("Unexpected exception", e);
+ }
+ }
+
+ public void testSubscription() throws AMQException
+ {
+ // Check adding a subscription adds it to the queue
+ _queue.registerSubscription(_subscription, false);
+ assertEquals("Subscription did not get queue", _queue,
+ _subscription.getQueue());
+ assertEquals("Queue does not have consumer", 1,
+ _queue.getConsumerCount());
+ assertEquals("Queue does not have active consumer", 1,
+ _queue.getActiveConsumerCount());
+
+ // Check sending a message ends up with the subscriber
+ AMQMessage messageA = createMessage(new Long(24));
+ _queue.enqueue(null, messageA);
+ assertEquals(messageA, _subscription.getLastSeenEntry().getMessage());
+
+ // Check removing the subscription removes it's information from the queue
+ _queue.unregisterSubscription(_subscription);
+ assertTrue("Subscription still had queue", _subscription.isClosed());
+ assertFalse("Queue still has consumer", 1 == _queue.getConsumerCount());
+ assertFalse("Queue still has active consumer",
+ 1 == _queue.getActiveConsumerCount());
+
+ AMQMessage messageB = createMessage(new Long (25));
+ _queue.enqueue(null, messageB);
+ QueueEntry entry = _subscription.getLastSeenEntry();
+ assertNull(entry);
+ }
+
+ public void testQueueNoSubscriber() throws AMQException, InterruptedException
+ {
+ AMQMessage messageA = createMessage(new Long(24));
+ _queue.enqueue(null, messageA);
+ _queue.registerSubscription(_subscription, false);
+ Thread.sleep(150);
+ assertEquals(messageA, _subscription.getLastSeenEntry().getMessage());
+ }
+
+ public void testExclusiveConsumer() throws AMQException
+ {
+ // Check adding an exclusive subscription adds it to the queue
+ _queue.registerSubscription(_subscription, true);
+ assertEquals("Subscription did not get queue", _queue,
+ _subscription.getQueue());
+ assertEquals("Queue does not have consumer", 1,
+ _queue.getConsumerCount());
+ assertEquals("Queue does not have active consumer", 1,
+ _queue.getActiveConsumerCount());
+
+ // Check sending a message ends up with the subscriber
+ AMQMessage messageA = createMessage(new Long(24));
+ _queue.enqueue(null, messageA);
+ assertEquals(messageA, _subscription.getLastSeenEntry().getMessage());
+
+ // Check we cannot add a second subscriber to the queue
+ Subscription subB = new MockSubscription();
+ Exception ex = null;
+ try
+ {
+ _queue.registerSubscription(subB, false);
+ }
+ catch (AMQException e)
+ {
+ ex = e;
+ }
+ assertNotNull(ex);
+ assertTrue(ex instanceof AMQException);
+
+ // Check we cannot add an exclusive subscriber to a queue with an
+ // existing subscription
+ _queue.unregisterSubscription(_subscription);
+ _queue.registerSubscription(_subscription, false);
+ try
+ {
+ _queue.registerSubscription(subB, true);
+ }
+ catch (AMQException e)
+ {
+ ex = e;
+ }
+ assertNotNull(ex);
+ }
+
+ public void testAutoDeleteQueue() throws Exception
+ {
+ _queue.stop();
+ _queue = new SimpleAMQQueue(_qname, false, _owner, true, _virtualHost);
+ _queue.registerSubscription(_subscription, false);
+ AMQMessage message = createMessage(new Long(25));
+ _queue.enqueue(null, message);
+ _queue.unregisterSubscription(_subscription);
+ assertTrue("Queue was not deleted when subscription was removed",
+ _queue.isDeleted());
+ }
+
+ public void testResend() throws Exception
+ {
+ _queue.registerSubscription(_subscription, false);
+ Long id = new Long(26);
+ AMQMessage message = createMessage(id);
+ _queue.enqueue(null, message);
+ QueueEntry entry = _subscription.getLastSeenEntry();
+ entry.setRedelivered(true);
+ _queue.resend(entry, _subscription);
+
+ }
+
+ public void testGetFirstMessageId() throws Exception
+ {
+ // Create message
+ Long messageId = new Long(23);
+ AMQMessage message = createMessage(messageId);
+
+ // Put message on queue
+ _queue.enqueue(null, message);
+ // Get message id
+ Long testmsgid = _queue.getMessagesOnTheQueue(1).get(0);
+
+ // Check message id
+ assertEquals("Message ID was wrong", messageId, testmsgid);
+ }
+
+ public void testGetFirstFiveMessageIds() throws Exception
+ {
+ for (int i = 0 ; i < 5; i++)
+ {
+ // Create message
+ Long messageId = new Long(i);
+ AMQMessage message = createMessage(messageId);
+ // Put message on queue
+ _queue.enqueue(null, message);
+ }
+ // Get message ids
+ List<Long> msgids = _queue.getMessagesOnTheQueue(5);
+
+ // Check message id
+ for (int i = 0; i < 5; i++)
+ {
+ Long messageId = new Long(i);
+ assertEquals("Message ID was wrong", messageId, msgids.get(i));
+ }
+ }
+
+ public void testGetLastFiveMessageIds() throws Exception
+ {
+ for (int i = 0 ; i < 10; i++)
+ {
+ // Create message
+ Long messageId = new Long(i);
+ AMQMessage message = createMessage(messageId);
+ // Put message on queue
+ _queue.enqueue(null, message);
+ }
+ // Get message ids
+ List<Long> msgids = _queue.getMessagesOnTheQueue(5, 5);
+
+ // Check message id
+ for (int i = 0; i < 5; i++)
+ {
+ Long messageId = new Long(i+5);
+ assertEquals("Message ID was wrong", messageId, msgids.get(i));
+ }
+ }
+
+ public void testEnqueueDequeueOfPersistentMessageToNonDurableQueue() throws AMQException
+ {
+ // Create IncomingMessage and nondurable queue
+ NonTransactionalContext txnContext = new NonTransactionalContext(_store, null, null, null);
+ IncomingMessage msg = new IncomingMessage(1L, info, txnContext, null);
+ ContentHeaderBody contentHeaderBody = new ContentHeaderBody();
+ contentHeaderBody.properties = new BasicContentHeaderProperties();
+ ((BasicContentHeaderProperties) contentHeaderBody.properties).setDeliveryMode((byte) 2);
+ msg.setContentHeaderBody(contentHeaderBody);
+ ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>();
+
+ // Send persistent message
+ qs.add(_queue);
+ msg.enqueue(qs);
+ msg.routingComplete(_store, new MessageHandleFactory());
+ _store.storeMessageMetaData(null, new Long(1L), new MessageMetaData(info, contentHeaderBody, 1));
+
+ // Check that it is enqueued
+ AMQQueue data = _store.getMessages().get(1L);
+ assertNotNull(data);
+
+ // Dequeue message
+ MockQueueEntry entry = new MockQueueEntry();
+ AMQMessage amqmsg = new AMQMessage(1L, _store, new MessageHandleFactory(), txnContext);
+
+ entry.setMessage(amqmsg);
+ _queue.dequeue(null, entry);
+
+ // Check that it is dequeued
+ data = _store.getMessages().get(1L);
+ assertNull(data);
+ }
+
+
+ // FIXME: move this to somewhere useful
+ private static AMQMessageHandle createMessageHandle(final long messageId, final MessagePublishInfo publishBody)
+ {
+ final AMQMessageHandle amqMessageHandle = (new MessageHandleFactory()).createMessageHandle(messageId,
+ null,
+ false);
+ try
+ {
+ amqMessageHandle.setPublishAndContentHeaderBody(new StoreContext(),
+ publishBody,
+ new ContentHeaderBody()
+ {
+ public int getSize()
+ {
+ return 1;
+ }
+ });
+ }
+ catch (AMQException e)
+ {
+ // won't happen
+ }
+
+
+ return amqMessageHandle;
+ }
+
+ public class TestMessage extends AMQMessage
+ {
+ private final long _tag;
+ private int _count;
+
+ TestMessage(long tag, long messageId, MessagePublishInfo publishBody, StoreContext storeContext)
+ throws AMQException
+ {
+ super(createMessageHandle(messageId, publishBody), storeContext, publishBody);
+ _tag = tag;
+ }
+
+
+ public boolean incrementReference()
+ {
+ _count++;
+ return true;
+ }
+
+ public void decrementReference(StoreContext context)
+ {
+ _count--;
+ }
+
+ void assertCountEquals(int expected)
+ {
+ assertEquals("Wrong count for message with tag " + _tag, expected, _count);
+ }
+ }
+
+ protected AMQMessage createMessage(Long id) throws AMQException
+ {
+ AMQMessage messageA = new TestMessage(id, id, info, new StoreContext());
+ return messageA;
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java
new file mode 100644
index 0000000000..832df80004
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java
@@ -0,0 +1,60 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import junit.framework.TestCase;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.pool.ReferenceCountingExecutorService;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.AMQException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SimpleAMQQueueThreadPoolTest extends TestCase
+{
+
+ public void test() throws AMQException
+ {
+ int initialCount = ReferenceCountingExecutorService.getInstance().getReferenceCount();
+ VirtualHost test = ApplicationRegistry.getInstance(1).getVirtualHostRegistry().getVirtualHost("test");
+
+ try
+ {
+ SimpleAMQQueue queue = (SimpleAMQQueue) AMQQueueFactory.createAMQQueueImpl(new AMQShortString("test"), false,
+ new AMQShortString("owner"),
+ false, test, null);
+
+ assertFalse("Creation did not start Pool.", ReferenceCountingExecutorService.getInstance().getPool().isShutdown());
+
+ assertEquals("References not increased", initialCount + 1, ReferenceCountingExecutorService.getInstance().getReferenceCount());
+
+ queue.stop();
+
+ assertEquals("References not decreased", initialCount , ReferenceCountingExecutorService.getInstance().getReferenceCount());
+ }
+ finally
+ {
+ ApplicationRegistry.remove(1);
+ }
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/registry/ApplicationRegistryShutdownTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/registry/ApplicationRegistryShutdownTest.java
new file mode 100644
index 0000000000..939e3436a5
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/registry/ApplicationRegistryShutdownTest.java
@@ -0,0 +1,120 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.registry;
+
+import junit.framework.TestCase;
+import org.apache.qpid.server.util.TestApplicationRegistry;
+
+import java.security.Security;
+import java.security.Provider;
+import java.util.List;
+import java.util.LinkedList;
+
+/**
+ * QPID-1390 : Test to validate that the AuthenticationManger succesfully unregisters any new SASL providers when
+ * The ApplicationRegistry is closed.
+ *
+ * This should be expanded as QPID-1399 is implemented.
+ */
+public class ApplicationRegistryShutdownTest extends TestCase
+{
+
+ ApplicationRegistry _registry;
+
+ public void setUp() throws Exception
+ {
+ _registry = new TestApplicationRegistry();
+ }
+
+ /**
+ * QPID-1399 : Ensure that the Authentiction manager unregisters any SASL providers created during
+ * ApplicationRegistry initialisation.
+ *
+ */
+ public void testAuthenticationMangerCleansUp()
+ {
+ // Get default providers
+ Provider[] defaultProviders = Security.getProviders();
+
+ // Register new providers
+ try
+ {
+ _registry.initialise();
+ }
+ catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+
+ // Get the providers after initialisation
+ Provider[] providersAfterInitialisation = Security.getProviders();
+
+ // Find the additions
+ List additions = new LinkedList();
+ for (Provider afterInit : providersAfterInitialisation)
+ {
+ boolean found = false;
+ for (Provider defaultProvider : defaultProviders)
+ {
+ if (defaultProvider == afterInit)
+ {
+ found=true;
+ break;
+ }
+ }
+
+ // Record added registies
+ if (!found)
+ {
+ additions.add(afterInit);
+ }
+ }
+
+ // Not using isEmpty as that is not in Java 5
+ assertTrue("No new SASL mechanisms added by initialisation.", additions.size() != 0 );
+
+ //Close the registry which will perform the close the AuthenticationManager
+ try
+ {
+ _registry.close();
+ }
+ catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+
+ //Validate that the SASL plugins have been removed.
+ Provider[] providersAfterClose = Security.getProviders();
+
+ assertTrue("No providers unregistered", providersAfterInitialisation.length > providersAfterClose.length);
+
+ //Ensure that the additions are not still present after close().
+ for (Provider afterClose : providersAfterClose)
+ {
+ assertFalse("Added provider not unregistered", additions.contains(afterClose));
+ }
+ }
+
+
+
+
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ACLManagerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ACLManagerTest.java
new file mode 100644
index 0000000000..6c6835ccca
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ACLManagerTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.qpid.server.configuration.SecurityConfiguration;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.plugins.MockPluginManager;
+import org.apache.qpid.server.plugins.PluginManager;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.MockAMQQueue;
+import org.apache.qpid.server.queue.MockProtocolSession;
+import org.apache.qpid.server.store.TestableMemoryMessageStore;
+
+public class ACLManagerTest extends TestCase
+{
+
+ private ACLManager _authzManager;
+ private AMQProtocolSession _session;
+ private SecurityConfiguration _conf;
+ private PluginManager _pluginManager;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ File tmpFile = File.createTempFile(getClass().getName(), "testconfig");
+ tmpFile.deleteOnExit();
+ BufferedWriter out = new BufferedWriter(new FileWriter(tmpFile));
+ out.write("<security><queueDenier>notyet</queueDenier><exchangeDenier>yes</exchangeDenier></security>");
+ out.close();
+
+ _conf = new SecurityConfiguration(new XMLConfiguration(tmpFile));
+
+ // Create ACLManager
+
+ _pluginManager = new MockPluginManager("");
+ _authzManager = new ACLManager(_conf, _pluginManager);
+
+ _session = new MockProtocolSession(new TestableMemoryMessageStore());
+ }
+
+ public void testACLManagerConfigurationPluginManager() throws Exception
+ {
+ AMQQueue queue = new MockAMQQueue("notyet");
+ AMQQueue otherQueue = new MockAMQQueue("other");
+
+ assertFalse(_authzManager.authoriseDelete(_session, queue));
+
+ // This should only be denied if the config hasn't been correctly passed in
+ assertTrue(_authzManager.authoriseDelete(_session, otherQueue));
+ assertTrue(_authzManager.authorisePurge(_session, queue));
+ }
+
+ public void testACLManagerConfigurationPluginManagerACLPlugin() throws ConfigurationException
+ {
+ _authzManager = new ACLManager(_conf, _pluginManager, ExchangeDenier.FACTORY);
+
+ Exchange exchange = null;
+ assertFalse(_authzManager.authoriseDelete(_session, exchange));
+ }
+
+ public void testConfigurePlugins() throws ConfigurationException
+ {
+ Configuration hostConfig = new PropertiesConfiguration();
+ hostConfig.setProperty("queueDenier", "thisoneneither");
+ _authzManager.configureHostPlugins(new SecurityConfiguration(hostConfig));
+ AMQQueue queue = new MockAMQQueue("thisoneneither");
+ assertFalse(_authzManager.authoriseDelete(_session, queue));
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ExchangeDenier.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ExchangeDenier.java
new file mode 100644
index 0000000000..f62b0c6241
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ExchangeDenier.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.security.access.plugins.AllowAll;
+
+public class ExchangeDenier extends AllowAll
+{
+
+ public static final ACLPluginFactory FACTORY = new ACLPluginFactory()
+ {
+ public boolean supportsTag(String name)
+ {
+ return name.startsWith("exchangeDenier");
+ }
+
+ public ACLPlugin newInstance(Configuration config)
+ {
+ return new ExchangeDenier();
+ }
+ };
+
+ @Override
+ public AuthzResult authoriseDelete(AMQProtocolSession session, Exchange exchange)
+ {
+ return AuthzResult.DENIED;
+ }
+
+ @Override
+ public String getPluginName()
+ {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public boolean supportsTag(String name)
+ {
+ return name.equals("exchangeDenier");
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java
new file mode 100644
index 0000000000..e1e93ae9cb
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java
@@ -0,0 +1,156 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.security.access;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.amqp_0_9.ExchangeDeclareBodyImpl;
+import org.apache.qpid.framing.amqp_0_9.QueueDeclareBodyImpl;
+import org.apache.qpid.framing.amqp_8_0.QueueBindBodyImpl;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.exchange.DirectExchange;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.AMQQueueFactory;
+import org.apache.qpid.server.security.access.ACLPlugin.AuthzResult;
+import org.apache.qpid.server.store.SkeletonMessageStore;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class PrincipalPermissionsTest extends TestCase
+{
+
+ private String _user = "user";
+ private PrincipalPermissions _perms;
+
+ // Common things that are passed to frame constructors
+ private AMQShortString _queueName = new AMQShortString(this.getClass().getName()+"queue");
+ private AMQShortString _exchangeName = new AMQShortString("amq.direct");
+ private AMQShortString _routingKey = new AMQShortString(this.getClass().getName()+"route");
+ private int _ticket = 1;
+ private FieldTable _arguments = null;
+ private boolean _nowait = false;
+ private boolean _passive = false;
+ private boolean _durable = false;
+ private boolean _autoDelete = false;
+ private AMQShortString _exchangeType = new AMQShortString("direct");
+ private boolean _internal = false;
+
+ private DirectExchange _exchange;
+ private VirtualHost _virtualHost;
+ private AMQShortString _owner = new AMQShortString(this.getClass().getName()+"owner");
+ private AMQQueue _queue;
+ private Boolean _temporary = false;
+
+ @Override
+ public void setUp()
+ {
+ _perms = new PrincipalPermissions(_user);
+ try
+ {
+ PropertiesConfiguration env = new PropertiesConfiguration();
+ _virtualHost = new VirtualHost(new VirtualHostConfiguration("test", env));
+ _exchange = DirectExchange.TYPE.newInstance(_virtualHost, _exchangeName, _durable, _ticket, _autoDelete);
+ _queue = AMQQueueFactory.createAMQQueueImpl(_queueName, false, _owner , false, _virtualHost, _arguments);
+ }
+ catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+
+ public void testPrincipalPermissions()
+ {
+ assertNotNull(_perms);
+ assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.ACCESS, (Object[]) null));
+ }
+
+ // FIXME: test has been disabled since the permissions assume that the user has tried to create
+ // the queue first. QPID-1597
+ public void disableTestBind() throws Exception
+ {
+ QueueBindBodyImpl bind = new QueueBindBodyImpl(_ticket, _queueName, _exchangeName, _routingKey, _nowait, _arguments);
+ Object[] args = new Object[]{bind, _exchange, _queue, _routingKey};
+
+ assertEquals(AuthzResult.DENIED, _perms.authorise(Permission.BIND, args));
+ _perms.grant(Permission.BIND, (Object[]) null);
+ assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.BIND, args));
+ }
+
+ public void testQueueCreate()
+ {
+ Object[] grantArgs = new Object[]{_temporary , _queueName, _exchangeName, _routingKey};
+ Object[] authArgs = new Object[]{_autoDelete, _queueName};
+
+ assertEquals(AuthzResult.DENIED, _perms.authorise(Permission.CREATEQUEUE, authArgs));
+ _perms.grant(Permission.CREATEQUEUE, grantArgs);
+ assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.CREATEQUEUE, authArgs));
+ }
+
+ public void testQueueCreateWithNullRoutingKey()
+ {
+ Object[] grantArgs = new Object[]{_temporary , _queueName, _exchangeName, null};
+ Object[] authArgs = new Object[]{_autoDelete, _queueName};
+
+ assertEquals(AuthzResult.DENIED, _perms.authorise(Permission.CREATEQUEUE, authArgs));
+ _perms.grant(Permission.CREATEQUEUE, grantArgs);
+ assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.CREATEQUEUE, authArgs));
+ }
+
+ // FIXME disabled, this fails due to grant putting the grant into the wrong map QPID-1598
+ public void disableTestExchangeCreate()
+ {
+ ExchangeDeclareBodyImpl exchangeDeclare =
+ new ExchangeDeclareBodyImpl(_ticket, _exchangeName, _exchangeType, _passive, _durable,
+ _autoDelete, _internal, _nowait, _arguments);
+ Object[] authArgs = new Object[]{exchangeDeclare};
+ Object[] grantArgs = new Object[]{_exchangeName, _exchangeType};
+
+ assertEquals(AuthzResult.DENIED, _perms.authorise(Permission.CREATEEXCHANGE, authArgs));
+ _perms.grant(Permission.CREATEEXCHANGE, grantArgs);
+ assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.CREATEEXCHANGE, authArgs));
+ }
+
+ public void testConsume()
+ {
+ Object[] authArgs = new Object[]{_queue};
+ Object[] grantArgs = new Object[]{_queueName, _temporary, _temporary};
+
+ /* FIXME: This throws a null pointer exception QPID-1599
+ * assertFalse(_perms.authorise(Permission.CONSUME, authArgs));
+ */
+ _perms.grant(Permission.CONSUME, grantArgs);
+ assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.CONSUME, authArgs));
+ }
+
+ public void testPublish()
+ {
+ Object[] authArgs = new Object[]{_exchange, _routingKey};
+ Object[] grantArgs = new Object[]{_exchange.getName(), _routingKey};
+
+ assertEquals(AuthzResult.DENIED, _perms.authorise(Permission.PUBLISH, authArgs));
+ _perms.grant(Permission.PUBLISH, grantArgs);
+ assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.PUBLISH, authArgs));
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/QueueDenier.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/QueueDenier.java
new file mode 100644
index 0000000000..5497f0ae44
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/QueueDenier.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.security.access.ACLPlugin.AuthzResult;
+import org.apache.qpid.server.security.access.plugins.AllowAll;
+
+public class QueueDenier extends AllowAll
+{
+
+ public static final ACLPluginFactory FACTORY = new ACLPluginFactory()
+ {
+ public boolean supportsTag(String name)
+ {
+ return name.equals("queueDenier");
+ }
+
+ public ACLPlugin newInstance(Configuration config)
+ {
+ QueueDenier plugin = new QueueDenier();
+ plugin.setConfiguration(config);
+ return plugin;
+ }
+ };
+
+ private String _queueName = "";
+
+
+ @Override
+ public AuthzResult authoriseDelete(AMQProtocolSession session, AMQQueue queue)
+ {
+ if (!(queue.getName().toString().equals(_queueName)))
+ {
+ return AuthzResult.ALLOWED;
+ }
+ else
+ {
+ return AuthzResult.DENIED;
+ }
+ }
+
+ @Override
+ public void setConfiguration(Configuration config)
+ {
+ _queueName = config.getString("queueDenier");
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBeanTest.java
new file mode 100644
index 0000000000..958ee35476
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBeanTest.java
@@ -0,0 +1,271 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.security.access.management;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.management.MBeanInvocationHandlerImpl;
+import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase;
+
+import junit.framework.TestCase;
+
+/* Note: The main purpose is to test the jmx access rights file manipulation
+ * within AMQUserManagementMBean. The Principal Databases are tested by their own tests,
+ * this test just exercises their usage in AMQUserManagementMBean.
+ */
+public class AMQUserManagementMBeanTest extends TestCase
+{
+ private PlainPasswordFilePrincipalDatabase _database;
+ private AMQUserManagementMBean _amqumMBean;
+
+ private File _passwordFile;
+ private File _accessFile;
+
+ private static final String TEST_USERNAME = "testuser";
+ private static final String TEST_PASSWORD = "password";
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ _database = new PlainPasswordFilePrincipalDatabase();
+ _amqumMBean = new AMQUserManagementMBean();
+ loadFreshTestPasswordFile();
+ loadFreshTestAccessFile();
+ }
+
+ @Override
+ protected void tearDown() throws Exception
+ {
+ _passwordFile.delete();
+ _accessFile.delete();
+ }
+
+ public void testDeleteUser()
+ {
+ loadFreshTestPasswordFile();
+ loadFreshTestAccessFile();
+
+ //try deleting a non existant user
+ assertFalse(_amqumMBean.deleteUser("made.up.username"));
+
+ assertTrue(_amqumMBean.deleteUser(TEST_USERNAME));
+ }
+
+ public void testDeleteUserIsSavedToAccessFile()
+ {
+ loadFreshTestPasswordFile();
+ loadFreshTestAccessFile();
+
+ assertTrue(_amqumMBean.deleteUser(TEST_USERNAME));
+
+ //check the access rights were actually deleted from the file
+ try{
+ BufferedReader reader = new BufferedReader(new FileReader(_accessFile));
+
+ //check the 'generated by' comment line is present
+ assertTrue("File has no content", reader.ready());
+ assertTrue("'Generated by' comment line was missing",reader.readLine().contains("Generated by " +
+ "AMQUserManagementMBean Console : Last edited by user:"));
+
+ //there should also be a modified date/time comment line
+ assertTrue("File has no modified date/time comment line", reader.ready());
+ assertTrue("Modification date/time comment line was missing",reader.readLine().startsWith("#"));
+
+ //the access file should not contain any further data now as we just deleted the only user
+ assertFalse("User access data was present when it should have been deleted", reader.ready());
+ }
+ catch (IOException e)
+ {
+ fail("Unable to valdate file contents due to:" + e.getMessage());
+ }
+
+ }
+
+ public void testSetRights()
+ {
+ loadFreshTestPasswordFile();
+ loadFreshTestAccessFile();
+
+ assertFalse(_amqumMBean.setRights("made.up.username", true, false, false));
+
+ assertTrue(_amqumMBean.setRights(TEST_USERNAME, true, false, false));
+ assertTrue(_amqumMBean.setRights(TEST_USERNAME, false, true, false));
+ assertTrue(_amqumMBean.setRights(TEST_USERNAME, false, false, true));
+ }
+
+ public void testSetRightsIsSavedToAccessFile()
+ {
+ loadFreshTestPasswordFile();
+ loadFreshTestAccessFile();
+
+ assertTrue(_amqumMBean.setRights(TEST_USERNAME, false, false, true));
+
+ //check the access rights were actually updated in the file
+ try{
+ BufferedReader reader = new BufferedReader(new FileReader(_accessFile));
+
+ //check the 'generated by' comment line is present
+ assertTrue("File has no content", reader.ready());
+ assertTrue("'Generated by' comment line was missing",reader.readLine().contains("Generated by " +
+ "AMQUserManagementMBean Console : Last edited by user:"));
+
+ //there should also be a modified date/time comment line
+ assertTrue("File has no modified date/time comment line", reader.ready());
+ assertTrue("Modification date/time comment line was missing",reader.readLine().startsWith("#"));
+
+ //the access file should not contain any further data now as we just deleted the only user
+ assertTrue("User access data was not updated in the access file",
+ reader.readLine().equals(TEST_USERNAME + "=" + MBeanInvocationHandlerImpl.ADMIN));
+
+ //the access file should not contain any further data now as we just deleted the only user
+ assertFalse("Additional user access data was present when there should be no more", reader.ready());
+ }
+ catch (IOException e)
+ {
+ fail("Unable to valdate file contents due to:" + e.getMessage());
+ }
+ }
+
+ public void testMBeanVersion()
+ {
+ try
+ {
+ ObjectName name = _amqumMBean.getObjectName();
+ assertEquals(AMQUserManagementMBean.VERSION, Integer.parseInt(name.getKeyProperty("version")));
+ }
+ catch (MalformedObjectNameException e)
+ {
+ fail(e.getMessage());
+ }
+ }
+
+ public void testSetAccessFileWithMissingFile()
+ {
+ try
+ {
+ _amqumMBean.setAccessFile("made.up.filename");
+ }
+ catch (IOException e)
+ {
+ fail("Should not have been an IOE." + e.getMessage());
+ }
+ catch (ConfigurationException e)
+ {
+ assertTrue(e.getMessage(), e.getMessage().endsWith("does not exist"));
+ }
+ }
+
+ public void testSetAccessFileWithReadOnlyFile()
+ {
+ File testFile = null;
+ try
+ {
+ testFile = File.createTempFile(this.getClass().getName(),".access.readonly");
+ BufferedWriter passwordWriter = new BufferedWriter(new FileWriter(testFile, false));
+ passwordWriter.write(TEST_USERNAME + ":" + TEST_PASSWORD);
+ passwordWriter.newLine();
+ passwordWriter.flush();
+ passwordWriter.close();
+
+ testFile.setReadOnly();
+ _amqumMBean.setAccessFile(testFile.getPath());
+ }
+ catch (IOException e)
+ {
+ fail("Access file was not created." + e.getMessage());
+ }
+ catch (ConfigurationException e)
+ {
+ fail("There should not have been a configuration exception." + e.getMessage());
+ }
+
+ testFile.delete();
+ }
+
+ // ============================ Utility methods =========================
+
+ private void loadFreshTestPasswordFile()
+ {
+ try
+ {
+ if(_passwordFile == null)
+ {
+ _passwordFile = File.createTempFile(this.getClass().getName(),".password");
+ }
+
+ BufferedWriter passwordWriter = new BufferedWriter(new FileWriter(_passwordFile, false));
+ passwordWriter.write(TEST_USERNAME + ":" + TEST_PASSWORD);
+ passwordWriter.newLine();
+ passwordWriter.flush();
+ passwordWriter.close();
+ _database.setPasswordFile(_passwordFile.toString());
+ _amqumMBean.setPrincipalDatabase(_database);
+ }
+ catch (IOException e)
+ {
+ fail("Unable to create test password file: " + e.getMessage());
+ }
+ }
+
+ private void loadFreshTestAccessFile()
+ {
+ try
+ {
+ if(_accessFile == null)
+ {
+ _accessFile = File.createTempFile(this.getClass().getName(),".access");
+ }
+
+ BufferedWriter accessWriter = new BufferedWriter(new FileWriter(_accessFile,false));
+ accessWriter.write("#Last Updated By comment");
+ accessWriter.newLine();
+ accessWriter.write("#Date/time comment");
+ accessWriter.newLine();
+ accessWriter.write(TEST_USERNAME + "=" + MBeanInvocationHandlerImpl.READONLY);
+ accessWriter.newLine();
+ accessWriter.flush();
+ accessWriter.close();
+ }
+ catch (IOException e)
+ {
+ fail("Unable to create test access file: " + e.getMessage());
+ }
+
+ try{
+ _amqumMBean.setAccessFile(_accessFile.toString());
+ }
+ catch (Exception e)
+ {
+ fail("Unable to set access file: " + e.getMessage());
+ }
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/plugins/network/FirewallPluginTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/plugins/network/FirewallPluginTest.java
new file mode 100644
index 0000000000..ff1fb8c97d
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/plugins/network/FirewallPluginTest.java
@@ -0,0 +1,295 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.security.access.plugins.network;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.protocol.AMQMinaProtocolSession;
+import org.apache.qpid.server.protocol.TestIoSession;
+import org.apache.qpid.server.security.access.ACLPlugin.AuthzResult;
+import org.apache.qpid.server.store.TestableMemoryMessageStore;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+
+public class FirewallPluginTest extends TestCase
+{
+
+ public class RuleInfo
+ {
+ private String _access;
+ private String _network;
+ private String _hostname;
+
+ public void setAccess(String _access)
+ {
+ this._access = _access;
+ }
+
+ public String getAccess()
+ {
+ return _access;
+ }
+
+ public void setNetwork(String _network)
+ {
+ this._network = _network;
+ }
+
+ public String getNetwork()
+ {
+ return _network;
+ }
+
+ public void setHostname(String _hostname)
+ {
+ this._hostname = _hostname;
+ }
+
+ public String getHostname()
+ {
+ return _hostname;
+ }
+ }
+
+ private TestableMemoryMessageStore _store;
+ private VirtualHost _virtualHost;
+ private AMQMinaProtocolSession _session;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ _store = new TestableMemoryMessageStore();
+ PropertiesConfiguration env = new PropertiesConfiguration();
+ _virtualHost = new VirtualHost(new VirtualHostConfiguration("test", env));
+ TestIoSession iosession = new TestIoSession();
+ iosession.setAddress("127.0.0.1");
+ VirtualHostRegistry virtualHostRegistry = null;
+ AMQCodecFactory codecFactory = new AMQCodecFactory(true);
+ _session = new AMQMinaProtocolSession(iosession, virtualHostRegistry, codecFactory);
+ }
+
+ private FirewallPlugin initialisePlugin(String defaultAction, RuleInfo[] rules) throws IOException, ConfigurationException
+ {
+ // Create sample config file
+ File confFile = File.createTempFile(getClass().getSimpleName()+"conffile", null);
+ confFile.deleteOnExit();
+ BufferedWriter buf = new BufferedWriter(new FileWriter(confFile));
+ buf.write("<firewall default-action=\""+defaultAction+"\">\n");
+ if (rules != null)
+ {
+ for (RuleInfo rule : rules)
+ {
+ buf.write("<rule");
+ buf.write(" access=\""+rule.getAccess()+"\"");
+ if (rule.getHostname() != null)
+ {
+ buf.write(" hostname=\""+rule.getHostname()+"\"");
+ }
+ if (rule.getNetwork() != null)
+ {
+ buf.write(" network=\""+rule.getNetwork()+"\"");
+ }
+ buf.write("/>\n");
+ }
+ }
+ buf.write("</firewall>");
+ buf.close();
+
+ // Configure plugin
+ FirewallPlugin plugin = new FirewallPlugin();
+ plugin.setConfiguration(new XMLConfiguration(confFile));
+ return plugin;
+ }
+
+ private FirewallPlugin initialisePlugin(String string) throws ConfigurationException, IOException
+ {
+ return initialisePlugin(string, null);
+ }
+
+ public void testDefaultAction() throws Exception
+ {
+ // Test simple deny
+ FirewallPlugin plugin = initialisePlugin("deny");
+ assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost));
+
+ // Test simple allow
+ plugin = initialisePlugin("allow");
+ assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost));
+ }
+
+
+ public void testSingleIPRule() throws Exception
+ {
+ RuleInfo rule = new RuleInfo();
+ rule.setAccess("allow");
+ rule.setNetwork("192.168.23.23");
+
+ FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{rule});
+
+ assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost));
+
+ // Set session IP so that we're connected from the right address
+ ((TestIoSession) _session.getIOSession()).setAddress("192.168.23.23");
+ assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost));
+ }
+
+ public void testSingleNetworkRule() throws Exception
+ {
+ RuleInfo rule = new RuleInfo();
+ rule.setAccess("allow");
+ rule.setNetwork("192.168.23.0/24");
+
+ FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{rule});
+
+ assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost));
+
+ // Set session IP so that we're connected from the right address
+ ((TestIoSession) _session.getIOSession()).setAddress("192.168.23.23");
+ assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost));
+ }
+
+ public void testSingleHostRule() throws Exception
+ {
+ RuleInfo rule = new RuleInfo();
+ rule.setAccess("allow");
+ rule.setHostname(new InetSocketAddress("127.0.0.1", 5672).getHostName());
+
+ FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{rule});
+
+ // Set session IP so that we're connected from the right address
+ ((TestIoSession) _session.getIOSession()).setAddress("127.0.0.1");
+ assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost));
+ }
+
+ public void testSingleHostWilcardRule() throws Exception
+ {
+ RuleInfo rule = new RuleInfo();
+ rule.setAccess("allow");
+ rule.setHostname(".*ocal.*");
+
+ FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{rule});
+
+ // Set session IP so that we're connected from the right address
+ ((TestIoSession) _session.getIOSession()).setAddress("127.0.0.1");
+ assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost));
+ }
+
+ public void testSeveralFirstAllowsAccess() throws Exception
+ {
+ RuleInfo firstRule = new RuleInfo();
+ firstRule.setAccess("allow");
+ firstRule.setNetwork("192.168.23.23");
+
+ RuleInfo secondRule = new RuleInfo();
+ secondRule.setAccess("deny");
+ secondRule.setNetwork("192.168.42.42");
+
+ RuleInfo thirdRule = new RuleInfo();
+ thirdRule.setAccess("deny");
+ thirdRule.setHostname("localhost");
+
+ FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{firstRule, secondRule, thirdRule});
+
+ assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost));
+
+ // Set session IP so that we're connected from the right address
+ ((TestIoSession) _session.getIOSession()).setAddress("192.168.23.23");
+ assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost));
+ }
+
+ public void testSeveralLastAllowsAccess() throws Exception
+ {
+ RuleInfo firstRule = new RuleInfo();
+ firstRule.setAccess("deny");
+ firstRule.setHostname("localhost");
+
+ RuleInfo secondRule = new RuleInfo();
+ secondRule.setAccess("deny");
+ secondRule.setNetwork("192.168.42.42");
+
+ RuleInfo thirdRule = new RuleInfo();
+ thirdRule.setAccess("allow");
+ thirdRule.setNetwork("192.168.23.23");
+
+ FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{firstRule, secondRule, thirdRule});
+
+ assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost));
+
+ // Set session IP so that we're connected from the right address
+ ((TestIoSession) _session.getIOSession()).setAddress("192.168.23.23");
+ assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost));
+ }
+
+ public void testNetmask() throws Exception
+ {
+ RuleInfo firstRule = new RuleInfo();
+ firstRule.setAccess("allow");
+ firstRule.setNetwork("192.168.23.0/24");
+ FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{firstRule});
+
+ assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost));
+
+ // Set session IP so that we're connected from the right address
+ ((TestIoSession) _session.getIOSession()).setAddress("192.168.23.23");
+ assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost));
+ }
+
+ public void testCommaSeperatedNetmask() throws Exception
+ {
+ RuleInfo firstRule = new RuleInfo();
+ firstRule.setAccess("allow");
+ firstRule.setNetwork("10.1.1.1/8, 192.168.23.0/24");
+ FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{firstRule});
+
+ assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost));
+
+ // Set session IP so that we're connected from the right address
+ ((TestIoSession) _session.getIOSession()).setAddress("192.168.23.23");
+ assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost));
+ }
+
+ public void testCommaSeperatedHostnames() throws Exception
+ {
+ RuleInfo firstRule = new RuleInfo();
+ firstRule.setAccess("allow");
+ firstRule.setHostname("foo, bar, "+new InetSocketAddress("127.0.0.1", 5672).getHostName());
+ FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{firstRule});
+ ((TestIoSession) _session.getIOSession()).setAddress("10.0.0.1");
+ assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost));
+
+ // Set session IP so that we're connected from the right address
+ ((TestIoSession) _session.getIOSession()).setAddress("127.0.0.1");
+ assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost));
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java
new file mode 100644
index 0000000000..413b974986
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java
@@ -0,0 +1,447 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import junit.framework.TestCase;
+
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.AccountNotFoundException;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class Base64MD5PasswordFilePrincipalDatabaseTest extends TestCase
+{
+
+ private static final String TEST_COMMENT = "# Test Comment";
+
+ private static final String USERNAME = "testUser";
+ private static final String PASSWORD = "guest";
+ private static final String PASSWORD_B64MD5HASHED = "CE4DQ6BIb/BVMN9scFyLtA==";
+ private static char[] PASSWORD_MD5_CHARS;
+ private static final String PRINCIPAL_USERNAME = "testUserPrincipal";
+ private static final Principal PRINCIPAL = new UsernamePrincipal(PRINCIPAL_USERNAME);
+ private Base64MD5PasswordFilePrincipalDatabase _database;
+ private File _pwdFile;
+
+ static
+ {
+ try
+ {
+ Base64 b64 = new Base64();
+ byte[] md5passBytes = PASSWORD_B64MD5HASHED.getBytes(Base64MD5PasswordFilePrincipalDatabase.DEFAULT_ENCODING);
+ byte[] decoded = b64.decode(md5passBytes);
+
+ PASSWORD_MD5_CHARS = new char[decoded.length];
+
+ int index = 0;
+ for (byte c : decoded)
+ {
+ PASSWORD_MD5_CHARS[index++] = (char) c;
+ }
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ fail("Unable to perform B64 decode to get the md5 char[] password");
+ }
+ }
+
+
+ public void setUp() throws Exception
+ {
+ _database = new Base64MD5PasswordFilePrincipalDatabase();
+ _pwdFile = File.createTempFile(this.getClass().getName(), "pwd");
+ _pwdFile.deleteOnExit();
+ _database.setPasswordFile(_pwdFile.getAbsolutePath());
+ }
+
+ private File createPasswordFile(int commentLines, int users)
+ {
+ try
+ {
+ File testFile = File.createTempFile("Base64MD5PDPDTest","tmp");
+ testFile.deleteOnExit();
+
+ BufferedWriter writer = new BufferedWriter(new FileWriter(testFile));
+
+ for (int i = 0; i < commentLines; i++)
+ {
+ writer.write(TEST_COMMENT);
+ writer.newLine();
+ }
+
+ for (int i = 0; i < users; i++)
+ {
+ writer.write(USERNAME + i + ":Password");
+ writer.newLine();
+ }
+
+ writer.flush();
+ writer.close();
+
+ return testFile;
+
+ }
+ catch (IOException e)
+ {
+ fail("Unable to create test password file." + e.getMessage());
+ }
+
+ return null;
+ }
+
+ private void loadPasswordFile(File file)
+ {
+ try
+ {
+ _database.setPasswordFile(file.toString());
+ }
+ catch (IOException e)
+ {
+ fail("Password File was not created." + e.getMessage());
+ }
+ }
+
+ /** **** Test Methods ************** */
+
+ public void testCreatePrincipal()
+ {
+ File testFile = createPasswordFile(1, 0);
+
+ loadPasswordFile(testFile);
+
+
+ Principal principal = new Principal()
+ {
+ public String getName()
+ {
+ return USERNAME;
+ }
+ };
+
+ assertTrue("New user not created.", _database.createPrincipal(principal, PASSWORD.toCharArray()));
+
+ PasswordCallback callback = new PasswordCallback("prompt",false);
+ try
+ {
+ _database.setPassword(principal, callback);
+ }
+ catch (AccountNotFoundException e)
+ {
+ fail("user account did not exist");
+ }
+ assertTrue("Password returned was incorrect.", Arrays.equals(PASSWORD_MD5_CHARS, callback.getPassword()));
+
+ loadPasswordFile(testFile);
+
+ try
+ {
+ _database.setPassword(principal, callback);
+ }
+ catch (AccountNotFoundException e)
+ {
+ fail("user account did not exist");
+ }
+ assertTrue("Password returned was incorrect.", Arrays.equals(PASSWORD_MD5_CHARS, callback.getPassword()));
+
+ assertNotNull("Created User was not saved", _database.getUser(USERNAME));
+
+ assertFalse("Duplicate user created.", _database.createPrincipal(principal, PASSWORD.toCharArray()));
+
+ testFile.delete();
+ }
+
+ public void testCreatePrincipalIsSavedToFile()
+ {
+
+ File testFile = createPasswordFile(1, 0);
+
+ loadPasswordFile(testFile);
+
+ final String CREATED_PASSWORD = "guest";
+ final String CREATED_B64MD5HASHED_PASSWORD = "CE4DQ6BIb/BVMN9scFyLtA==";
+ final String CREATED_USERNAME = "createdUser";
+
+ Principal principal = new Principal()
+ {
+ public String getName()
+ {
+ return CREATED_USERNAME;
+ }
+ };
+
+ _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray());
+
+ try
+ {
+ BufferedReader reader = new BufferedReader(new FileReader(testFile));
+
+ assertTrue("File has no content", reader.ready());
+
+ assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine());
+
+ assertTrue("File is missing user data.", reader.ready());
+
+ String userLine = reader.readLine();
+
+ String[] result = Pattern.compile(":").split(userLine);
+
+ assertEquals("User line not complete '" + userLine + "'", 2, result.length);
+
+ assertEquals("Username not correct,", CREATED_USERNAME, result[0]);
+ assertEquals("Password not correct,", CREATED_B64MD5HASHED_PASSWORD, result[1]);
+
+ assertFalse("File has more content", reader.ready());
+
+ }
+ catch (IOException e)
+ {
+ fail("Unable to valdate file contents due to:" + e.getMessage());
+ }
+ testFile.delete();
+ }
+
+
+ public void testDeletePrincipal()
+ {
+ File testFile = createPasswordFile(1, 1);
+
+ loadPasswordFile(testFile);
+
+ Principal user = _database.getUser(USERNAME + "0");
+ assertNotNull("Generated user not present.", user);
+
+ try
+ {
+ _database.deletePrincipal(user);
+ }
+ catch (AccountNotFoundException e)
+ {
+ fail("User should be present" + e.getMessage());
+ }
+
+ try
+ {
+ _database.deletePrincipal(user);
+ fail("User should not be present");
+ }
+ catch (AccountNotFoundException e)
+ {
+ //pass
+ }
+
+ loadPasswordFile(testFile);
+
+ try
+ {
+ _database.deletePrincipal(user);
+ fail("User should not be present");
+ }
+ catch (AccountNotFoundException e)
+ {
+ //pass
+ }
+
+ assertNull("Deleted user still present.", _database.getUser(USERNAME + "0"));
+
+ testFile.delete();
+ }
+
+ public void testGetUsers()
+ {
+ int USER_COUNT = 10;
+ File testFile = createPasswordFile(1, USER_COUNT);
+
+ loadPasswordFile(testFile);
+
+ Principal user = _database.getUser("MISSING_USERNAME");
+ assertNull("Missing user present.", user);
+
+ List<Principal> users = _database.getUsers();
+
+ assertNotNull("Users list is null.", users);
+
+ assertEquals(USER_COUNT, users.size());
+
+ boolean[] verify = new boolean[USER_COUNT];
+ for (int i = 0; i < USER_COUNT; i++)
+ {
+ Principal principal = users.get(i);
+
+ assertNotNull("Generated user not present.", principal);
+
+ String name = principal.getName();
+
+ int id = Integer.parseInt(name.substring(USERNAME.length()));
+
+ assertFalse("Duplicated username retrieve", verify[id]);
+ verify[id] = true;
+ }
+
+ for (int i = 0; i < USER_COUNT; i++)
+ {
+ assertTrue("User " + i + " missing", verify[i]);
+ }
+
+ testFile.delete();
+ }
+
+ public void testUpdatePasswordIsSavedToFile()
+ {
+
+ File testFile = createPasswordFile(1, 1);
+
+ loadPasswordFile(testFile);
+
+ Principal testUser = _database.getUser(USERNAME + "0");
+
+ assertNotNull(testUser);
+
+ String NEW_PASSWORD = "guest";
+ String NEW_PASSWORD_HASH = "CE4DQ6BIb/BVMN9scFyLtA==";
+ try
+ {
+ _database.updatePassword(testUser, NEW_PASSWORD.toCharArray());
+ }
+ catch (AccountNotFoundException e)
+ {
+ fail(e.toString());
+ }
+
+ try
+ {
+ BufferedReader reader = new BufferedReader(new FileReader(testFile));
+
+ assertTrue("File has no content", reader.ready());
+
+ assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine());
+
+ assertTrue("File is missing user data.", reader.ready());
+
+ String userLine = reader.readLine();
+
+ String[] result = Pattern.compile(":").split(userLine);
+
+ assertEquals("User line not complete '" + userLine + "'", 2, result.length);
+
+ assertEquals("Username not correct,", USERNAME + "0", result[0]);
+ assertEquals("New Password not correct,", NEW_PASSWORD_HASH, result[1]);
+
+ assertFalse("File has more content", reader.ready());
+
+ }
+ catch (IOException e)
+ {
+ fail("Unable to valdate file contents due to:" + e.getMessage());
+ }
+ testFile.delete();
+ }
+
+ public void testSetPasswordFileWithMissingFile()
+ {
+ try
+ {
+ _database.setPasswordFile("DoesntExist");
+ }
+ catch (FileNotFoundException fnfe)
+ {
+ assertTrue(fnfe.getMessage(), fnfe.getMessage().startsWith("Cannot find password file"));
+ }
+ catch (IOException e)
+ {
+ fail("Password File was not created." + e.getMessage());
+ }
+
+ }
+
+ public void testSetPasswordFileWithReadOnlyFile()
+ {
+
+ File testFile = createPasswordFile(0, 0);
+
+ testFile.setReadOnly();
+
+ try
+ {
+ _database.setPasswordFile(testFile.toString());
+ }
+ catch (FileNotFoundException fnfe)
+ {
+ assertTrue(fnfe.getMessage().startsWith("Cannot read password file "));
+ }
+ catch (IOException e)
+ {
+ fail("Password File was not created." + e.getMessage());
+ }
+
+ testFile.delete();
+ }
+
+ public void testCreateUserPrincipal() throws IOException
+ {
+ _database.createPrincipal(PRINCIPAL, PASSWORD.toCharArray());
+ Principal newPrincipal = _database.getUser(PRINCIPAL_USERNAME);
+ assertNotNull(newPrincipal);
+ assertEquals(PRINCIPAL.getName(), newPrincipal.getName());
+ }
+
+ public void testVerifyPassword() throws IOException, AccountNotFoundException
+ {
+ testCreateUserPrincipal();
+ //assertFalse(_pwdDB.verifyPassword(_username, null));
+ assertFalse(_database.verifyPassword(PRINCIPAL_USERNAME, new char[]{}));
+ assertFalse(_database.verifyPassword(PRINCIPAL_USERNAME, (PASSWORD+"z").toCharArray()));
+ assertTrue(_database.verifyPassword(PRINCIPAL_USERNAME, PASSWORD.toCharArray()));
+
+ try
+ {
+ _database.verifyPassword("made.up.username", PASSWORD.toCharArray());
+ fail("Should not have been able to verify this non-existant users password.");
+ }
+ catch (AccountNotFoundException e)
+ {
+ // pass
+ }
+ }
+
+ public void testUpdatePassword() throws IOException, AccountNotFoundException
+ {
+ testCreateUserPrincipal();
+ char[] newPwd = "newpassword".toCharArray();
+ _database.updatePassword(PRINCIPAL, newPwd);
+ assertFalse(_database.verifyPassword(PRINCIPAL_USERNAME, PASSWORD.toCharArray()));
+ assertTrue(_database.verifyPassword(PRINCIPAL_USERNAME, newPwd));
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java
new file mode 100644
index 0000000000..aa85cac758
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java
@@ -0,0 +1,95 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import junit.framework.TestCase;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+
+import java.io.UnsupportedEncodingException;
+
+/*
+ Note User is mainly tested by Base64MD5PFPDTest this is just to catch the extra methods
+ */
+public class HashedUserTest extends TestCase
+{
+
+ String USERNAME = "username";
+ String PASSWORD = "password";
+ String B64_ENCODED_PASSWORD = "cGFzc3dvcmQ=";
+
+ public void testToLongArrayConstructor()
+ {
+ try
+ {
+ HashedUser user = new HashedUser(new String[]{USERNAME, PASSWORD, USERNAME});
+ fail("Error expected");
+ }
+ catch (IllegalArgumentException e)
+ {
+ assertEquals("User Data should be length 2, username, password", e.getMessage());
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ fail(e.getMessage());
+ }
+ }
+
+ public void testArrayConstructor()
+ {
+ try
+ {
+ HashedUser user = new HashedUser(new String[]{USERNAME, B64_ENCODED_PASSWORD});
+ assertEquals("Username incorrect", USERNAME, user.getName());
+ int index = 0;
+
+ char[] hash = B64_ENCODED_PASSWORD.toCharArray();
+
+ try
+ {
+ for (byte c : user.getEncodedPassword())
+ {
+ assertEquals("Password incorrect", hash[index], (char) c);
+ index++;
+ }
+ }
+ catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+
+ hash = PASSWORD.toCharArray();
+
+ index=0;
+ for (char c : user.getPassword())
+ {
+ assertEquals("Password incorrect", hash[index], c);
+ index++;
+ }
+
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ fail(e.getMessage());
+ }
+ }
+}
+
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java
new file mode 100644
index 0000000000..20b8d0a7b4
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java
@@ -0,0 +1,396 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import junit.framework.TestCase;
+
+import javax.security.auth.login.AccountNotFoundException;
+
+import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class PlainPasswordFilePrincipalDatabaseTest extends TestCase
+{
+
+ private static final String TEST_COMMENT = "# Test Comment";
+ private static final String TEST_PASSWORD = "testPassword";
+ private static final char[] TEST_PASSWORD_CHARS = TEST_PASSWORD.toCharArray();
+ private static final String TEST_USERNAME = "testUser";
+
+ private Principal _principal = new UsernamePrincipal(TEST_USERNAME);
+ private PlainPasswordFilePrincipalDatabase _database;
+
+ public void setUp() throws Exception
+ {
+ _database = new PlainPasswordFilePrincipalDatabase();
+ }
+
+ // ******* Test Methods ********** //
+
+ public void testCreatePrincipal()
+ {
+ File testFile = createPasswordFile(1, 0);
+
+ loadPasswordFile(testFile);
+
+ final String CREATED_PASSWORD = "guest";
+ final String CREATED_USERNAME = "createdUser";
+
+ Principal principal = new Principal()
+ {
+ public String getName()
+ {
+ return CREATED_USERNAME;
+ }
+ };
+
+ assertTrue("New user not created.", _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray()));
+
+ loadPasswordFile(testFile);
+
+ assertNotNull("Created User was not saved", _database.getUser(CREATED_USERNAME));
+
+ assertFalse("Duplicate user created.", _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray()));
+
+ testFile.delete();
+ }
+
+ public void testCreatePrincipalIsSavedToFile()
+ {
+
+ File testFile = createPasswordFile(1, 0);
+
+ loadPasswordFile(testFile);
+
+ Principal principal = new Principal()
+ {
+ public String getName()
+ {
+ return TEST_USERNAME;
+ }
+ };
+
+ _database.createPrincipal(principal, TEST_PASSWORD_CHARS);
+
+ try
+ {
+ BufferedReader reader = new BufferedReader(new FileReader(testFile));
+
+ assertTrue("File has no content", reader.ready());
+
+ assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine());
+
+ assertTrue("File is missing user data.", reader.ready());
+
+ String userLine = reader.readLine();
+
+ String[] result = Pattern.compile(":").split(userLine);
+
+ assertEquals("User line not complete '" + userLine + "'", 2, result.length);
+
+ assertEquals("Username not correct,", TEST_USERNAME, result[0]);
+ assertEquals("Password not correct,", TEST_PASSWORD, result[1]);
+
+ assertFalse("File has more content", reader.ready());
+
+ }
+ catch (IOException e)
+ {
+ fail("Unable to valdate file contents due to:" + e.getMessage());
+ }
+ testFile.delete();
+ }
+
+ public void testDeletePrincipal()
+ {
+ File testFile = createPasswordFile(1, 1);
+
+ loadPasswordFile(testFile);
+
+ Principal user = _database.getUser(TEST_USERNAME + "0");
+ assertNotNull("Generated user not present.", user);
+
+ try
+ {
+ _database.deletePrincipal(user);
+ }
+ catch (AccountNotFoundException e)
+ {
+ fail("User should be present" + e.getMessage());
+ }
+
+ try
+ {
+ _database.deletePrincipal(user);
+ fail("User should not be present");
+ }
+ catch (AccountNotFoundException e)
+ {
+ //pass
+ }
+
+ loadPasswordFile(testFile);
+
+ try
+ {
+ _database.deletePrincipal(user);
+ fail("User should not be present");
+ }
+ catch (AccountNotFoundException e)
+ {
+ //pass
+ }
+
+ assertNull("Deleted user still present.", _database.getUser(TEST_USERNAME + "0"));
+
+ testFile.delete();
+ }
+
+ public void testGetUsers()
+ {
+ int USER_COUNT = 10;
+ File testFile = createPasswordFile(1, USER_COUNT);
+
+ loadPasswordFile(testFile);
+
+ Principal user = _database.getUser("MISSING_USERNAME");
+ assertNull("Missing user present.", user);
+
+ List<Principal> users = _database.getUsers();
+
+ assertNotNull("Users list is null.", users);
+
+ assertEquals(USER_COUNT, users.size());
+
+ boolean[] verify = new boolean[USER_COUNT];
+ for (int i = 0; i < USER_COUNT; i++)
+ {
+ Principal principal = users.get(i);
+
+ assertNotNull("Generated user not present.", principal);
+
+ String name = principal.getName();
+
+ int id = Integer.parseInt(name.substring(TEST_USERNAME.length()));
+
+ assertFalse("Duplicated username retrieve", verify[id]);
+ verify[id] = true;
+ }
+
+ for (int i = 0; i < USER_COUNT; i++)
+ {
+ assertTrue("User " + i + " missing", verify[i]);
+ }
+
+ testFile.delete();
+ }
+
+ public void testUpdatePasswordIsSavedToFile()
+ {
+
+ File testFile = createPasswordFile(1, 1);
+
+ loadPasswordFile(testFile);
+
+ Principal testUser = _database.getUser(TEST_USERNAME + "0");
+
+ assertNotNull(testUser);
+
+ String NEW_PASSWORD = "NewPassword";
+ try
+ {
+ _database.updatePassword(testUser, NEW_PASSWORD.toCharArray());
+ }
+ catch (AccountNotFoundException e)
+ {
+ fail(e.toString());
+ }
+
+ try
+ {
+ BufferedReader reader = new BufferedReader(new FileReader(testFile));
+
+ assertTrue("File has no content", reader.ready());
+
+ assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine());
+
+ assertTrue("File is missing user data.", reader.ready());
+
+ String userLine = reader.readLine();
+
+ String[] result = Pattern.compile(":").split(userLine);
+
+ assertEquals("User line not complete '" + userLine + "'", 2, result.length);
+
+ assertEquals("Username not correct,", TEST_USERNAME + "0", result[0]);
+ assertEquals("New Password not correct,", NEW_PASSWORD, result[1]);
+
+ assertFalse("File has more content", reader.ready());
+
+ }
+ catch (IOException e)
+ {
+ fail("Unable to valdate file contents due to:" + e.getMessage());
+ }
+ testFile.delete();
+ }
+
+ public void testSetPasswordFileWithMissingFile()
+ {
+ try
+ {
+ _database.setPasswordFile("DoesntExist");
+ }
+ catch (FileNotFoundException fnfe)
+ {
+ assertTrue(fnfe.getMessage(), fnfe.getMessage().startsWith("Cannot find password file"));
+ }
+ catch (IOException e)
+ {
+ fail("Password File was not created." + e.getMessage());
+ }
+
+ }
+
+ public void testSetPasswordFileWithReadOnlyFile()
+ {
+
+ File testFile = createPasswordFile(0, 0);
+
+ testFile.setReadOnly();
+
+ try
+ {
+ _database.setPasswordFile(testFile.toString());
+ }
+ catch (FileNotFoundException fnfe)
+ {
+ assertTrue(fnfe.getMessage().startsWith("Cannot read password file "));
+ }
+ catch (IOException e)
+ {
+ fail("Password File was not created." + e.getMessage());
+ }
+
+ testFile.delete();
+ }
+
+ private void createUserPrincipal() throws IOException
+ {
+ File testFile = createPasswordFile(0, 0);
+ loadPasswordFile(testFile);
+
+ _database.createPrincipal(_principal, TEST_PASSWORD_CHARS);
+ Principal newPrincipal = _database.getUser(TEST_USERNAME);
+ assertNotNull(newPrincipal);
+ assertEquals(_principal.getName(), newPrincipal.getName());
+ }
+
+ public void testVerifyPassword() throws IOException, AccountNotFoundException
+ {
+ createUserPrincipal();
+ assertFalse(_database.verifyPassword(TEST_USERNAME, new char[]{}));
+ assertFalse(_database.verifyPassword(TEST_USERNAME, "massword".toCharArray()));
+ assertTrue(_database.verifyPassword(TEST_USERNAME, TEST_PASSWORD_CHARS));
+
+ try
+ {
+ _database.verifyPassword("made.up.username", TEST_PASSWORD_CHARS);
+ fail("Should not have been able to verify this non-existant users password.");
+ }
+ catch (AccountNotFoundException e)
+ {
+ // pass
+ }
+ }
+
+ public void testUpdatePassword() throws IOException, AccountNotFoundException
+ {
+ createUserPrincipal();
+ char[] newPwd = "newpassword".toCharArray();
+ _database.updatePassword(_principal, newPwd);
+ assertFalse(_database.verifyPassword(TEST_USERNAME, TEST_PASSWORD_CHARS));
+ assertTrue(_database.verifyPassword(TEST_USERNAME, newPwd));
+ }
+
+
+
+ // *********** Utility Methods ******** //
+
+ private File createPasswordFile(int commentLines, int users)
+ {
+ try
+ {
+ File testFile = File.createTempFile(this.getClass().getName(),"tmp");
+ testFile.deleteOnExit();
+
+ BufferedWriter writer = new BufferedWriter(new FileWriter(testFile));
+
+ for (int i = 0; i < commentLines; i++)
+ {
+ writer.write(TEST_COMMENT);
+ writer.newLine();
+ }
+
+ for (int i = 0; i < users; i++)
+ {
+ writer.write(TEST_USERNAME + i + ":" + TEST_PASSWORD);
+ writer.newLine();
+ }
+
+ writer.flush();
+ writer.close();
+
+ return testFile;
+
+ }
+ catch (IOException e)
+ {
+ fail("Unable to create test password file." + e.getMessage());
+ }
+
+ return null;
+ }
+
+ private void loadPasswordFile(File file)
+ {
+ try
+ {
+ _database.setPasswordFile(file.toString());
+ }
+ catch (IOException e)
+ {
+ fail("Password File was not created." + e.getMessage());
+ }
+ }
+
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java
new file mode 100644
index 0000000000..7f0843d46e
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java
@@ -0,0 +1,78 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import junit.framework.TestCase;
+
+/*
+ Note PlainUser is mainly tested by PlainPFPDTest, this is just to catch the extra methods
+ */
+public class PlainUserTest extends TestCase
+{
+
+ String USERNAME = "username";
+ String PASSWORD = "password";
+
+ public void testTooLongArrayConstructor()
+ {
+ try
+ {
+ PlainUser user = new PlainUser(new String[]{USERNAME, PASSWORD, USERNAME});
+ fail("Error expected");
+ }
+ catch (IllegalArgumentException e)
+ {
+ assertEquals("User Data should be length 2, username, password", e.getMessage());
+ }
+ }
+
+ public void testStringArrayConstructor()
+ {
+ PlainUser user = new PlainUser(new String[]{USERNAME, PASSWORD});
+ assertEquals("Username incorrect", USERNAME, user.getName());
+ int index = 0;
+
+ char[] password = PASSWORD.toCharArray();
+
+ try
+ {
+ for (byte c : user.getPasswordBytes())
+ {
+ assertEquals("Password incorrect", password[index], (char) c);
+ index++;
+ }
+ }
+ catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+
+ password = PASSWORD.toCharArray();
+
+ index=0;
+ for (char c : user.getPassword())
+ {
+ assertEquals("Password incorrect", password[index], c);
+ index++;
+ }
+ }
+}
+
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java
new file mode 100644
index 0000000000..e8c24da68d
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java
@@ -0,0 +1,267 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.rmi;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Collections;
+
+import javax.management.remote.JMXPrincipal;
+import javax.security.auth.Subject;
+
+import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase;
+import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase;
+
+import junit.framework.TestCase;
+
+public class RMIPasswordAuthenticatorTest extends TestCase
+{
+ private final String USERNAME = "guest";
+ private final String PASSWORD = "guest";
+ private final String B64_MD5HASHED_PASSWORD = "CE4DQ6BIb/BVMN9scFyLtA==";
+ private RMIPasswordAuthenticator _rmipa;
+
+ private Base64MD5PasswordFilePrincipalDatabase _md5Pd;
+ private File _md5PwdFile;
+
+ private PlainPasswordFilePrincipalDatabase _plainPd;
+ private File _plainPwdFile;
+
+ private Subject testSubject;
+
+ protected void setUp() throws Exception
+ {
+ _rmipa = new RMIPasswordAuthenticator();
+
+ _md5Pd = new Base64MD5PasswordFilePrincipalDatabase();
+ _md5PwdFile = createTempPasswordFile(this.getClass().getName()+"md5pwd", USERNAME, B64_MD5HASHED_PASSWORD);
+ _md5Pd.setPasswordFile(_md5PwdFile.getAbsolutePath());
+
+ _plainPd = new PlainPasswordFilePrincipalDatabase();
+ _plainPwdFile = createTempPasswordFile(this.getClass().getName()+"plainpwd", USERNAME, PASSWORD);
+ _plainPd.setPasswordFile(_plainPwdFile.getAbsolutePath());
+
+ testSubject = new Subject(true,
+ Collections.singleton(new JMXPrincipal(USERNAME)),
+ Collections.EMPTY_SET,
+ Collections.EMPTY_SET);
+ }
+
+ private File createTempPasswordFile(String filenamePrefix, String user, String password)
+ {
+ try
+ {
+ File testFile = File.createTempFile(filenamePrefix,"tmp");
+ testFile.deleteOnExit();
+
+ BufferedWriter writer = new BufferedWriter(new FileWriter(testFile));
+
+ writer.write(user + ":" + password);
+ writer.newLine();
+
+ writer.flush();
+ writer.close();
+
+ return testFile;
+ }
+ catch (IOException e)
+ {
+ fail("Unable to create temporary test password file." + e.getMessage());
+ }
+
+ return null;
+ }
+
+
+ //********** Test Methods *********//
+
+
+ public void testAuthenticate()
+ {
+ String[] credentials;
+ Subject newSubject;
+
+ // Test when no PD has been set
+ try
+ {
+ credentials = new String[]{USERNAME, PASSWORD};
+ newSubject = _rmipa.authenticate(credentials);
+ fail("SecurityException expected due to lack of principal database");
+ }
+ catch (SecurityException se)
+ {
+ assertEquals("Unexpected exception message",
+ RMIPasswordAuthenticator.UNABLE_TO_LOOKUP, se.getMessage());
+ }
+
+ //The PrincipalDatabase's are tested primarily by their own tests, but
+ //minimal tests are done here to exercise their usage in this area.
+
+ // Test correct passwords are verified with an MD5 PD
+ try
+ {
+ _rmipa.setPrincipalDatabase(_md5Pd);
+ credentials = new String[]{USERNAME, PASSWORD};
+ newSubject = _rmipa.authenticate(credentials);
+ assertTrue("Returned subject does not equal expected value",
+ newSubject.equals(testSubject));
+ }
+ catch (Exception e)
+ {
+ fail("Unexpected Exception:" + e.getMessage());
+ }
+
+ // Test incorrect passwords are not verified with an MD5 PD
+ try
+ {
+ credentials = new String[]{USERNAME, PASSWORD+"incorrect"};
+ newSubject = _rmipa.authenticate(credentials);
+ fail("SecurityException expected due to incorrect password");
+ }
+ catch (SecurityException se)
+ {
+ assertEquals("Unexpected exception message",
+ RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage());
+ }
+
+ // Test non-existent accounts are not verified with an MD5 PD
+ try
+ {
+ credentials = new String[]{USERNAME+"invalid", PASSWORD};
+ newSubject = _rmipa.authenticate(credentials);
+ fail("SecurityException expected due to non-existant account");
+ }
+ catch (SecurityException se)
+ {
+ assertEquals("Unexpected exception message",
+ RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage());
+ }
+
+ // Test correct passwords are verified with a Plain PD
+ try
+ {
+ _rmipa.setPrincipalDatabase(_plainPd);
+ credentials = new String[]{USERNAME, PASSWORD};
+ newSubject = _rmipa.authenticate(credentials);
+ assertTrue("Returned subject does not equal expected value",
+ newSubject.equals(testSubject));
+ }
+ catch (Exception e)
+ {
+ fail("Unexpected Exception");
+ }
+
+ // Test incorrect passwords are not verified with a Plain PD
+ try
+ {
+ credentials = new String[]{USERNAME, PASSWORD+"incorrect"};
+ newSubject = _rmipa.authenticate(credentials);
+ fail("SecurityException expected due to incorrect password");
+ }
+ catch (SecurityException se)
+ {
+ assertEquals("Unexpected exception message",
+ RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage());
+ }
+
+ // Test non-existent accounts are not verified with an Plain PD
+ try
+ {
+ credentials = new String[]{USERNAME+"invalid", PASSWORD};
+ newSubject = _rmipa.authenticate(credentials);
+ fail("SecurityException expected due to non existant account");
+ }
+ catch (SecurityException se)
+ {
+ assertEquals("Unexpected exception message",
+ RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage());
+ }
+
+ // Test handling of non-string credential's
+ try
+ {
+ Object[] objCredentials = new Object[]{USERNAME, PASSWORD};
+ newSubject = _rmipa.authenticate(objCredentials);
+ fail("SecurityException expected due to non string[] credentials");
+ }
+ catch (SecurityException se)
+ {
+ assertEquals("Unexpected exception message",
+ RMIPasswordAuthenticator.SHOULD_BE_STRING_ARRAY, se.getMessage());
+ }
+
+ // Test handling of incorrect number of credential's
+ try
+ {
+ credentials = new String[]{USERNAME, PASSWORD, PASSWORD};
+ newSubject = _rmipa.authenticate(credentials);
+ fail("SecurityException expected due to supplying wrong number of credentials");
+ }
+ catch (SecurityException se)
+ {
+ assertEquals("Unexpected exception message",
+ RMIPasswordAuthenticator.SHOULD_HAVE_2_ELEMENTS, se.getMessage());
+ }
+
+ // Test handling of null credential's
+ try
+ {
+ //send a null array
+ credentials = null;
+ newSubject = _rmipa.authenticate(credentials);
+ fail("SecurityException expected due to not supplying an array of credentials");
+ }
+ catch (SecurityException se)
+ {
+ assertEquals("Unexpected exception message",
+ RMIPasswordAuthenticator.CREDENTIALS_REQUIRED, se.getMessage());
+ }
+
+ try
+ {
+ //send a null password
+ credentials = new String[]{USERNAME, null};
+ newSubject = _rmipa.authenticate(credentials);
+ fail("SecurityException expected due to sending a null password");
+ }
+ catch (SecurityException se)
+ {
+ assertEquals("Unexpected exception message",
+ RMIPasswordAuthenticator.SHOULD_BE_NON_NULL, se.getMessage());
+ }
+
+ try
+ {
+ //send a null username
+ credentials = new String[]{null, PASSWORD};
+ newSubject = _rmipa.authenticate(credentials);
+ fail("SecurityException expected due to sending a null username");
+ }
+ catch (SecurityException se)
+ {
+ assertEquals("Unexpected exception message",
+ RMIPasswordAuthenticator.SHOULD_BE_NON_NULL, se.getMessage());
+ }
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java
new file mode 100644
index 0000000000..f80413d4f8
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.security.auth.sasl;
+
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+
+import junit.framework.TestCase;
+
+public abstract class SaslServerTestCase extends TestCase
+{
+ protected SaslServer server;
+ protected String username = "u";
+ protected String password = "p";
+ protected String notpassword = "a";
+ protected PrincipalDatabase db = new TestPrincipalDatabase();
+
+ protected byte[] correctresponse;
+ protected byte[] wrongresponse;
+
+ public void testSucessfulAuth() throws SaslException
+ {
+ byte[] resp = this.server.evaluateResponse(correctresponse);
+ assertNull(resp);
+ }
+
+ public void testFailAuth()
+ {
+ boolean exceptionCaught = false;
+ try
+ {
+ byte[] resp = this.server.evaluateResponse(wrongresponse);
+ }
+ catch (SaslException e)
+ {
+ assertEquals("Authentication failed", e.getCause().getMessage());
+ exceptionCaught = true;
+ }
+ if (!exceptionCaught)
+ {
+ fail("Should have thrown SaslException");
+ }
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java
new file mode 100644
index 0000000000..8507e49e17
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java
@@ -0,0 +1,91 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.security.auth.sasl;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.AccountNotFoundException;
+
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser;
+
+public class TestPrincipalDatabase implements PrincipalDatabase
+{
+
+ public boolean createPrincipal(Principal principal, char[] password)
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public boolean deletePrincipal(Principal principal) throws AccountNotFoundException
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public Map<String, AuthenticationProviderInitialiser> getMechanisms()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public Principal getUser(String username)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List<Principal> getUsers()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public void setPassword(Principal principal, PasswordCallback callback) throws IOException,
+ AccountNotFoundException
+ {
+ callback.setPassword("p".toCharArray());
+ }
+
+ public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public void reload() throws IOException
+ {
+ // TODO Auto-generated method stub
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java
new file mode 100644
index 0000000000..6245064bf7
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.security.auth.sasl.amqplain;
+
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.FieldTableFactory;
+import org.apache.qpid.server.security.auth.sasl.SaslServerTestCase;
+import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser;
+
+public class AMQPlainSaslServerTest extends SaslServerTestCase
+{
+ protected void setUp() throws Exception
+ {
+ UsernamePasswordInitialiser handler = new AmqPlainInitialiser();
+ handler.initialise(db);
+ this.server = new AmqPlainSaslServer(handler.getCallbackHandler());
+ FieldTable table = FieldTableFactory.newFieldTable();
+ table.setString("LOGIN", username);
+ table.setString("PASSWORD", password);
+ correctresponse = table.getDataAsBytes();
+ table.setString("PASSWORD", notpassword);
+ wrongresponse = table.getDataAsBytes();
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java
new file mode 100644
index 0000000000..5dd51250dc
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java
@@ -0,0 +1,39 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.security.auth.sasl.plain;
+
+import org.apache.qpid.server.security.auth.sasl.SaslServerTestCase;
+import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser;
+
+public class PlainSaslServerTest extends SaslServerTestCase
+{
+
+ protected void setUp() throws Exception
+ {
+ UsernamePasswordInitialiser handler = new PlainInitialiser();
+ handler.initialise(db);
+ this.server = new PlainSaslServer(handler.getCallbackHandler());
+ correctresponse = new byte[]{0x0, (byte) username.charAt(0), 0x0, (byte) password.charAt(0)};
+ wrongresponse = new byte[]{0x0,(byte) username.charAt(0), 0x0, (byte) notpassword.charAt(0)};
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreShutdownTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreShutdownTest.java
new file mode 100644
index 0000000000..a695a67eea
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreShutdownTest.java
@@ -0,0 +1,81 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.store;
+
+import org.apache.qpid.server.util.InternalBrokerBaseCase;
+import org.apache.qpid.server.protocol.InternalTestProtocolSession;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+
+import java.util.List;
+
+public class MessageStoreShutdownTest extends InternalBrokerBaseCase
+{
+
+ public void test()
+ {
+ subscribe(_session, _channel, _queue);
+
+ try
+ {
+ publishMessages(_session, _channel, 1);
+ }
+ catch (AMQException e)
+ {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ fail(e.getMessage());
+ }
+
+ try
+ {
+ _registry.close();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ fail(e.getMessage());
+ }
+
+ assertTrue("Session should now be closed", _session.isClosed());
+
+
+ //Test attempting to modify the broker state after session has been closed.
+
+ //The Message should have been removed from the unacked list.
+
+ //Ack Messages
+ List<InternalTestProtocolSession.DeliveryPair> list = _session.getDelivers(_channel.getChannelId(), new AMQShortString("sgen_1"), 1);
+
+ InternalTestProtocolSession.DeliveryPair pair = list.get(0);
+
+ try
+ {
+ // The message should now be requeued and so unable to ack it.
+ _channel.acknowledgeMessage(pair.getDeliveryTag(), false);
+ }
+ catch (AMQException e)
+ {
+ assertEquals("Incorrect exception thrown", "Single ack on delivery tag 1 not known for channel:1", e.getMessage());
+ }
+
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreTest.java
new file mode 100644
index 0000000000..4317a501ed
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreTest.java
@@ -0,0 +1,646 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.store;
+
+import junit.framework.TestCase;
+
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.exchange.DirectExchange;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.ExchangeType;
+import org.apache.qpid.server.exchange.TopicExchange;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.AMQQueueFactory;
+import org.apache.qpid.server.queue.IncomingMessage;
+import org.apache.qpid.server.queue.MessageHandleFactory;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.queue.AMQPriorityQueue;
+import org.apache.qpid.server.queue.SimpleAMQQueue;
+import org.apache.qpid.server.queue.ExchangeBinding;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+import org.apache.qpid.server.protocol.InternalTestProtocolSession;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.amqp_8_0.BasicConsumeBodyImpl;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.common.AMQPFilterTypes;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * This tests the MessageStores by using the available interfaces.
+ *
+ * This test validates that Exchanges, Queues, Bindings and Messages are persisted correctly.
+ */
+public class MessageStoreTest extends TestCase
+{
+
+ private static final int DEFAULT_PRIORTY_LEVEL = 5;
+ private static final Logger _logger = LoggerFactory.getLogger(MessageStoreTest.class);
+
+ public void testMemoryMessageStore()
+ {
+
+ PropertiesConfiguration config = new PropertiesConfiguration();
+
+ config.addProperty("store.class", "org.apache.qpid.server.store.MemoryMessageStore");
+
+ runTestWithStore(config);
+ }
+
+ public void DISABLE_testDerbyMessageStore()
+ {
+ PropertiesConfiguration config = new PropertiesConfiguration();
+
+ config.addProperty("store.environment-path", "derbyDB_MST");
+ config.addProperty("store.class", "org.apache.qpid.server.store.DerbyMessageStore");
+
+ runTestWithStore(config);
+ }
+
+ private void reload(Configuration configuration)
+ {
+ if (_virtualHost != null)
+ {
+ try
+ {
+ _virtualHost.close();
+ }
+ catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+
+ try
+ {
+ _virtualHost = new VirtualHost(new VirtualHostConfiguration(getClass().getName(), configuration));
+ ApplicationRegistry.getInstance().getVirtualHostRegistry().registerVirtualHost(_virtualHost);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+
+ VirtualHost _virtualHost = null;
+ String virtualHostName = "MessageStoreTest";
+
+ AMQShortString nonDurableExchangeName = new AMQShortString("MST-NonDurableDirectExchange");
+ AMQShortString directExchangeName = new AMQShortString("MST-DirectExchange");
+ AMQShortString topicExchangeName = new AMQShortString("MST-TopicExchange");
+ AMQShortString queueOwner = new AMQShortString("MST");
+
+ AMQShortString durablePriorityTopicQueueName = new AMQShortString("MST-PriorityTopicQueue-Durable");
+ AMQShortString durableTopicQueueName = new AMQShortString("MST-TopicQueue-Durable");
+ AMQShortString priorityTopicQueueName = new AMQShortString("MST-PriorityTopicQueue");
+ AMQShortString topicQueueName = new AMQShortString("MST-TopicQueue");
+
+ AMQShortString durablePriorityQueueName = new AMQShortString("MST-PriorityQueue-Durable");
+ AMQShortString durableQueueName = new AMQShortString("MST-Queue-Durable");
+ AMQShortString priorityQueueName = new AMQShortString("MST-PriorityQueue");
+ AMQShortString queueName = new AMQShortString("MST-Queue");
+
+ AMQShortString directRouting = new AMQShortString("MST-direct");
+ AMQShortString topicRouting = new AMQShortString("MST-topic");
+
+ protected void setUp()
+ {
+ ApplicationRegistry.getInstance(1);
+ }
+
+ protected void tearDown()
+ {
+ ApplicationRegistry.remove(1);
+ }
+
+ protected void runTestWithStore(Configuration configuration)
+ {
+ //Ensure Environment Path is empty
+ cleanup(configuration);
+
+ //Load the Virtualhost with the required MessageStore
+ reload(configuration);
+
+ MessageStore messageStore = _virtualHost.getMessageStore();
+
+ createAllQueues();
+ createAllTopicQueues();
+
+ //Register Non-Durable DirectExchange
+ Exchange nonDurableExchange = createExchange(DirectExchange.TYPE, nonDurableExchangeName, false);
+ bindAllQueuesToExchange(nonDurableExchange, directRouting);
+
+ //Register DirectExchange
+ Exchange directExchange = createExchange(DirectExchange.TYPE, directExchangeName, true);
+ bindAllQueuesToExchange(directExchange, directRouting);
+
+ //Register TopicExchange
+ Exchange topicExchange = createExchange(TopicExchange.TYPE, topicExchangeName, true);
+ bindAllTopicQueuesToExchange(topicExchange, topicRouting);
+
+ //Send Message To NonDurable direct Exchange = persistent
+ sendMessageOnExchange(nonDurableExchange, directRouting, true);
+ // and non-persistent
+ sendMessageOnExchange(nonDurableExchange, directRouting, false);
+
+ //Send Message To direct Exchange = persistent
+ sendMessageOnExchange(directExchange, directRouting, true);
+ // and non-persistent
+ sendMessageOnExchange(directExchange, directRouting, false);
+
+ //Send Message To topic Exchange = persistent
+ sendMessageOnExchange(topicExchange, topicRouting, true);
+ // and non-persistent
+ sendMessageOnExchange(topicExchange, topicRouting, false);
+
+ //Ensure all the Queues have four messages (one transient, one persistent) x 2 exchange routings
+ validateMessageOnQueues(4, true);
+ //Ensure all the topics have two messages (one transient, one persistent)
+ validateMessageOnTopics(2, true);
+
+ assertEquals("Not all queues correctly registered", 8, _virtualHost.getQueueRegistry().getQueues().size());
+
+ if (!messageStore.isPersistent())
+ {
+ _logger.warn("Unable to test Persistent capabilities of messages store(" + messageStore.getClass() + ") as it is not capable of peristence.");
+ return;
+ }
+
+ //Reload the Virtualhost to test persistence
+ _logger.info("Reloading Virtualhost");
+
+ VirtualHost original = _virtualHost;
+
+ reload(configuration);
+
+ assertTrue("Virtualhost has not been reloaded", original != _virtualHost);
+
+ validateExchanges();
+
+ //Validate Durable Queues still have the persistentn message
+ validateMessageOnQueues(2, false);
+ //Validate Durable Queues still have the persistentn message
+ validateMessageOnTopics(1, false);
+
+ //Validate Properties of Binding
+ validateBindingProperties();
+
+ //Validate Properties of Queues
+ validateQueueProperties();
+
+ //Validate Non-Durable Queues are gone.
+ assertNull("Non-Durable queue still registered:" + priorityQueueName, _virtualHost.getQueueRegistry().getQueue(priorityQueueName));
+ assertNull("Non-Durable queue still registered:" + queueName, _virtualHost.getQueueRegistry().getQueue(queueName));
+ assertNull("Non-Durable queue still registered:" + priorityTopicQueueName, _virtualHost.getQueueRegistry().getQueue(priorityTopicQueueName));
+ assertNull("Non-Durable queue still registered:" + topicQueueName, _virtualHost.getQueueRegistry().getQueue(topicQueueName));
+
+ assertEquals("Not all queues correctly registered", 4, _virtualHost.getQueueRegistry().getQueues().size());
+ }
+
+ private void validateExchanges()
+ {
+ ExchangeRegistry registry = _virtualHost.getExchangeRegistry();
+
+ assertTrue(directExchangeName + " exchange NOT reloaded after failover",
+ registry.getExchangeNames().contains(directExchangeName));
+ assertTrue(topicExchangeName + " exchange NOT reloaded after failover",
+ registry.getExchangeNames().contains(topicExchangeName));
+ assertTrue(nonDurableExchangeName + " exchange reloaded after failover",
+ !registry.getExchangeNames().contains(nonDurableExchangeName));
+
+ // There are 5 required exchanges + our 2 durable queues
+ assertEquals("Incorrect number of exchanges available", 5 + 2, registry.getExchangeNames().size());
+ }
+
+ /** Validates that the Durable queues */
+ private void validateBindingProperties()
+ {
+ QueueRegistry queueRegistry = _virtualHost.getQueueRegistry();
+
+ validateBindingProperties(queueRegistry.getQueue(durablePriorityQueueName).getExchangeBindings(), false);
+ validateBindingProperties(queueRegistry.getQueue(durablePriorityTopicQueueName).getExchangeBindings(), true);
+ validateBindingProperties(queueRegistry.getQueue(durableQueueName).getExchangeBindings(), false);
+ validateBindingProperties(queueRegistry.getQueue(durableTopicQueueName).getExchangeBindings(), true);
+ }
+
+ /**
+ * Validate that each queue is bound once.
+ *
+ * @param bindings the set of bindings to validate
+ * @param useSelectors if set validate that the binding has a JMS_SELECTOR argument
+ */
+ private void validateBindingProperties(List<ExchangeBinding> bindings, boolean useSelectors)
+ {
+ assertEquals("Each queue should only be bound once.", 1, bindings.size());
+
+ ExchangeBinding binding = bindings.get(0);
+
+ if (useSelectors)
+ {
+ assertTrue("Binding does not contain a Selector argument.",
+ binding.getArguments().containsKey(AMQPFilterTypes.JMS_SELECTOR.getValue()));
+ }
+ }
+
+ private void validateQueueProperties()
+ {
+ QueueRegistry queueRegistry = _virtualHost.getQueueRegistry();
+
+ validateQueueProperties(queueRegistry.getQueue(durablePriorityQueueName), true);
+ validateQueueProperties(queueRegistry.getQueue(durablePriorityTopicQueueName), true);
+ validateQueueProperties(queueRegistry.getQueue(durableQueueName), false);
+ validateQueueProperties(queueRegistry.getQueue(durableTopicQueueName), false);
+
+ }
+
+ private void validateQueueProperties(AMQQueue queue, boolean usePriority)
+ {
+ if (usePriority)
+ {
+ assertEquals("Queue is no longer a Priority Queue", AMQPriorityQueue.class, queue.getClass());
+ assertEquals("Priority Queue does not have set priorities", DEFAULT_PRIORTY_LEVEL, ((AMQPriorityQueue) queue).getPriorities());
+ }
+ else
+ {
+ assertEquals("Queue is no longer a Priority Queue", SimpleAMQQueue.class, queue.getClass());
+ }
+ }
+
+ /**
+ * Delete the Store Environment path
+ *
+ * @param configuration The configuration that contains the store environment path.
+ */
+ private void cleanup(Configuration configuration)
+ {
+
+ String environment = configuration.getString("store.environment-path");
+
+ if (environment != null)
+ {
+ File environmentPath = new File(environment);
+
+ if (environmentPath.exists())
+ {
+ deleteDirectory(environmentPath);
+ }
+ }
+ }
+
+ private void deleteDirectory(File path)
+ {
+ if (path.isDirectory())
+ {
+ for (File file : path.listFiles())
+ {
+ deleteDirectory(file);
+ }
+ }
+ else
+ {
+ path.delete();
+ }
+ }
+
+ private void sendMessageOnExchange(Exchange directExchange, AMQShortString routingKey, boolean deliveryMode)
+ {
+ //Set MessagePersustebce
+ BasicContentHeaderProperties properties = new BasicContentHeaderProperties();
+ properties.setDeliveryMode(deliveryMode ? Integer.valueOf(2).byteValue() : Integer.valueOf(1).byteValue());
+ FieldTable headers = properties.getHeaders();
+ headers.setString("Test", "MST");
+ properties.setHeaders(headers);
+
+ MessagePublishInfo messageInfo = new TestMessagePublishInfo(directExchange, false, false, routingKey);
+
+ IncomingMessage currentMessage = null;
+
+ try
+ {
+ currentMessage = new IncomingMessage(_virtualHost.getMessageStore().getNewMessageId(),
+ messageInfo,
+ new NonTransactionalContext(_virtualHost.getMessageStore(),
+ new StoreContext(), null, null),
+ new InternalTestProtocolSession());
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+
+ currentMessage.setMessageStore(_virtualHost.getMessageStore());
+ currentMessage.setExchange(directExchange);
+
+ ContentHeaderBody headerBody = new ContentHeaderBody();
+ headerBody.classId = BasicConsumeBodyImpl.CLASS_ID;
+ headerBody.bodySize = 0;
+
+ headerBody.properties = properties;
+
+ try
+ {
+ currentMessage.setContentHeaderBody(headerBody);
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+
+ currentMessage.setExpiration();
+
+ try
+ {
+ currentMessage.route();
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+
+ try
+ {
+ currentMessage.routingComplete(_virtualHost.getMessageStore(), new MessageHandleFactory());
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+
+ // check and deliver if header says body length is zero
+ if (currentMessage.allContentReceived())
+ {
+ try
+ {
+ currentMessage.deliverToQueues();
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+ }
+ }
+
+ private void createAllQueues()
+ {
+ //Register Durable Priority Queue
+ createQueue(durablePriorityQueueName, true, true);
+
+ //Register Durable Simple Queue
+ createQueue(durableQueueName, false, true);
+
+ //Register NON-Durable Priority Queue
+ createQueue(priorityQueueName, true, false);
+
+ //Register NON-Durable Simple Queue
+ createQueue(queueName, false, false);
+ }
+
+ private void createAllTopicQueues()
+ {
+ //Register Durable Priority Queue
+ createQueue(durablePriorityTopicQueueName, true, true);
+
+ //Register Durable Simple Queue
+ createQueue(durableTopicQueueName, false, true);
+
+ //Register NON-Durable Priority Queue
+ createQueue(priorityTopicQueueName, true, false);
+
+ //Register NON-Durable Simple Queue
+ createQueue(topicQueueName, false, false);
+ }
+
+ private Exchange createExchange(ExchangeType type, AMQShortString name, boolean durable)
+ {
+ Exchange exchange = null;
+
+ try
+ {
+ exchange = type.newInstance(_virtualHost, name, durable, 0, false);
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+
+ try
+ {
+ _virtualHost.getExchangeRegistry().registerExchange(exchange);
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+ return exchange;
+ }
+
+ private void createQueue(AMQShortString queueName, boolean usePriority, boolean durable)
+ {
+
+ FieldTable queueArguments = null;
+
+ if (usePriority)
+ {
+ queueArguments = new FieldTable();
+ queueArguments.put(AMQQueueFactory.X_QPID_PRIORITIES, DEFAULT_PRIORTY_LEVEL);
+ }
+
+ AMQQueue queue = null;
+
+ //Ideally we would be able to use the QueueDeclareHandler here.
+ try
+ {
+ queue = AMQQueueFactory.createAMQQueueImpl(queueName, durable, queueOwner, false, _virtualHost,
+ queueArguments);
+
+ validateQueueProperties(queue, usePriority);
+
+ if (queue.isDurable() && !queue.isAutoDelete())
+ {
+ _virtualHost.getMessageStore().createQueue(queue, queueArguments);
+ }
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+
+ try
+ {
+ _virtualHost.getQueueRegistry().registerQueue(queue);
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+
+ }
+
+ private void bindAllQueuesToExchange(Exchange exchange, AMQShortString routingKey)
+ {
+ FieldTable queueArguments = new FieldTable();
+ queueArguments.put(AMQQueueFactory.X_QPID_PRIORITIES, DEFAULT_PRIORTY_LEVEL);
+
+ QueueRegistry queueRegistry = _virtualHost.getQueueRegistry();
+
+ bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durablePriorityQueueName), false, queueArguments);
+ bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durableQueueName), false, null);
+ bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(priorityQueueName), false, queueArguments);
+ bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(queueName), false, null);
+ }
+
+ private void bindAllTopicQueuesToExchange(Exchange exchange, AMQShortString routingKey)
+ {
+ FieldTable queueArguments = new FieldTable();
+ queueArguments.put(AMQQueueFactory.X_QPID_PRIORITIES, DEFAULT_PRIORTY_LEVEL);
+
+ QueueRegistry queueRegistry = _virtualHost.getQueueRegistry();
+
+ bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durablePriorityTopicQueueName), true, queueArguments);
+ bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durableTopicQueueName), true, null);
+ bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(priorityTopicQueueName), true, queueArguments);
+ bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(topicQueueName), true, null);
+ }
+
+
+ protected void bindQueueToExchange(Exchange exchange, AMQShortString routingKey, AMQQueue queue, boolean useSelector, FieldTable queueArguments)
+ {
+ try
+ {
+ exchange.registerQueue(queueName, queue, queueArguments);
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+
+ FieldTable bindArguments = null;
+
+ if (useSelector)
+ {
+ bindArguments = new FieldTable();
+ bindArguments.put(AMQPFilterTypes.JMS_SELECTOR.getValue(), "Test = 'MST'");
+ }
+
+ try
+ {
+ queue.bind(exchange, routingKey, bindArguments);
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+ }
+
+ private void validateMessage(long messageCount, boolean allQueues)
+ {
+ validateMessageOnTopics(messageCount, allQueues);
+ validateMessageOnQueues(messageCount, allQueues);
+ }
+
+ private void validateMessageOnTopics(long messageCount, boolean allQueues)
+ {
+ validateMessageOnQueue(durablePriorityTopicQueueName, messageCount);
+ validateMessageOnQueue(durableTopicQueueName, messageCount);
+
+ if (allQueues)
+ {
+ validateMessageOnQueue(priorityTopicQueueName, messageCount);
+ validateMessageOnQueue(topicQueueName, messageCount);
+ }
+ }
+
+ private void validateMessageOnQueues(long messageCount, boolean allQueues)
+ {
+ validateMessageOnQueue(durablePriorityQueueName, messageCount);
+ validateMessageOnQueue(durableQueueName, messageCount);
+
+ if (allQueues)
+ {
+ validateMessageOnQueue(priorityQueueName, messageCount);
+ validateMessageOnQueue(queueName, messageCount);
+ }
+ }
+
+ private void validateMessageOnQueue(AMQShortString queueName, long messageCount)
+ {
+ AMQQueue queue = _virtualHost.getQueueRegistry().getQueue(queueName);
+
+ assertNotNull("Queue(" + queueName + ") not correctly registered:", queue);
+
+ assertEquals("Incorrect Message count on queue:" + queueName, messageCount, queue.getMessageCount());
+ }
+
+ private class TestMessagePublishInfo implements MessagePublishInfo
+ {
+
+ Exchange _exchange;
+ boolean _immediate;
+ boolean _mandatory;
+ AMQShortString _routingKey;
+
+ TestMessagePublishInfo(Exchange exchange, boolean immediate, boolean mandatory, AMQShortString routingKey)
+ {
+ _exchange = exchange;
+ _immediate = immediate;
+ _mandatory = mandatory;
+ _routingKey = routingKey;
+ }
+
+ public AMQShortString getExchange()
+ {
+ return _exchange.getName();
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+ //no-op
+ }
+
+ public boolean isImmediate()
+ {
+ return _immediate;
+ }
+
+ public boolean isMandatory()
+ {
+ return _mandatory;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return _routingKey;
+ }
+ }
+} \ No newline at end of file
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java
new file mode 100644
index 0000000000..fd6789f5ce
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java
@@ -0,0 +1,156 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.store;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.server.queue.MessageMetaData;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.exchange.Exchange;
+
+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(String base, Configuration config) throws Exception
+ {
+ }
+
+ public void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void close() throws Exception
+ {
+ }
+
+ public void removeMessage(StoreContext s, Long messageId)
+ {
+ }
+
+ public void createExchange(Exchange exchange) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void removeExchange(Exchange exchange) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void createQueue(AMQQueue queue) throws AMQException
+ {
+ }
+
+ public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQException
+ {
+ }
+
+ public void beginTran(StoreContext s) throws AMQException
+ {
+ }
+
+ public boolean inTran(StoreContext sc)
+ {
+ return false;
+ }
+
+ public void commitTran(StoreContext storeContext) throws AMQException
+ {
+ }
+
+ public void abortTran(StoreContext storeContext) throws AMQException
+ {
+ }
+
+ public List<AMQQueue> createQueues() throws AMQException
+ {
+ return null;
+ }
+
+ public Long getNewMessageId()
+ {
+ return _messageId.getAndIncrement();
+ }
+
+ public void storeContentBodyChunk(StoreContext sc, Long messageId, int index, ContentChunk contentBody, boolean lastContentBody) throws AMQException
+ {
+
+ }
+
+ public void storeMessageMetaData(StoreContext sc, Long messageId, MessageMetaData messageMetaData) throws AMQException
+ {
+
+ }
+
+ public MessageMetaData getMessageMetaData(StoreContext s,Long messageId) throws AMQException
+ {
+ return null;
+ }
+
+ public ContentChunk getContentBodyChunk(StoreContext s,Long messageId, int index) throws AMQException
+ {
+ return null;
+ }
+
+ public boolean isPersistent()
+ {
+ return false;
+ }
+
+ public void removeQueue(final AMQQueue queue) throws AMQException
+ {
+
+ }
+
+ public void enqueueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException
+ {
+
+ }
+
+ public void dequeueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException
+ {
+
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java
new file mode 100644
index 0000000000..4e48435962
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java
@@ -0,0 +1,51 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.store;
+
+import org.apache.qpid.server.queue.MessageMetaData;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.List;
+
+/**
+ * Adds some extra methods to the memory message store for testing purposes.
+ */
+public class TestMemoryMessageStore extends MemoryMessageStore
+{
+ public TestMemoryMessageStore()
+ {
+ _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>();
+ _contentBodyMap = new ConcurrentHashMap<Long, List<ContentChunk>>();
+ }
+
+ public ConcurrentMap<Long, MessageMetaData> getMessageMetaDataMap()
+ {
+ return _metaDataMap;
+ }
+
+ public ConcurrentMap<Long, List<ContentChunk>> getContentBodyMap()
+ {
+ return _contentBodyMap;
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestReferenceCounting.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestReferenceCounting.java
new file mode 100644
index 0000000000..2346660d25
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestReferenceCounting.java
@@ -0,0 +1,169 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.store;
+
+import junit.framework.TestCase;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.MessageHandleFactory;
+import org.apache.qpid.server.queue.AMQMessageHandle;
+
+/**
+ * Tests that reference counting works correctly with AMQMessage and the message store
+ */
+public class TestReferenceCounting extends TestCase
+{
+ private TestMemoryMessageStore _store;
+
+ private StoreContext _storeContext = new StoreContext();
+
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ _store = new TestMemoryMessageStore();
+ }
+
+ /**
+ * Check that when the reference count is decremented the message removes itself from the store
+ */
+ public void testMessageGetsRemoved() throws AMQException
+ {
+ ContentHeaderBody chb = createPersistentContentHeader();
+
+ MessagePublishInfo info = new MessagePublishInfo()
+ {
+
+ public AMQShortString getExchange()
+ {
+ return null;
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isImmediate()
+ {
+ return false;
+ }
+
+ public boolean isMandatory()
+ {
+ return false;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return null;
+ }
+ };
+
+
+ final long messageId = _store.getNewMessageId();
+ AMQMessageHandle messageHandle = (new MessageHandleFactory()).createMessageHandle(messageId, _store, true);
+ messageHandle.setPublishAndContentHeaderBody(_storeContext,info, chb);
+ AMQMessage message = new AMQMessage(messageHandle,
+ _storeContext,info);
+
+ message = message.takeReference();
+
+ // we call routing complete to set up the handle
+ // message.routingComplete(_store, _storeContext, new MessageHandleFactory());
+
+
+ assertEquals(1, _store.getMessageMetaDataMap().size());
+ message.decrementReference(_storeContext);
+ assertEquals(1, _store.getMessageMetaDataMap().size());
+ }
+
+ private ContentHeaderBody createPersistentContentHeader()
+ {
+ ContentHeaderBody chb = new ContentHeaderBody();
+ BasicContentHeaderProperties bchp = new BasicContentHeaderProperties();
+ bchp.setDeliveryMode((byte)2);
+ chb.properties = bchp;
+ return chb;
+ }
+
+ public void testMessageRemains() throws AMQException
+ {
+
+ MessagePublishInfo info = new MessagePublishInfo()
+ {
+
+ public AMQShortString getExchange()
+ {
+ return null;
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isImmediate()
+ {
+ return false;
+ }
+
+ public boolean isMandatory()
+ {
+ return false;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return null;
+ }
+ };
+
+ final Long messageId = _store.getNewMessageId();
+ final ContentHeaderBody chb = createPersistentContentHeader();
+ AMQMessageHandle messageHandle = (new MessageHandleFactory()).createMessageHandle(messageId, _store, true);
+ messageHandle.setPublishAndContentHeaderBody(_storeContext,info,chb);
+ AMQMessage message = new AMQMessage(messageHandle,
+ _storeContext,
+ info);
+
+
+ message = message.takeReference();
+ // we call routing complete to set up the handle
+ // message.routingComplete(_store, _storeContext, new MessageHandleFactory());
+
+
+
+ assertEquals(1, _store.getMessageMetaDataMap().size());
+ message = message.takeReference();
+ message.decrementReference(_storeContext);
+ assertEquals(1, _store.getMessageMetaDataMap().size());
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(TestReferenceCounting.class);
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java
new file mode 100644
index 0000000000..9146fe88ae
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java
@@ -0,0 +1,92 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.store;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.MessageMetaData;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Adds some extra methods to the memory message store for testing purposes.
+ */
+public class TestableMemoryMessageStore extends MemoryMessageStore
+{
+
+ MemoryMessageStore _mms = null;
+ private HashMap<Long, AMQQueue> _messages = new HashMap<Long, AMQQueue>();
+
+ public TestableMemoryMessageStore(MemoryMessageStore mms)
+ {
+ _mms = mms;
+ }
+
+ public TestableMemoryMessageStore()
+ {
+ _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>();
+ _contentBodyMap = new ConcurrentHashMap<Long, List<ContentChunk>>();
+ }
+
+ public ConcurrentMap<Long, MessageMetaData> getMessageMetaDataMap()
+ {
+ if (_mms != null)
+ {
+ return _mms._metaDataMap;
+ }
+ else
+ {
+ return _metaDataMap;
+ }
+ }
+
+ public ConcurrentMap<Long, List<ContentChunk>> getContentBodyMap()
+ {
+ if (_mms != null)
+ {
+ return _mms._contentBodyMap;
+ }
+ else
+ {
+ return _contentBodyMap;
+ }
+ }
+
+ public void enqueueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException
+ {
+ getMessages().put(messageId, queue);
+ }
+
+ public void dequeueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException
+ {
+ getMessages().remove(messageId);
+ }
+
+ public HashMap<Long, AMQQueue> getMessages()
+ {
+ return _messages;
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java
new file mode 100644
index 0000000000..33fd669d5c
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java
@@ -0,0 +1,180 @@
+package org.apache.qpid.server.subscription;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+
+import java.util.ArrayList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.queue.QueueEntry.SubscriptionAcquiredState;
+
+public class MockSubscription implements Subscription
+{
+
+ private boolean _closed = false;
+ private AMQShortString tag = new AMQShortString("mocktag");
+ private AMQQueue queue = null;
+ private StateListener _listener = null;
+ private QueueEntry lastSeen = null;
+ private State _state = State.ACTIVE;
+ private ArrayList<QueueEntry> messages = new ArrayList<QueueEntry>();
+ private final Lock _stateChangeLock = new ReentrantLock();
+
+ public void close()
+ {
+ _closed = true;
+ if (_listener != null)
+ {
+ _listener.stateChange(this, _state, State.CLOSED);
+ }
+ _state = State.CLOSED;
+ }
+
+ public boolean filtersMessages()
+ {
+ return false;
+ }
+
+ public AMQChannel getChannel()
+ {
+ return null;
+ }
+
+ public AMQShortString getConsumerTag()
+ {
+ return tag ;
+ }
+
+ public QueueEntry getLastSeenEntry()
+ {
+ return lastSeen;
+ }
+
+ public SubscriptionAcquiredState getOwningState()
+ {
+ return new QueueEntry.SubscriptionAcquiredState(this);
+ }
+
+ public AMQQueue getQueue()
+ {
+ return queue;
+ }
+
+ public void getSendLock()
+ {
+ _stateChangeLock.lock();
+ }
+
+ public boolean hasInterest(QueueEntry msg)
+ {
+ return true;
+ }
+
+ public boolean isActive()
+ {
+ return true;
+ }
+
+ public boolean isAutoClose()
+ {
+ return false;
+ }
+
+ public boolean isBrowser()
+ {
+ return false;
+ }
+
+ public boolean isClosed()
+ {
+ return _closed;
+ }
+
+ public boolean isSuspended()
+ {
+ return false;
+ }
+
+ public void queueDeleted(AMQQueue queue)
+ {
+ }
+
+ public void releaseSendLock()
+ {
+ _stateChangeLock.unlock();
+ }
+
+ public void resend(QueueEntry entry) throws AMQException
+ {
+ }
+
+ public void restoreCredit(QueueEntry queueEntry)
+ {
+ }
+
+ public void send(QueueEntry msg) throws AMQException
+ {
+ lastSeen = msg;
+ messages.add(msg);
+ }
+
+ public boolean setLastSeenEntry(QueueEntry expected, QueueEntry newValue)
+ {
+ boolean result = false;
+ if (expected != null)
+ {
+ result = (expected.equals(lastSeen));
+ }
+ lastSeen = newValue;
+ return result;
+ }
+
+ public void setQueue(AMQQueue queue)
+ {
+ this.queue = queue;
+ }
+
+ public void setStateListener(StateListener listener)
+ {
+ this._listener = listener;
+ }
+
+ public State getState()
+ {
+ return _state;
+ }
+
+ public boolean wouldSuspend(QueueEntry msg)
+ {
+ return false;
+ }
+
+ public ArrayList<QueueEntry> getMessages()
+ {
+ return messages;
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/QueueBrowserUsesNoAckTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/QueueBrowserUsesNoAckTest.java
new file mode 100644
index 0000000000..d0db4ebd38
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/QueueBrowserUsesNoAckTest.java
@@ -0,0 +1,77 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.subscription;
+
+import org.apache.qpid.server.util.InternalBrokerBaseCase;
+import org.apache.qpid.server.protocol.InternalTestProtocolSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.AMQException;
+
+import java.util.List;
+
+public class QueueBrowserUsesNoAckTest extends InternalBrokerBaseCase
+{
+
+ public void testQueueBrowserUsesNoAck() throws AMQException
+ {
+ int sendMessageCount = 2;
+ int prefetch = 1;
+
+ //Check store is empty
+ checkStoreContents(0);
+
+ //Send required messsages to the queue
+ publishMessages(_session, _channel, sendMessageCount);
+
+ //Ensure they are stored
+ checkStoreContents(sendMessageCount);
+
+ //Check that there are no unacked messages
+ assertEquals("Channel should have no unacked msgs ", 0,
+ _channel.getUnacknowledgedMessageMap().size());
+
+ //Set the prefetch on the session to be less than the sent messages
+ _channel.setCredit(0, prefetch);
+
+ //browse the queue
+ AMQShortString browser = browse(_channel, _queue);
+
+ _queue.deliverAsync();
+
+ //Wait for messages to fill the prefetch
+ _session.awaitDelivery(prefetch);
+
+ //Get those messages
+ List<InternalTestProtocolSession.DeliveryPair> messages =
+ _session.getDelivers(_channel.getChannelId(), browser,
+ prefetch);
+
+ //Ensure we recevied the prefetched messages
+ assertEquals(prefetch, messages.size());
+
+ //Check the process didn't suspend the subscription as this would
+ // indicate we are using the prefetch credit. i.e. using acks not No-Ack
+ assertTrue("The subscription has been suspended",
+ !_channel.getSubscription(browser).getState()
+ .equals(Subscription.State.SUSPENDED));
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/TxnBufferTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/TxnBufferTest.java
new file mode 100644
index 0000000000..84d3d313d1
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/TxnBufferTest.java
@@ -0,0 +1,306 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.txn;
+
+import junit.framework.TestCase;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.TestMemoryMessageStore;
+import org.apache.qpid.server.store.StoreContext;
+
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+
+public class TxnBufferTest extends TestCase
+{
+ private final LinkedList<MockOp> ops = new LinkedList<MockOp>();
+
+ public void testCommit() throws AMQException
+ {
+ MockStore store = new MockStore();
+
+ TxnBuffer buffer = new TxnBuffer();
+ 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(null);
+
+ validateOps();
+ store.validate();
+ }
+
+ public void testRollback() throws AMQException
+ {
+ MockStore store = new MockStore();
+
+ TxnBuffer buffer = new TxnBuffer();
+ buffer.enlist(new MockOp().expectRollback());
+ buffer.enlist(new MockOp().expectRollback());
+ buffer.enlist(new MockOp().expectRollback());
+
+ buffer.rollback(null);
+
+ validateOps();
+ store.validate();
+ }
+
+ public void testCommitWithFailureDuringPrepare() throws AMQException
+ {
+ MockStore store = new MockStore();
+ store.beginTran(null);
+
+ TxnBuffer buffer = new TxnBuffer();
+ buffer.enlist(new StoreMessageOperation(store));
+ 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());
+
+ try
+ {
+ buffer.commit(null);
+ }
+ catch (NoSuchElementException e)
+ {
+
+ }
+
+ validateOps();
+ store.validate();
+ }
+
+ public void testCommitWithPersistance() throws AMQException
+ {
+ MockStore store = new MockStore();
+ store.beginTran(null);
+ store.expectCommit();
+
+ TxnBuffer buffer = new TxnBuffer();
+ buffer.enlist(new MockOp().expectPrepare().expectCommit());
+ buffer.enlist(new MockOp().expectPrepare().expectCommit());
+ buffer.enlist(new MockOp().expectPrepare().expectCommit());
+ buffer.enlist(new StoreMessageOperation(store));
+ buffer.enlist(new TxnTester(store));
+
+ buffer.commit(null);
+ validateOps();
+ store.validate();
+ }
+
+ private void validateOps()
+ {
+ for (MockOp op : ops)
+ {
+ op.validate();
+ }
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(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(StoreContext context)
+ {
+ assertEquals(expected.removeLast(), PREPARE);
+ }
+
+ public void commit(StoreContext context)
+ {
+ assertEquals(expected.removeLast(), COMMIT);
+ }
+
+ public void undoPrepare()
+ {
+ assertEquals(expected.removeLast(), UNDO_PREPARE);
+ }
+
+ public void rollback(StoreContext context)
+ {
+ 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 TestMemoryMessageStore
+ {
+ final Object BEGIN = "BEGIN";
+ final Object ABORT = "ABORT";
+ final Object COMMIT = "COMMIT";
+
+ private final LinkedList expected = new LinkedList();
+ private boolean inTran;
+
+ public void beginTran(StoreContext context) throws AMQException
+ {
+ inTran = true;
+ }
+
+ public void commitTran(StoreContext context) throws AMQException
+ {
+ assertEquals(expected.removeLast(), COMMIT);
+ inTran = false;
+ }
+
+ public void abortTran(StoreContext context) throws AMQException
+ {
+ assertEquals(expected.removeLast(), ABORT);
+ inTran = false;
+ }
+
+ public boolean inTran(StoreContext context)
+ {
+ 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(StoreContext context) throws AMQException
+ {
+ }
+ public void commit(StoreContext context)
+ {
+ }
+ public void undoPrepare()
+ {
+ }
+ public void rollback(StoreContext context)
+ {
+ }
+ }
+
+ class FailedPrepare extends NullOp
+ {
+ public void prepare() throws AMQException
+ {
+ throw new AMQException(null, "Fail!", null);
+ }
+ }
+
+ class TxnTester extends NullOp
+ {
+ private final MessageStore store;
+
+ private final StoreContext context = new StoreContext();
+
+ TxnTester(MessageStore store)
+ {
+ this.store = store;
+ }
+
+ public void prepare() throws AMQException
+ {
+ assertTrue("Expected prepare to be performed under txn", store.inTran(context));
+ }
+
+ public void commit()
+ {
+ assertTrue("Expected commit not to be performed under txn", !store.inTran(context));
+ }
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/util/InternalBrokerBaseCase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/InternalBrokerBaseCase.java
new file mode 100644
index 0000000000..101c33043d
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/InternalBrokerBaseCase.java
@@ -0,0 +1,214 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.util;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.AMQQueueFactory;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.protocol.InternalTestProtocolSession;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.ConsumerTagNotUniqueException;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.store.TestableMemoryMessageStore;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.util.MockChannel;
+import org.apache.qpid.common.AMQPFilterTypes;
+import org.apache.qpid.exchange.ExchangeDefaults;
+
+public class InternalBrokerBaseCase extends TestCase
+{
+ protected IApplicationRegistry _registry;
+ protected MessageStore _messageStore;
+ protected MockChannel _channel;
+ protected InternalTestProtocolSession _session;
+ protected VirtualHost _virtualHost;
+ protected StoreContext _storeContext = new StoreContext();
+ protected AMQQueue _queue;
+ protected AMQShortString QUEUE_NAME;
+
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.setProperty("virtualhosts.virtualhost.test.store.class", TestableMemoryMessageStore.class.getName());
+ _registry = new TestApplicationRegistry(new ServerConfiguration(configuration));
+ ApplicationRegistry.initialise(_registry);
+ _virtualHost = _registry.getVirtualHostRegistry().getVirtualHost("test");
+
+ _messageStore = _virtualHost.getMessageStore();
+
+ QUEUE_NAME = new AMQShortString("test");
+ _queue = AMQQueueFactory.createAMQQueueImpl(QUEUE_NAME, false, new AMQShortString("testowner"),
+ false, _virtualHost, null);
+
+ _virtualHost.getQueueRegistry().registerQueue(_queue);
+
+ Exchange defaultExchange = _virtualHost.getExchangeRegistry().getDefaultExchange();
+
+ _queue.bind(defaultExchange, QUEUE_NAME, null);
+
+ _session = new InternalTestProtocolSession();
+
+ _session.setVirtualHost(_virtualHost);
+
+ _channel = new MockChannel(_session, 1, _messageStore);
+
+ _session.addChannel(_channel);
+ }
+
+ public void tearDown() throws Exception
+ {
+ ApplicationRegistry.remove(1);
+ super.tearDown();
+ }
+
+ protected void checkStoreContents(int messageCount)
+ {
+ assertEquals("Message header count incorrect in the MetaDataMap", messageCount, ((TestableMemoryMessageStore) _messageStore).getMessageMetaDataMap().size());
+
+ //The above publish message is sufficiently small not to fit in the header so no Body is required.
+ //assertEquals("Message body count incorrect in the ContentBodyMap", messageCount, ((TestableMemoryMessageStore) _messageStore).getContentBodyMap().size());
+ }
+
+ protected AMQShortString subscribe(InternalTestProtocolSession session, AMQChannel channel, AMQQueue queue)
+ {
+ try
+ {
+ return channel.subscribeToQueue(null, queue, true, null, false, true);
+ }
+ catch (AMQException e)
+ {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ catch (ConsumerTagNotUniqueException e)
+ {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ //Keep the compiler happy
+ return null;
+ }
+
+ protected AMQShortString browse(AMQChannel channel, AMQQueue queue)
+ {
+ try
+ {
+ FieldTable filters = new FieldTable();
+ filters.put(AMQPFilterTypes.NO_CONSUME.getValue(), true);
+
+ return channel.subscribeToQueue(null, queue, true, filters, false, true);
+ }
+ catch (AMQException e)
+ {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ catch (ConsumerTagNotUniqueException e)
+ {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ //Keep the compiler happy
+ return null;
+ }
+
+ public void publishMessages(InternalTestProtocolSession session, AMQChannel channel, int messages) throws AMQException
+ {
+ MessagePublishInfo info = new MessagePublishInfo()
+ {
+ public AMQShortString getExchange()
+ {
+ return ExchangeDefaults.DEFAULT_EXCHANGE_NAME;
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+
+ }
+
+ public boolean isImmediate()
+ {
+ return false;
+ }
+
+ public boolean isMandatory()
+ {
+ return false;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return QUEUE_NAME;
+ }
+ };
+
+ for (int count = 0; count < messages; count++)
+ {
+ channel.setPublishFrame(info, _virtualHost.getExchangeRegistry().getExchange(info.getExchange()));
+
+ //Set the body size
+ ContentHeaderBody _headerBody = new ContentHeaderBody();
+ _headerBody.bodySize = 0;
+
+ //Set Minimum properties
+ BasicContentHeaderProperties properties = new BasicContentHeaderProperties();
+
+ properties.setExpiration(0L);
+ properties.setTimestamp(System.currentTimeMillis());
+
+ //Make Message Persistent
+ properties.setDeliveryMode((byte) 2);
+
+ _headerBody.properties = properties;
+
+ channel.publishContentHeader(_headerBody);
+ }
+
+ }
+
+ public void acknowledge(AMQChannel channel, long deliveryTag)
+ {
+ try
+ {
+ channel.acknowledgeMessage(deliveryTag, false);
+ }
+ catch (AMQException e)
+ {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/util/LoggingProxyTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/LoggingProxyTest.java
new file mode 100644
index 0000000000..c7db51016e
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/LoggingProxyTest.java
@@ -0,0 +1,88 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.util;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+public class LoggingProxyTest extends TestCase
+{
+ 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";
+ }
+ }
+
+ public void testSimple() {
+ LoggingProxy proxy = new LoggingProxy(new Foo(), 20);
+ IFoo foo = (IFoo)proxy.getProxy(IFoo.class);
+ foo.foo();
+ assertEquals(2, proxy.getBufferSize());
+ assertTrue(proxy.getBuffer().get(0).toString().matches(".*: foo\\(\\) entered$"));
+ assertTrue(proxy.getBuffer().get(1).toString().matches(".*: foo\\(\\) returned$"));
+
+ foo.foo(3, Arrays.asList(0, 1, 2));
+ assertEquals(4, proxy.getBufferSize());
+ assertTrue(proxy.getBuffer().get(2).toString().matches(".*: foo\\(\\[3, \\[0, 1, 2\\]\\]\\) entered$"));
+ assertTrue(proxy.getBuffer().get(3).toString().matches(".*: foo\\(\\) returned$"));
+
+ foo.bar();
+ assertEquals(6, proxy.getBufferSize());
+ assertTrue(proxy.getBuffer().get(4).toString().matches(".*: bar\\(\\) entered$"));
+ assertTrue(proxy.getBuffer().get(5).toString().matches(".*: bar\\(\\) returned null$"));
+
+ foo.bar("hello", Arrays.asList(1, 2, 3));
+ assertEquals(8, proxy.getBufferSize());
+ assertTrue(proxy.getBuffer().get(6).toString().matches(".*: bar\\(\\[hello, \\[1, 2, 3\\]\\]\\) entered$"));
+ assertTrue(proxy.getBuffer().get(7).toString().matches(".*: bar\\(\\) returned ha$"));
+
+ proxy.dump();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(LoggingProxyTest.class);
+ }
+}
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java
new file mode 100644
index 0000000000..c6ecac6a01
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java
@@ -0,0 +1,137 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.util;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.MapConfiguration;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.management.NoopManagedObjectRegistry;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.access.ACLManager;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.plugins.AllowAll;
+import org.apache.qpid.server.security.auth.database.PropertiesPrincipalDatabaseManager;
+import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.TestableMemoryMessageStore;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Properties;
+import java.util.Arrays;
+
+public class TestApplicationRegistry extends ApplicationRegistry
+{
+ private QueueRegistry _queueRegistry;
+
+ private ExchangeRegistry _exchangeRegistry;
+
+ private ExchangeFactory _exchangeFactory;
+
+ private MessageStore _messageStore;
+
+ private VirtualHost _vHost;
+
+
+ private ServerConfiguration _config;
+
+ public TestApplicationRegistry() throws ConfigurationException
+ {
+ super(new ServerConfiguration(new PropertiesConfiguration()));
+ }
+
+ public TestApplicationRegistry(ServerConfiguration config) throws ConfigurationException
+ {
+ super(config);
+ _config = config;
+ }
+
+ public void initialise() throws Exception
+ {
+ Properties users = new Properties();
+
+ users.put("guest", "guest");
+
+ _databaseManager = new PropertiesPrincipalDatabaseManager("default", users);
+
+ _accessManager = new ACLManager(_configuration.getSecurityConfiguration(), _pluginManager, AllowAll.FACTORY);
+
+ _authenticationManager = new PrincipalDatabaseAuthenticationManager(null, null);
+
+ _managedObjectRegistry = new NoopManagedObjectRegistry();
+
+ _messageStore = new TestableMemoryMessageStore();
+
+ _virtualHostRegistry = new VirtualHostRegistry();
+
+ PropertiesConfiguration vhostProps = new PropertiesConfiguration();
+ VirtualHostConfiguration hostConfig = new VirtualHostConfiguration("test", vhostProps);
+ _vHost = new VirtualHost(hostConfig, _messageStore);
+
+ _virtualHostRegistry.registerVirtualHost(_vHost);
+
+ _queueRegistry = _vHost.getQueueRegistry();
+ _exchangeFactory = _vHost.getExchangeFactory();
+ _exchangeRegistry = _vHost.getExchangeRegistry();
+
+ }
+
+ public QueueRegistry getQueueRegistry()
+ {
+ return _queueRegistry;
+ }
+
+ public ExchangeRegistry getExchangeRegistry()
+ {
+ return _exchangeRegistry;
+ }
+
+ public ExchangeFactory getExchangeFactory()
+ {
+ return _exchangeFactory;
+ }
+
+ public Collection<String> getVirtualHostNames()
+ {
+ String[] hosts = {"test"};
+ return Arrays.asList(hosts);
+ }
+
+ public void setAccessManager(ACLManager newManager)
+ {
+ _accessManager = newManager;
+ }
+
+ public MessageStore getMessageStore()
+ {
+ return _messageStore;
+ }
+
+}
+
+
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/util/MockChannel.java b/qpid/java/broker/src/test/java/org/apache/qpid/util/MockChannel.java
new file mode 100644
index 0000000000..447d09429d
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/util/MockChannel.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.util;
+
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+
+public class MockChannel extends AMQChannel
+{
+ public MockChannel(AMQProtocolSession session, int channelId, MessageStore messageStore)
+ throws AMQException
+ {
+ super(session, channelId, messageStore);
+ }
+
+ public Subscription getSubscription(AMQShortString subscription)
+ {
+ return _tag2SubscriptionMap.get(subscription);
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java
index 662f04b3c9..7d8c81f4d5 100644
--- a/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java
@@ -31,62 +31,54 @@ import org.apache.qpid.server.configuration.VirtualHostConfiguration;
import org.apache.qpid.server.exchange.Exchange;
import org.apache.qpid.server.queue.AMQQueue;
import org.apache.qpid.server.queue.MessageMetaData;
-import org.apache.qpid.server.transactionlog.TransactionLog;
-import org.apache.qpid.server.routing.RoutingTable;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.ArrayList;
-public class SlowMessageStore implements TransactionLog, RoutingTable
+public class SlowMessageStore implements MessageStore
{
private static final Logger _logger = Logger.getLogger(SlowMessageStore.class);
private static final String DELAYS = "delays";
private HashMap<String, Long> _preDelays = new HashMap<String, Long>();
private HashMap<String, Long> _postDelays = new HashMap<String, Long>();
private long _defaultDelay = 0L;
- private TransactionLog _realTransactionLog = new MemoryMessageStore();
- private RoutingTable _realRoutingTable = (RoutingTable)_realTransactionLog;
+ private MessageStore _realStore = new MemoryMessageStore();
private static final String PRE = "pre";
private static final String POST = "post";
private String DEFAULT_DELAY = "default";
- public Object configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception
+ public void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception
{
- _logger.warn("Starting SlowMessageStore on Virtualhost:" + virtualHost.getName());
+ _logger.info("Starting SlowMessageStore on Virtualhost:" + virtualHost.getName());
Configuration delays = config.getStoreConfiguration().subset(DELAYS);
configureDelays(delays);
- String transactionLogClass = config.getTransactionLogClass();
+ String messageStoreClass = config.getStoreConfiguration().getString("realStore");
if (delays.containsKey(DEFAULT_DELAY))
{
_defaultDelay = delays.getLong(DEFAULT_DELAY);
- _logger.warn("Delay is:" + _defaultDelay);
}
- if (transactionLogClass != null)
+ if (messageStoreClass != null)
{
- Class clazz = Class.forName(transactionLogClass);
- if (clazz != this.getClass())
- {
+ Class clazz = Class.forName(messageStoreClass);
- Object o = clazz.newInstance();
+ Object o = clazz.newInstance();
- if (!(o instanceof TransactionLog))
- {
- throw new ClassCastException("TransactionLog class must implement " + TransactionLog.class + ". Class " + clazz +
- " does not.");
- }
- _realTransactionLog = (TransactionLog) o;
+ if (!(o instanceof MessageStore))
+ {
+ throw new ClassCastException("Message store class must implement " + MessageStore.class + ". Class " + clazz +
+ " does not.");
}
+ _realStore = (MessageStore) o;
+ _realStore.configure(virtualHost, base + ".store", config);
+ }
+ else
+ {
+ _realStore.configure(virtualHost, base + ".store", config);
}
-
- // The call to configure may return a new transaction log
- _realTransactionLog = (TransactionLog) _realTransactionLog.configure(virtualHost, base , config);
-
- return this;
}
private void configureDelays(Configuration config)
@@ -159,35 +151,42 @@ public class SlowMessageStore implements TransactionLog, RoutingTable
public void close() throws Exception
{
doPreDelay("close");
- _realTransactionLog.close();
+ _realStore.close();
doPostDelay("close");
}
+ public void removeMessage(StoreContext storeContext, Long messageId) throws AMQException
+ {
+ doPreDelay("removeMessage");
+ _realStore.removeMessage(storeContext, messageId);
+ doPostDelay("removeMessage");
+ }
+
public void createExchange(Exchange exchange) throws AMQException
{
doPreDelay("createExchange");
- _realRoutingTable.createExchange(exchange);
+ _realStore.createExchange(exchange);
doPostDelay("createExchange");
}
public void removeExchange(Exchange exchange) throws AMQException
{
doPreDelay("removeExchange");
- _realRoutingTable.removeExchange(exchange);
+ _realStore.removeExchange(exchange);
doPostDelay("removeExchange");
}
public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
{
doPreDelay("bindQueue");
- _realRoutingTable.bindQueue(exchange, routingKey, queue, args);
+ _realStore.bindQueue(exchange, routingKey, queue, args);
doPostDelay("bindQueue");
}
public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException
{
doPreDelay("unbindQueue");
- _realRoutingTable.unbindQueue(exchange, routingKey, queue, args);
+ _realStore.unbindQueue(exchange, routingKey, queue, args);
doPostDelay("unbindQueue");
}
@@ -199,100 +198,101 @@ public class SlowMessageStore implements TransactionLog, RoutingTable
public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQException
{
doPreDelay("createQueue");
- _realRoutingTable.createQueue(queue, arguments);
+ _realStore.createQueue(queue, arguments);
doPostDelay("createQueue");
}
public void removeQueue(AMQQueue queue) throws AMQException
{
doPreDelay("removeQueue");
- _realRoutingTable.removeQueue(queue);
+ _realStore.removeQueue(queue);
doPostDelay("removeQueue");
}
- public void enqueueMessage(StoreContext context, ArrayList<AMQQueue> queues, Long messageId) throws AMQException
+ public void enqueueMessage(StoreContext context, AMQQueue queue, Long messageId) throws AMQException
{
doPreDelay("enqueueMessage");
- _realTransactionLog.enqueueMessage(context, queues, messageId);
+ _realStore.enqueueMessage(context, queue, messageId);
doPostDelay("enqueueMessage");
}
public void dequeueMessage(StoreContext context, AMQQueue queue, Long messageId) throws AMQException
{
doPreDelay("dequeueMessage");
- _realTransactionLog.dequeueMessage(context, queue, messageId);
- doPostDelay("dequeueMessage");
- }
-
- public void removeMessage(StoreContext context, Long messageId) throws AMQException
- {
- doPreDelay("dequeueMessage");
- _realTransactionLog.removeMessage(context, messageId);
+ _realStore.dequeueMessage(context, queue, messageId);
doPostDelay("dequeueMessage");
}
public void beginTran(StoreContext context) throws AMQException
{
doPreDelay("beginTran");
- _realTransactionLog.beginTran(context);
+ _realStore.beginTran(context);
doPostDelay("beginTran");
}
public void commitTran(StoreContext context) throws AMQException
{
doPreDelay("commitTran");
- _realTransactionLog.commitTran(context);
+ _realStore.commitTran(context);
doPostDelay("commitTran");
}
public void abortTran(StoreContext context) throws AMQException
{
doPreDelay("abortTran");
- _realTransactionLog.abortTran(context);
+ _realStore.abortTran(context);
doPostDelay("abortTran");
}
public boolean inTran(StoreContext context)
{
doPreDelay("inTran");
- boolean b = _realTransactionLog.inTran(context);
+ boolean b = _realStore.inTran(context);
doPostDelay("inTran");
return b;
}
+ public Long getNewMessageId()
+ {
+ doPreDelay("getNewMessageId");
+ Long l = _realStore.getNewMessageId();
+ doPostDelay("getNewMessageId");
+ return l;
+ }
+
public void storeContentBodyChunk(StoreContext context, Long messageId, int index, ContentChunk contentBody, boolean lastContentBody) throws AMQException
{
doPreDelay("storeContentBodyChunk");
- _realTransactionLog.storeContentBodyChunk(context, messageId, index, contentBody, lastContentBody);
+ _realStore.storeContentBodyChunk(context, messageId, index, contentBody, lastContentBody);
doPostDelay("storeContentBodyChunk");
}
public void storeMessageMetaData(StoreContext context, Long messageId, MessageMetaData messageMetaData) throws AMQException
{
doPreDelay("storeMessageMetaData");
- _realTransactionLog.storeMessageMetaData(context, messageId, messageMetaData);
+ _realStore.storeMessageMetaData(context, messageId, messageMetaData);
doPostDelay("storeMessageMetaData");
}
-// public MessageMetaData getMessageMetaData(StoreContext context, Long messageId) throws AMQException
-// {
-// doPreDelay("getMessageMetaData");
-// MessageMetaData mmd = _realTransactionLog.getMessageMetaData(context, messageId);
-// doPostDelay("getMessageMetaData");
-// return mmd;
-// }
-//
-// public ContentChunk getContentBodyChunk(StoreContext context, Long messageId, int index) throws AMQException
-// {
-// doPreDelay("getContentBodyChunk");
-// ContentChunk c = _realTransactionLog.getContentBodyChunk(context, messageId, index);
-// doPostDelay("getContentBodyChunk");
-// return c;
-// }
-//
+ public MessageMetaData getMessageMetaData(StoreContext context, Long messageId) throws AMQException
+ {
+ doPreDelay("getMessageMetaData");
+ MessageMetaData mmd = _realStore.getMessageMetaData(context, messageId);
+ doPostDelay("getMessageMetaData");
+ return mmd;
+ }
+
+ public ContentChunk getContentBodyChunk(StoreContext context, Long messageId, int index) throws AMQException
+ {
+ doPreDelay("getContentBodyChunk");
+ ContentChunk c = _realStore.getContentBodyChunk(context, messageId, index);
+ doPostDelay("getContentBodyChunk");
+ return c;
+ }
+
public boolean isPersistent()
{
- return _realTransactionLog.isPersistent();
+ return _realStore.isPersistent();
}
}