summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAidan Skinner <aidan@apache.org>2008-02-29 15:35:16 +0000
committerAidan Skinner <aidan@apache.org>2008-02-29 15:35:16 +0000
commit0858c0e1d36ce3585528ce792f60fecce790406c (patch)
treee99d76b8afb3193eb00048749406c4a427bf48af
parent4de99991220b090e6af3143b8fa39c9df5360915 (diff)
downloadqpid-python-0858c0e1d36ce3585528ce792f60fecce790406c.tar.gz
Copy M2.1 broker
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/thegreatmerge@632357 13f79535-47bb-0310-9956-ffa450edef68
-rwxr-xr-xqpid/java/broker/bin/msTool.sh60
-rw-r--r--qpid/java/broker/bin/qpid-passwd30
-rw-r--r--qpid/java/broker/bin/qpid-server31
-rw-r--r--qpid/java/broker/bin/qpid-server-bdb.bat22
-rw-r--r--qpid/java/broker/bin/qpid-server.bat70
-rw-r--r--qpid/java/broker/bin/qpid.start21
-rw-r--r--qpid/java/broker/bin/qpid.stop137
-rw-r--r--qpid/java/broker/bin/qpid.stopall74
-rwxr-xr-xqpid/java/broker/bin/run.bat31
-rwxr-xr-xqpid/java/broker/bin/run.sh44
-rw-r--r--qpid/java/broker/bin/runAll37
-rw-r--r--qpid/java/broker/distribution/pom.xml153
-rw-r--r--qpid/java/broker/distribution/src/main/assembly/broker-bin-tests.xml116
-rw-r--r--qpid/java/broker/distribution/src/main/assembly/broker-bin.xml183
-rw-r--r--qpid/java/broker/distribution/src/main/assembly/broker-src.xml78
-rw-r--r--qpid/java/broker/etc/access19
-rw-r--r--qpid/java/broker/etc/acl.config.xml229
-rw-r--r--qpid/java/broker/etc/config.xml128
-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.xml112
-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.xml115
-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.xml127
-rw-r--r--qpid/java/broker/etc/virtualhosts.xml123
-rw-r--r--qpid/java/broker/pom.xml272
-rwxr-xr-xqpid/java/broker/python-test.xml56
-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.java244
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java1021
-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/Main.java522
-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.java68
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java132
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessage.java91
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java80
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java235
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java118
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java269
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java217
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java113
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java138
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DestNameExchange.java260
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java579
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java97
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java40
-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.java240
-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.java360
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java90
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java98
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java40
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java48
-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.java40
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ComparisonExpression.java595
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java210
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/Expression.java37
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.java37
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java77
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java68
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/LogicExpression.java110
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java29
-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.java322
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java76
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/UnaryExpression.java337
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XPathExpression.java126
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XQueryExpression.java57
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XalanXPathEvaluator.java102
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java44
-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.java76
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java169
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicGetMethodHandler.java101
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java101
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java60
-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.java54
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRejectMethodHandler.java130
-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.java99
-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.java161
-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.java180
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java113
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java71
-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.java139
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java213
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java124
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueuePurgeHandler.java121
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueUnbindHandler.java113
-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.java80
-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/jms/JmsConsumer.java110
-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.java191
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java283
-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.java239
-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.java48
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagementConfiguration.java30
-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.java285
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9/ProtocolOutputConverterImpl.java372
-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.java145
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java788
-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.java277
-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.java179
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBean.java306
-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/HeartbeatConfig.java67
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java135
-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.java719
-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/AMQQueue.java1022
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueMBean.java479
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AsyncDeliveryConfig.java56
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ConcurrentSelectorDeliveryManager.java1077
-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/DeliveryManager.java102
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java135
-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/InMemoryMessageHandle.java143
-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/QueueEntry.java173
-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/Subscription.java63
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.java43
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java669
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.java34
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java274
-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/WeakReferenceMessageHandle.java227
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java26
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java203
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java187
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java75
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLManager.java161
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPlugin.java58
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessResult.java66
-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/Permission.java37
-rwxr-xr-xqpid/java/broker/src/main/java/org/apache/qpid/server/security/access/PrincipalPermissions.java587
-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.java468
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/UserManagement.java118
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java68
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/DenyAll.java57
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java431
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java43
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java598
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java236
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java240
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java100
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java34
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java160
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java48
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java37
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java241
-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.java47
-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.java129
-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.java149
-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/MemoryMessageStore.java223
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java261
-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.java68
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ConnectorConfiguration.java114
-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/CleanupMessageOperation.java77
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java262
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java228
-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.java170
-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.java131
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java44
-rw-r--r--qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java304
-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.java301
-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.java68
-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/RunBrokerWithCommand.java132
-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/exchange/DestWildExchangeTest.java612
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/ExchangeMBeanTest.java138
-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/protocol/TestIoSession.java295
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/TestMinaProtocolSession.java52
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java306
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java305
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java73
-rw-r--r--qpid/java/broker/src/test/java/org/apache/qpid/server/util/LoggingProxyTest.java88
276 files changed, 42377 insertions, 0 deletions
diff --git a/qpid/java/broker/bin/msTool.sh b/qpid/java/broker/bin/msTool.sh
new file mode 100755
index 0000000000..73b1eb2ec7
--- /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-incubating.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 100644
index 0000000000..f046252522
--- /dev/null
+++ b/qpid/java/broker/bin/qpid-passwd
@@ -0,0 +1,30 @@
+#!/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.
+#
+
+# Set classpath to include Qpid jar with all required jars in manifest
+QPID_LIBS=$QPID_HOME/lib/qpid-incubating.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 100644
index 0000000000..f1f4d72e64
--- /dev/null
+++ b/qpid/java/broker/bin/qpid-server
@@ -0,0 +1,31 @@
+#!/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.
+#
+
+# Set classpath to include Qpid jar with all required jars in manifest
+QPID_LIBS=$QPID_HOME/lib/qpid-incubating.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 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 100644
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 100644
index 0000000000..a99022cd2d
--- /dev/null
+++ b/qpid/java/broker/bin/qpid-server.bat
@@ -0,0 +1,70 @@
+@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
+
+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 exit
+: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 exit
+:okJavaHome
+
+rem Slurp the command line arguments. This loop allows for an unlimited number
+rem of agruments (up to the command line limit, anyway).
+set QPID_ARGS=%1
+if ""%1""=="""" goto runCommand
+shift
+:loop
+if ""%1""=="""" goto runCommand
+set QPID_ARGS=%QPID_ARGS% %1
+shift
+goto loop
+
+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 LAUNCH_JAR=%QPID_HOME%\lib\qpid-incubating.jar
+set MODULE_JARS=%QPID_MODULE_JARS%
+"%JAVA_HOME%\bin\java" -server -Xmx1024m %QPID_OPTS% -DQPID_HOME="%QPID_HOME%" -cp "%LAUNCH_JAR%;%MODULE_JARS%" org.apache.qpid.server.Main %QPID_ARGS%
+
+:end
diff --git a/qpid/java/broker/bin/qpid.start b/qpid/java/broker/bin/qpid.start
new file mode 100644
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 100644
index 0000000000..6482fc3293
--- /dev/null
+++ b/qpid/java/broker/bin/qpid.stop
@@ -0,0 +1,137 @@
+#!/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 PROGRAM and attempts to quit it
+#
+
+MAX_ATTEMPTS=1
+SLEEP_DELAY=1
+PROGRAM="DQPID"
+
+
+#
+# Print what is going to be done
+#
+printActions()
+{
+#ps=`ps o command p $1|grep $PROGRAM`
+ps=`ps -o args -p $1|grep $PROGRAM`
+echo "Attempting to kill: $ps"
+}
+
+#
+# Forcably Quit the specified PID($1)
+#
+forceQuit()
+{
+kill -9 $1
+}
+
+
+#
+# Gracefully ask the PID($1) to quit
+#
+quit()
+{
+kill $1
+}
+
+#
+# Grep the ps log for the PID ($1) to ensure that it has quit
+#
+lookup()
+{
+result=`ps -o args -p $1 |grep -v grep |grep $PROGRAM |wc -l`
+}
+
+#
+# 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 $1
+}
+
+
+
+#
+# Verify the PID($1) is available
+#
+verifyPid()
+{
+lookup $1
+if [[ $[$result] == 1 ]] ; then
+ brokerspid=$1
+else
+ echo "Unable to locate Qpid Process with PID $1"
+ exit -1
+fi
+}
+
+#
+# Main Run
+#
+
+# Check if we are killing all qpid pids or just one.
+if [[ $# == 0 ]] ; then
+ echo "Killing All Qpid Brokers for user: '$USER'"
+ qpid.stopall
+ exit $?
+else
+ verifyPid $1
+fi
+
+printActions $brokerspid
+
+# 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"
+ exit 0
+else
+
+ # Now attempt to force quit the process
+ attempt=0
+ 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
diff --git a/qpid/java/broker/bin/qpid.stopall b/qpid/java/broker/bin/qpid.stopall
new file mode 100644
index 0000000000..d71f591de8
--- /dev/null
+++ b/qpid/java/broker/bin/qpid.stopall
@@ -0,0 +1,74 @@
+#!/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
+#
+
+PROGRAM="DQPID"
+
+#
+# grep ps for instances of $PROGRAM and collect PIDs
+#
+lookup()
+{
+#pids=`ps o pid,command | grep $PROGRAM | grep -v grep | cut -d ' ' -f 1`
+pids=`ps -ef |grep $USER | grep $PROGRAM | grep -v grep | awk '{print $2}'`
+result=`echo -n $pids | wc -w`
+}
+
+
+#
+# Show the PS output for given set of pids
+#
+showPids()
+{
+ps -o user,pid,args -p $pids
+}
+
+
+#
+# Main Run
+#
+
+lookup
+
+if [[ $[$result] == 0 ]] ; then
+ echo "No Qpid Brokers found running under user '$USER'"
+ exit 0
+fi
+
+for pid in $pids ; do
+
+qpid.stop $pid
+
+done
+
+# Check we have quit all
+lookup
+
+if [[ $[$result] == 0 ]] ; then
+ echo "All Qpid brokers successfully quit"
+else
+ echo "Some brokers were not quit"
+ showPids $pids
+fi
diff --git a/qpid/java/broker/bin/run.bat b/qpid/java/broker/bin/run.bat
new file mode 100755
index 0000000000..5b0aa0f23b
--- /dev/null
+++ b/qpid/java/broker/bin/run.bat
@@ -0,0 +1,31 @@
+@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
+set CORE_CLASSES=..\classes;..\testclasses
+
+set COMMONDIR=..\..\common
+
+set COMMONLIB=%COMMONDIR%\lib\slf4j\slf4j-simple.jar;%COMMONDIR%\lib\logging-log4j\log4j-1.2.9.jar;%COMMONDIR%\lib\mina\mina-core-0.9.2.jar
+
+set DIST=..\lib\bdb\je-3.0.12.jar;..\dist\amqpd.jar;..\..\client\dist\amqp-common.jar
+
+set CP=%CORE_CLASSES%;%DIST%;%COMMONLIB%
+
+"%JAVA_HOME%\bin\java" -Xmx512m -Damqj.logging.level=INFO -cp %CP% %*
diff --git a/qpid/java/broker/bin/run.sh b/qpid/java/broker/bin/run.sh
new file mode 100755
index 0000000000..6b62049e94
--- /dev/null
+++ b/qpid/java/broker/bin/run.sh
@@ -0,0 +1,44 @@
+#!/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.
+#
+
+
+COMMONDIR=../../common
+
+COMMONLIB=$COMMONLIB:$COMMONDIR/lib/slf4j/slf4j-simple.jar
+COMMONLIB=$COMMONLIB:$COMMONDIR/lib/logging-log4j/log4j-1.2.13.jar
+COMMONLIB=$COMMONLIB:$COMMONDIR/lib/mina/mina-core-0.9.5-SNAPSHOT.jar
+COMMONLIB=$COMMONLIB:$COMMONDIR/lib/mina/mina-filter-ssl-0.9.5-SNAPSHOT.jar
+COMMONLIB=$COMMONLIB:$COMMONDIR/lib/commons-collections/commons-collections-3.1.jar
+COMMONLIB=$COMMONLIB:$COMMONDIR/lib/commons-cli/commons-cli-1.0.jar
+COMMONLIB=$COMMONLIB:$COMMONDIR/lib/commons-configuration/commons-configuration-1.2.jar
+COMMONLIB=$COMMONLIB:$COMMONDIR/lib/commons-logging/commons-logging-api.jar
+COMMONLIB=$COMMONLIB:$COMMONDIR/lib/commons-logging/commons-logging.jar
+COMMONLIB=$COMMONLIB:$COMMONDIR/lib/commons-lang/commons-lang-2.1.jar
+COMMONLIB=$COMMONLIB:$COMMONDIR/lib/junit/junit-4.0.jar
+
+DIST=../lib/bdb/je-3.0.12.jar:../dist/amqpd-tests.jar:../dist/amqpd.jar:../../client/dist/amqp-common.jar
+
+CP=../intellijclasses:$DIST:$COMMONLIB
+
+if [ "$(uname -a | fgrep Cygwin)" != "" ]; then
+ CP=$(cygpath --mixed --path $CP)
+fi
+
+"$JAVA_HOME/bin/java" -Xmx512m -Dprepopulate=10 -Damqj.logging.level=INFO -cp $CP $*
diff --git a/qpid/java/broker/bin/runAll b/qpid/java/broker/bin/runAll
new file mode 100644
index 0000000000..4ced1d263b
--- /dev/null
+++ b/qpid/java/broker/bin/runAll
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+doRun()
+{
+ class=$1
+ shift
+ echo
+ echo ================================================================================
+ echo Running $class
+ ./run.sh $class "$@"
+}
+
+# parameters are: clients messages iterations
+doRun org.apache.qpid.server.queue.SendPerfTest 10 1000 100
+
+doRun org.apache.qpid.server.queue.QueuePerfTest
+
+doRun org.apache.qpid.server.queue.QueueConcurrentPerfTest
diff --git a/qpid/java/broker/distribution/pom.xml b/qpid/java/broker/distribution/pom.xml
new file mode 100644
index 0000000000..9875e9bf2b
--- /dev/null
+++ b/qpid/java/broker/distribution/pom.xml
@@ -0,0 +1,153 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.qpid</groupId>
+ <artifactId>qpid-broker-distribution</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0-incubating-M2-SNAPSHOT</version>
+ <name>Qpid Broker Distributions</name>
+ <url>http://cwiki.apache.org/confluence/display/qpid</url>
+
+ <parent>
+ <groupId>org.apache.qpid</groupId>
+ <artifactId>qpid</artifactId>
+ <version>1.0-incubating-M2-SNAPSHOT</version>
+ </parent>
+
+ <properties>
+ <topDirectoryLocation>..</topDirectoryLocation>
+ <java.source.version>1.5</java.source.version>
+ <qpid.version>${pom.version}</qpid.version>
+ <qpid.targetDir>${project.build.directory}</qpid.targetDir>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.qpid</groupId>
+ <artifactId>qpid-broker</artifactId>
+ <type>jar</type>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${java.source.version}</source>
+ <target>${java.source.version}</target>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>${assembly.version}</version>
+ <configuration>
+ <descriptors>
+ <descriptor>src/main/assembly/broker-bin.xml</descriptor>
+ </descriptors>
+ <finalName>qpid-${pom.version}</finalName>
+ <outputDirectory>${qpid.targetDir}</outputDirectory>
+ <tarLongFileMode>gnu</tarLongFileMode>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <finalName>qpid-incubating</finalName>
+ <archive>
+ <manifest>
+ <addClasspath>true</addClasspath>
+ </manifest>
+ </archive>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </pluginManagement>
+
+ <plugins>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>distribution-package</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptors>
+ <descriptor>src/main/assembly/broker-bin.xml</descriptor>
+ <descriptor>src/main/assembly/broker-src.xml</descriptor>
+ </descriptors>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+
+ </build>
+
+ <profiles>
+ <profile>
+ <id>tests</id>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.qpid</groupId>
+ <artifactId>qpid-broker</artifactId>
+ <type>test-jar</type>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>distribution-package</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptors>
+ <descriptor>src/main/assembly/broker-bin-tests.xml</descriptor>
+ </descriptors>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/qpid/java/broker/distribution/src/main/assembly/broker-bin-tests.xml b/qpid/java/broker/distribution/src/main/assembly/broker-bin-tests.xml
new file mode 100644
index 0000000000..fa017d6232
--- /dev/null
+++ b/qpid/java/broker/distribution/src/main/assembly/broker-bin-tests.xml
@@ -0,0 +1,116 @@
+<?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.
+-->
+<assembly>
+ <id>java-broker-bin-with-tests</id>
+ <includeBaseDirectory>false</includeBaseDirectory>
+ <formats>
+ <format>tar.gz</format>
+ <format>zip</format>
+ </formats>
+
+ <fileSets>
+ <fileSet>
+ <!-- Apache license files -->
+ <directory>../../resources</directory>
+ <outputDirectory>qpid-${qpid.version}</outputDirectory>
+ <includes>
+ <include>DISCLAIMER</include>
+ <include>LICENSE.txt</include>
+ <include>NOTICE.txt</include>
+ <include>README.txt</include>
+ </includes>
+ </fileSet>
+
+ <fileSet>
+ <directory>../../release-docs</directory>
+ <outputDirectory>qpid-${qpid.version}/docs</outputDirectory>
+ <includes>
+ <include>RELEASE_NOTES.txt</include>
+ </includes>
+ </fileSet>
+
+ <!-- Include easy access to test source-->
+ <fileSet>
+ <directory>../src/test</directory>
+ <outputDirectory>qpid-${qpid.version}/src</outputDirectory>
+ <includes>
+ <include>**/*.java</include>
+ </includes>
+ </fileSet>
+
+ <!-- Execution Scripts -->
+ <fileSet>
+ <directory>../bin/</directory>
+ <outputDirectory>qpid-${qpid.version}/bin</outputDirectory>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ <fileMode>777</fileMode> <!-- RWX -->
+ </fileSet>
+
+ <!-- Configuration -->
+ <fileSet>
+ <directory>../etc/</directory>
+ <outputDirectory>qpid-${qpid.version}/etc</outputDirectory>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ <fileMode>420</fileMode>
+ </fileSet>
+
+ <!-- Metadata Jar -->
+ <fileSet>
+ <directory>target</directory>
+ <outputDirectory>qpid-${qpid.version}/lib</outputDirectory>
+ <includes>
+ <include>qpid-incubating.jar</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+
+
+ <files>
+ <!-- Common Run scripts -->
+ <file>
+ <source>../../common/bin/qpid-run</source>
+ <outputDirectory>qpid-${qpid.version}/bin</outputDirectory>
+ <destName>qpid-run</destName>
+ <fileMode>493</fileMode>
+ </file>
+
+ <!-- Common Configuration -->
+ <file>
+ <source>../../common/etc/qpid-run.conf</source>
+ <outputDirectory>qpid-${qpid.version}/etc</outputDirectory>
+ <destName>qpid-run.conf</destName>
+ <fileMode>420</fileMode>
+ </file>
+ </files>
+
+ <dependencySets>
+ <dependencySet>
+ <outputDirectory>qpid-${qpid.version}/lib</outputDirectory>
+ <unpack>false</unpack>
+ <excludes>
+ <exclude>org.apache.qpid:qpid-broker-distribution</exclude>
+ </excludes>
+ </dependencySet>
+ </dependencySets>
+</assembly>
diff --git a/qpid/java/broker/distribution/src/main/assembly/broker-bin.xml b/qpid/java/broker/distribution/src/main/assembly/broker-bin.xml
new file mode 100644
index 0000000000..e66190a3f4
--- /dev/null
+++ b/qpid/java/broker/distribution/src/main/assembly/broker-bin.xml
@@ -0,0 +1,183 @@
+<?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.
+-->
+<assembly>
+ <!-- id typically identifies the "type" (src vs bin etc) of the assembly -->
+ <id>java-broker-bin</id>
+ <includeBaseDirectory>false</includeBaseDirectory>
+ <formats>
+ <format>tar.gz</format>
+ <format>zip</format>
+ </formats>
+
+ <fileSets>
+ <!-- Apache Licensing -->
+ <fileSet>
+ <directory>../../resources</directory>
+ <outputDirectory>qpid-${qpid.version}</outputDirectory>
+ <includes>
+ <include>DISCLAIMER</include>
+ <include>LICENSE.txt</include>
+ <include>NOTICE.txt</include>
+ <include>README.txt</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>../..</directory>
+ <outputDirectory>qpid-${qpid.version}</outputDirectory>
+ <includes>
+ <include>*.txt</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>../../src/main/release-docs</directory>
+ <outputDirectory>qpid-${qpid.version}/docs</outputDirectory>
+ <includes>
+ <include>RELEASE_NOTES.txt</include>
+ </includes>
+ </fileSet>
+
+ <!-- Metadata Jar -->
+ <fileSet>
+ <directory>target</directory>
+ <outputDirectory>qpid-${qpid.version}/lib</outputDirectory>
+ <includes>
+ <include>qpid-incubating.jar</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+ <files>
+ <!-- due to a bug in the assembly plugin (MASSEMBLY-153) you have
+ to use decimal numbers to specify fileMode -->
+ <file>
+ <source>../../common/etc/qpid-run.conf</source>
+ <outputDirectory>qpid-${qpid.version}/etc</outputDirectory>
+ <destName>qpid-run.conf</destName>
+ <fileMode>420</fileMode>
+ </file>
+ <file>
+ <source>../etc/config.xml</source>
+ <outputDirectory>qpid-${qpid.version}/etc</outputDirectory>
+ <destName>config.xml</destName>
+ <fileMode>420</fileMode>
+ </file>
+ <file>
+ <source>../etc/jmxremote.access</source>
+ <outputDirectory>qpid-${qpid.version}/etc</outputDirectory>
+ <destName>jmxremote.access</destName>
+ <fileMode>420</fileMode>
+ </file>
+ <file>
+ <source>../etc/log4j.xml</source>
+ <outputDirectory>qpid-${qpid.version}/etc</outputDirectory>
+ <destName>log4j.xml</destName>
+ <fileMode>420</fileMode>
+ </file>
+ <file>
+ <source>../etc/passwd</source>
+ <outputDirectory>qpid-${qpid.version}/etc</outputDirectory>
+ <destName>passwd</destName>
+ <fileMode>420</fileMode>
+ </file>
+ <file>
+ <source>../etc/qpid-server.conf</source>
+ <outputDirectory>qpid-${qpid.version}/etc</outputDirectory>
+ <destName>qpid-server.conf</destName>
+ <fileMode>420</fileMode>
+ </file>
+ <file>
+ <source>../etc/virtualhosts.xml</source>
+ <outputDirectory>qpid-${qpid.version}/etc</outputDirectory>
+ <destName>virtualhosts.xml</destName>
+ <fileMode>420</fileMode>
+ </file>
+ <file>
+ <source>../../common/bin/qpid-run</source>
+ <outputDirectory>qpid-${qpid.version}/bin</outputDirectory>
+ <destName>qpid-run</destName>
+ <fileMode>473</fileMode>
+ </file>
+ <file>
+ <source>../bin/qpid-passwd</source>
+ <outputDirectory>qpid-${qpid.version}/bin</outputDirectory>
+ <destName>qpid-passwd</destName>
+ <fileMode>473</fileMode>
+ </file>
+ <file>
+ <source>../bin/qpid-server</source>
+ <outputDirectory>qpid-${qpid.version}/bin</outputDirectory>
+ <destName>qpid-server</destName>
+ <fileMode>473</fileMode>
+ </file>
+ <file>
+ <source>../bin/qpid-server.bat</source>
+ <outputDirectory>qpid-${qpid.version}/bin</outputDirectory>
+ <destName>qpid-server.bat</destName>
+ <fileMode>473</fileMode>
+ </file>
+ <file>
+ <source>../bin/run.bat</source>
+ <outputDirectory>qpid-${qpid.version}/bin</outputDirectory>
+ <destName>run.bat</destName>
+ <fileMode>473</fileMode>
+ </file>
+ <file>
+ <source>../bin/run.sh</source>
+ <outputDirectory>qpid-${qpid.version}/bin</outputDirectory>
+ <destName>run.sh</destName>
+ <fileMode>473</fileMode>
+ </file>
+ <file>
+ <source>../bin/runAll</source>
+ <outputDirectory>qpid-${qpid.version}/bin</outputDirectory>
+ <destName>runAll</destName>
+ <fileMode>473</fileMode>
+ </file>
+ </files>
+
+ <dependencySets>
+ <dependencySet>
+ <outputDirectory>qpid-${qpid.version}/lib</outputDirectory>
+ <unpack>false</unpack>
+ <!-- This needs to be tidied up QPID-280 -->
+ <excludes>
+ <exclude>org.apache.qpid:qpid-broker-distribution</exclude>
+ <exclude>org.apache.qpid.management:org.apache.qpid.management.ui</exclude>
+ <exclude>org.eclipse.core:org.eclipse.core.commands</exclude>
+ <exclude>org.eclipse.core:org.eclipse.core.contenttype</exclude>
+ <exclude>org.eclipse.core:org.eclipse.core.expressions</exclude>
+ <exclude>org.eclipse.core:org.eclipse.core.jobs</exclude>
+ <exclude>org.eclipse.core:org.eclipse.core.runtime</exclude>
+ <exclude>org.eclipse.core:org.eclipse.core.runtime.compatibility.auth</exclude>
+ <exclude>org.eclipse.core:org.eclipse.core.runtime.compatibility.registry</exclude>
+ <exclude>org.eclipse.equinox:org.eclipse.equinox.common</exclude>
+ <exclude>org.eclipse.equinox:org.eclipse.equinox.preferences</exclude>
+ <exclude>org.eclipse.equinox:org.eclipse.equinox.registry</exclude>
+ <exclude>org.eclipse.help:org.eclipse.help</exclude>
+ <exclude>org.eclipse.jface:org.eclipse.jface</exclude>
+ <exclude>org.eclipse.osgi:org.eclipse.osgi</exclude>
+ <exclude>org.eclipse.swt:org.eclipse.swt</exclude>
+ <exclude>org.eclipse.swt:org.eclipse.swt.win32.win32.x86</exclude>
+ <exclude>org.eclipse.ui:org.eclipse.ui</exclude>
+ <exclude>org.eclipse.ui:org.eclipse.ui.forms</exclude>
+ <exclude>org.eclipse.ui:org.eclipse.ui.workbench</exclude>
+ </excludes>
+ </dependencySet>
+ </dependencySets>
+</assembly>
diff --git a/qpid/java/broker/distribution/src/main/assembly/broker-src.xml b/qpid/java/broker/distribution/src/main/assembly/broker-src.xml
new file mode 100644
index 0000000000..28a22c3851
--- /dev/null
+++ b/qpid/java/broker/distribution/src/main/assembly/broker-src.xml
@@ -0,0 +1,78 @@
+<?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.
+-->
+<assembly>
+ <!-- id typically identifies the "type" (src vs bin etc) of the assembly -->
+ <id>java-broker-src</id>
+ <includeBaseDirectory>false</includeBaseDirectory>
+ <formats>
+ <format>tar.gz</format>
+ <format>zip</format>
+ </formats>
+
+ <fileSets>
+ <!-- Apache Licensing -->
+ <fileSet>
+ <directory>../../resources</directory>
+ <outputDirectory>qpid-${qpid.version}-src</outputDirectory>
+ <includes>
+ <include>DISCLAIMER</include>
+ <include>LICENSE.txt</include>
+ <include>licenses/*.*</include>
+ <include>NOTICE.txt</include>
+ <include>README.txt</include>
+ <include>BUILDING.txt</include>
+ </includes>
+ </fileSet>
+ <!-- Broker source -->
+ <fileSet>
+ <directory>..</directory>
+ <outputDirectory>qpid-${qpid.version}-src</outputDirectory>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ <!-- Tidy up wrt to QPID-280 -->
+ <excludes>
+ <exclude>build.xml</exclude>
+ <exclude>distribution/build.xml</exclude>
+ <exclude>benchmark</exclude>
+ <exclude>benchmark/**/*</exclude>
+ <exclude>**/target</exclude>
+ <exclude>**/target/**/*</exclude>
+ <exclude>**/build</exclude>
+ <exclude>**/build/**/*</exclude>
+ <exclude>**/.settings</exclude>
+ <exclude>**/.classpath</exclude>
+ <exclude>**/.project</exclude>
+ <exclude>**/.wtpmodules</exclude>
+ <exclude>**/surefire*</exclude>
+ <exclude>**/cobertura.ser</exclude>
+ <exclude>bin</exclude>
+ <exclude>bin/*</exclude>
+ <exclude>lib</exclude>
+ <exclude>lib/**/*</exclude>
+ <exclude>**/var/journal</exclude>
+ <exclude>**/build.out*</exclude>
+ <exclude>**/eclipse-plugin/bin/**</exclude>
+ <exclude>**/eclipse-plugin/plugins/**</exclude>
+ <exclude>**/eclipse-plugin/src/main/resources/**</exclude>
+ </excludes>
+ </fileSet>
+ </fileSets>
+</assembly>
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..701b71dbec
--- /dev/null
+++ b/qpid/java/broker/etc/acl.config.xml
@@ -0,0 +1,229 @@
+<?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>true</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.Base64MD5PasswordFilePrincipalDatabase</class>
+ <attributes>
+ <attribute>
+ <name>passwordFile</name>
+ <value>${conf}/qpid.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>4235g264</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_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>
+
+ <queue>
+ <auto_register>false</auto_register>
+ </queue>
+
+ <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..78d96e52fd
--- /dev/null
+++ b/qpid/java/broker/etc/config.xml
@@ -0,0 +1,128 @@
+<?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>false</protectio>
+ <transport>nio</transport>
+ <port>5672</port>
+ <sslport>8672</sslport>
+ <socketReceiveBuffer>32768</socketReceiveBuffer>
+ <socketSendBuffer>32768</socketSendBuffer>
+ </connector>
+ <management>
+ <enabled>true</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.AllowAll</class>
+ </access>
+ <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..71f9502b75
--- /dev/null
+++ b/qpid/java/broker/etc/debug.log4j.xml
@@ -0,0 +1,114 @@
+<?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="ArchivingFileAppender" class="org.apache.log4j.QpidCompositeRollingAppender">
+ <!-- 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 name="FileAppender" class="org.apache.log4j.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 name="AlertFile" class="org.apache.log4j.FileAppender">
+ <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 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"/>
+ </layout>
+ </appender>
+
+ <category name="Qpid.Broker">
+ <priority value="debug"/>
+ <appender-ref ref="AlertFile"/>
+ <!--appender-ref ref="STDOUT"/-->
+ </category>
+
+
+ <category name="org.apache.qpid.server.queue.AMQQueueMBean">
+ <priority value="info"/>
+ <appender-ref ref="AlertFile"/>
+ </category>
+
+
+ <!-- Provide warnings to standard output -->
+ <!--category 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 name="org.apache.qpid.server.txn">
+ <priority value="debug"/>
+ </category>-->
+
+ <!--<category 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..af8e7a8293
--- /dev/null
+++ b/qpid/java/broker/etc/log4j.xml
@@ -0,0 +1,112 @@
+<?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="ArchivingFileAppender" class="org.apache.log4j.QpidCompositeRollingAppender">
+ <!-- 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 name="FileAppender" class="org.apache.log4j.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 name="AlertFile" class="org.apache.log4j.FileAppender">
+ <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 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"/>
+ </layout>
+ </appender>
+
+ <category name="Qpid.Broker">
+ <priority value="debug"/>
+ <appender-ref ref="AlertFile"/>
+ <appender-ref ref="STDOUT"/>
+ </category>
+
+ <category name="org.apache.qpid.server.queue.AMQQueueMBean">
+ <priority value="info"/>
+ <appender-ref ref="AlertFile"/>
+ </category>
+
+ <!-- Provide warnings to standard output -->
+ <category name="org.apache.qpid">
+ <priority value="warn"/>
+ <appender-ref ref="STDOUT"/>
+ </category>
+
+
+ <!-- Examples of additional logging settings -->
+ <!-- Used to generate extra debug. See debug.log4j.xml -->
+
+ <!--<category name="org.apache.qpid.server.store">
+ <priority value="debug"/>
+ </category-->
+
+ <!--category name="org.apache.qpid.server.txn">
+ <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..2143009711
--- /dev/null
+++ b/qpid/java/broker/etc/persistent_config.xml
@@ -0,0 +1,115 @@
+<?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>
+ </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..c310094817
--- /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-incubating.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..5b13090f4c
--- /dev/null
+++ b/qpid/java/broker/etc/transient_config.xml
@@ -0,0 +1,127 @@
+<?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>
+ </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.PlainPasswordVhostFilePrincipalDatabase</class>
+ <attributes>
+ <attribute>
+ <name>passwordFile</name>
+ <value>${conf}/passwdVhost</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>
+
+ <security>
+ <access>
+ <class>org.apache.qpid.server.security.old.PrincipalDatabaseAccessManager</class>
+ <attributes>
+ <attribute>
+ <name>principalDatabase</name>
+ <value>passwordfile</value>
+ </attribute>
+ <attribute>
+ <name>defaultAccessManager</name>
+ <value>DenyAll</value>
+ </attribute>
+ </attributes>
+ </access>
+ </security>
+ </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/pom.xml b/qpid/java/broker/pom.xml
new file mode 100644
index 0000000000..1dd613e5f9
--- /dev/null
+++ b/qpid/java/broker/pom.xml
@@ -0,0 +1,272 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.qpid</groupId>
+ <artifactId>qpid-broker</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0-incubating-M2.1-SNAPSHOT</version>
+ <name>Qpid Broker</name>
+ <url>http://cwiki.apache.org/confluence/display/qpid</url>
+
+ <parent>
+ <groupId>org.apache.qpid</groupId>
+ <artifactId>qpid</artifactId>
+ <version>1.0-incubating-M2.1-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <properties>
+ <topDirectoryLocation>..</topDirectoryLocation>
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.qpid</groupId>
+ <artifactId>qpid-common</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>1.4.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <version>1.4.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-cli</groupId>
+ <artifactId>commons-cli</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-configuration</groupId>
+ <artifactId>commons-configuration</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>commons-digester</groupId>
+ <artifactId>commons-digester</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>xerces</groupId>
+ <artifactId>xercesImpl</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>xml-apis</groupId>
+ <artifactId>xml-apis</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+
+ <plugins>
+
+
+ <!--plugin>
+ <artifactId>minijar-maven-plugin</artifactId>
+ <groupId>org.codehaus.mojo</groupId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>minijars</goal>
+ </goals>
+ <configuration>
+ <stripUnusedClasses>true</stripUnusedClasses>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin-->
+
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>javacc-maven-plugin</artifactId>
+ <version>2.0</version>
+ <executions>
+ <execution>
+ <phase>generate-sources</phase>
+ <configuration>
+ <sourceDirectory>${basedir}/src/main/grammar</sourceDirectory>
+ <outputDirectory>${basedir}/target/generated-sources</outputDirectory>
+ <packageName>org.apache.qpid.server.filter.jms.selector</packageName>
+ </configuration>
+ <goals>
+ <goal>javacc</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemProperties>
+ <property>
+ <name>amqj.noAutoCreateVMBroker</name>
+ <value>true</value>
+ </property>
+ <property>
+ <name>amqj.logging.level</name>
+ <value>${amqj.logging.level}</value>
+ </property>
+ <property>
+ <name>log4j.configuration</name>
+ <value>file:///${basedir}/src/main/java/log4j.properties</value>
+ </property>
+ </systemProperties>
+ </configuration>
+ </plugin>
+
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+
+ <testResources>
+ <testResource>
+ <targetPath>src/</targetPath>
+ <filtering>false</filtering>
+ <directory>src/test/java</directory>
+ <includes>
+ <include>**/*.java</include>
+ </includes>
+ </testResource>
+ </testResources>
+
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>${antrun.version}</version>
+ <dependencies>
+ <dependency>
+ <groupId>ant</groupId>
+ <artifactId>ant-nodeps</artifactId>
+ <version>1.6.5</version>
+ </dependency>
+ </dependencies>
+
+ <executions>
+ <execution>
+ <id>python_test</id>
+ <phase>test</phase>
+ <configuration>
+ <tasks>
+
+ <condition property="skip-python-tests" value="true">
+ <isset property="skip.python.tests"/>
+ </condition>
+
+ <property name="command"
+ value="python run-tests -v -I java_failing.txt -b localhost:2110 -s ../specs/amqp.0-9.no-wip.xml"/>
+ <!--value="bash -c 'python run-tests -v -I java_failing.txt'"/>-->
+
+ <ant antfile="python-test.xml" inheritRefs="true">
+ <target name="run-tests" />
+ </ant>
+
+ </tasks>
+ </configuration>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+
+
+ </build>
+
+</project>
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/src/main/grammar/SelectorParser.jj b/qpid/java/broker/src/main/grammar/SelectorParser.jj
new file mode 100644
index 0000000000..f6a843e080
--- /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).initCause(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..9335723bc5
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQBrokerManagerMBean.java
@@ -0,0 +1,244 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.commons.configuration.Configuration;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.configuration.Configurator;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+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.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);
+
+ _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 = new AMQQueue(new AMQShortString(queueName), durable, ownerShortString, false, getVirtualHost());
+ if (queue.isDurable() && !queue.isAutoDelete())
+ {
+ _messageStore.createQueue(queue);
+ }
+
+ Configuration virtualHostDefaultQueueConfiguration =
+ VirtualHostConfiguration.getDefaultQueueConfiguration(queue);
+ if (virtualHostDefaultQueueConfiguration != null)
+ {
+ Configurator.configure(queue, virtualHostDefaultQueueConfiguration);
+ }
+
+ _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(new AMQShortString(queueName));
+
+ }
+ 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..5542fbc9b6
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java
@@ -0,0 +1,1021 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.configuration.Configured;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+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.UnacknowledgedMessage;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMapImpl;
+import org.apache.qpid.server.exchange.MessageRouter;
+import org.apache.qpid.server.exchange.NoRouteException;
+import org.apache.qpid.server.exchange.Exchange;
+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 org.apache.qpid.server.txn.LocalTransactionalContext;
+import org.apache.qpid.server.txn.NonTransactionalContext;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.server.configuration.Configurator;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class AMQChannel
+{
+ public static final int DEFAULT_PREFETCH = 5000;
+
+ private static final Logger _log = Logger.getLogger(AMQChannel.class);
+
+ private final int _channelId;
+
+ // private boolean _transactional;
+
+ private long _prefetch_HighWaterMark;
+
+ private long _prefetch_LowWaterMark;
+
+ private long _prefetchSize;
+
+ /**
+ * 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 AMQMessage _currentMessage;
+
+ /** Maps from consumer tag to queue instance. Allows us to unsubscribe from a queue. */
+ private final Map<AMQShortString, AMQQueue> _consumerTag2QueueMap = new HashMap<AMQShortString, AMQQueue>();
+
+ private final MessageStore _messageStore;
+
+ private UnacknowledgedMessageMap _unacknowledgedMessageMap = new UnacknowledgedMessageMapImpl(DEFAULT_PREFETCH);
+
+ private final AtomicBoolean _suspended = new AtomicBoolean(false);
+
+ private TransactionalContext _txnContext, _nonTransactedContext;
+
+ /**
+ * 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();
+
+ private Set<Long> _browsedAcks = new HashSet<Long>();
+
+ // Why do we need this reference ? - ritchiem
+ private final AMQProtocolSession _session;
+ private boolean _closing;
+
+ @Configured(path = "advanced.enableJMSXUserID",
+ defaultValue = "false")
+ public boolean ENABLE_JMSXUserID;
+
+
+ public AMQChannel(AMQProtocolSession session, int channelId, MessageStore messageStore)
+ throws AMQException
+ {
+ //Set values from configuration
+ Configurator.configure(this);
+
+ _session = session;
+ _channelId = channelId;
+ _storeContext = new StoreContext("Session: " + session.getClientIdentifier() + "; channel: " + channelId);
+ _prefetch_HighWaterMark = DEFAULT_PREFETCH;
+ _prefetch_LowWaterMark = _prefetch_HighWaterMark / 2;
+ _messageStore = messageStore;
+
+ // by default the session is non-transactional
+ _txnContext = new NonTransactionalContext(_messageStore, _storeContext, this, _returnMessages, _browsedAcks);
+ }
+
+ /** Sets this channel to be part of a local transaction */
+ public void setLocalTransactional()
+ {
+ _txnContext = new LocalTransactionalContext(_messageStore, _storeContext, _returnMessages);
+ }
+
+ 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 long getPrefetchCount()
+ {
+ return _prefetch_HighWaterMark;
+ }
+
+ public void setPrefetchCount(long prefetchCount)
+ {
+ _prefetch_HighWaterMark = prefetchCount;
+ }
+
+ public long getPrefetchSize()
+ {
+ return _prefetchSize;
+ }
+
+ public void setPrefetchSize(long prefetchSize)
+ {
+ _prefetchSize = prefetchSize;
+ }
+
+ public long getPrefetchLowMarkCount()
+ {
+ return _prefetch_LowWaterMark;
+ }
+
+ public void setPrefetchLowMarkCount(long prefetchCount)
+ {
+ _prefetch_LowWaterMark = prefetchCount;
+ }
+
+ public long getPrefetchHighMarkCount()
+ {
+ return _prefetch_HighWaterMark;
+ }
+
+ public void setPrefetchHighMarkCount(long prefetchCount)
+ {
+ _prefetch_HighWaterMark = prefetchCount;
+ }
+
+ public void setPublishFrame(MessagePublishInfo info, AMQProtocolSession publisher, final Exchange e) throws AMQException
+ {
+
+ _currentMessage = new AMQMessage(_messageStore.getNewMessageId(), info, _txnContext);
+ _currentMessage.setPublisher(publisher);
+ _currentMessage.setExchange(e);
+ }
+
+ public void publishContentHeader(ContentHeaderBody contentHeaderBody, AMQProtocolSession protocolSession)
+ throws AMQException
+ {
+ if (_currentMessage == null)
+ {
+ throw new AMQException("Received content header without previously receiving a BasicPublish frame");
+ }
+ else
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug(debugIdentity() + "Content header received on channel " + _channelId);
+ }
+
+ if (ENABLE_JMSXUserID)
+ {
+ //Set JMSXUserID
+ BasicContentHeaderProperties properties = (BasicContentHeaderProperties) contentHeaderBody.properties;
+ //fixme: fudge for QPID-677
+ properties.getHeaders().keySet();
+
+ properties.setUserId(protocolSession.getAuthorizedID().getName());
+ }
+
+ _currentMessage.setContentHeaderBody(contentHeaderBody);
+ _currentMessage.setExpiration();
+
+ routeCurrentMessage();
+ _currentMessage.routingComplete(_messageStore, _storeContext, _messageHandleFactory);
+
+ // check and deliver if header says body length is zero
+ if (contentHeaderBody.bodySize == 0)
+ {
+ _txnContext.messageProcessed(protocolSession);
+ _currentMessage = null;
+ }
+ }
+ }
+
+ public void publishContentBody(ContentBody contentBody, AMQProtocolSession protocolSession) 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
+ if (_currentMessage.addContentBodyFrame(_storeContext,
+ protocolSession.getMethodRegistry().getProtocolVersionMethodConverter().convertToContentChunk(
+ contentBody)))
+ {
+ // 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(protocolSession);
+ _currentMessage = null;
+ }
+ }
+ catch (AMQException e)
+ {
+ // we want to make sure we don't keep a reference to the message in the
+ // event of an error
+ _currentMessage = null;
+ throw e;
+ }
+ }
+
+ protected void routeCurrentMessage() throws AMQException
+ {
+ try
+ {
+ _currentMessage.route();
+ }
+ catch (NoRouteException e)
+ {
+ _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 session the protocol session of the subscriber
+ * @param noLocal Flag stopping own messages being receivied.
+ * @param exclusive Flag requesting exclusive access to the queue
+ * @param acks Are acks enabled for this subscriber
+ * @param filters Filters to apply to this subscriber
+ *
+ * @return the consumer tag. This is returned to the subscriber and used in subsequent unsubscribe requests
+ *
+ * @throws ConsumerTagNotUniqueException if the tag is not unique
+ * @throws AMQException if something goes wrong
+ */
+ public AMQShortString subscribeToQueue(AMQShortString tag, AMQQueue queue, AMQProtocolSession session, boolean acks,
+ FieldTable filters, boolean noLocal, boolean exclusive) throws AMQException, ConsumerTagNotUniqueException
+ {
+ if (tag == null)
+ {
+ tag = new AMQShortString("sgen_" + getNextConsumerTag());
+ }
+
+ if (_consumerTag2QueueMap.containsKey(tag))
+ {
+ throw new ConsumerTagNotUniqueException();
+ }
+
+ queue.registerProtocolSession(session, _channelId, tag, acks, filters, noLocal, exclusive);
+ _consumerTag2QueueMap.put(tag, queue);
+
+ return tag;
+ }
+
+ public void unsubscribeConsumer(AMQProtocolSession session, AMQShortString consumerTag) throws AMQException
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Unacked Map Dump size:" + _unacknowledgedMessageMap.size());
+ _unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor()
+ {
+
+ public boolean callback(UnacknowledgedMessage message) throws AMQException
+ {
+ _log.debug(message);
+
+ return true;
+ }
+
+ public void visitComplete()
+ {
+ }
+ });
+ }
+
+ AMQQueue q = _consumerTag2QueueMap.remove(consumerTag);
+ if (q != null)
+ {
+ q.unregisterProtocolSession(session, _channelId, consumerTag);
+ }
+ }
+
+ /**
+ * Called from the protocol session to close this channel and clean up. T
+ *
+ * @param session The session to close
+ *
+ * @throws AMQException if there is an error during closure
+ */
+ public void close(AMQProtocolSession session) throws AMQException
+ {
+ _txnContext.rollback();
+ unsubscribeAllConsumers(session);
+ 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(AMQProtocolSession session) throws AMQException
+ {
+ if (_log.isInfoEnabled())
+ {
+ if (!_consumerTag2QueueMap.isEmpty())
+ {
+ _log.info("Unsubscribing all consumers on channel " + toString());
+ }
+ else
+ {
+ _log.info("No consumers to unsubscribe on channel " + toString());
+ }
+ }
+
+ for (Map.Entry<AMQShortString, AMQQueue> me : _consumerTag2QueueMap.entrySet())
+ {
+ if (_log.isInfoEnabled())
+ {
+ _log.info("Unsubscribing consumer '" + me.getKey() + "' on channel " + toString());
+ }
+
+ me.getValue().unregisterProtocolSession(session, _channelId, me.getKey());
+ }
+
+ _consumerTag2QueueMap.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 consumerTag The tag for the consumer that is to acknowledge this message.
+ */
+ public void addUnacknowledgedMessage(QueueEntry entry, long deliveryTag, AMQShortString consumerTag)
+ {
+ 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 " + consumerTag);
+ }
+ }
+ }
+
+ synchronized (_unacknowledgedMessageMap.getLock())
+ {
+ _unacknowledgedMessageMap.add(deliveryTag, new UnacknowledgedMessage(entry, consumerTag, deliveryTag));
+ checkSuspension();
+ }
+ }
+
+ 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<UnacknowledgedMessage> 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))
+ {
+ // if (_nonTransactedContext == null)
+ {
+ _nonTransactedContext =
+ new NonTransactionalContext(_messageStore, _storeContext, this, _returnMessages, _browsedAcks);
+ }
+
+ deliveryContext = _nonTransactedContext;
+ }
+ else
+ {
+ deliveryContext = _txnContext;
+ }
+ }
+
+ for (UnacknowledgedMessage unacked : messagesToBeDelivered)
+ {
+ if (!unacked.isQueueDeleted())
+ {
+ // Ensure message is released for redelivery
+ unacked.entry.release();
+
+ // Mark message redelivered
+ unacked.getMessage().setRedelivered(true);
+
+ // Deliver Message
+ deliveryContext.deliver(unacked.entry, false);
+
+ // Should we allow access To the DM to directy deliver the message?
+ // As we don't need to check for Consumers or worry about incrementing the message count?
+ // unacked.queue.getDeliveryManager().deliver(_storeContext, unacked.queue.getName(), unacked.message, false);
+ }
+ }
+
+ }
+
+ /**
+ * Requeue a single message
+ *
+ * @param deliveryTag The message to requeue
+ *
+ * @throws AMQException If something goes wrong.
+ */
+ public void requeue(long deliveryTag) throws AMQException
+ {
+ UnacknowledgedMessage unacked = _unacknowledgedMessageMap.remove(deliveryTag);
+
+ if (unacked != null)
+ {
+
+ // Ensure message is released for redelivery
+ if (!unacked.isQueueDeleted())
+ {
+ unacked.entry.release();
+ }
+
+ // Mark message redelivered
+ unacked.getMessage().setRedelivered(true);
+
+ // 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))
+ {
+ // if (_nonTransactedContext == null)
+ {
+ _nonTransactedContext =
+ new NonTransactionalContext(_messageStore, _storeContext, this, _returnMessages, _browsedAcks);
+ }
+
+ deliveryContext = _nonTransactedContext;
+ }
+ else
+ {
+ deliveryContext = _txnContext;
+ }
+
+ if (!unacked.isQueueDeleted())
+ {
+ // Redeliver the messages to the front of the queue
+ deliveryContext.deliver(unacked.entry, true);
+ // 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.");
+ // _log.error("Requested requeue of message:" + deliveryTag +
+ // " but no queue defined using DeadLetter queue:" + getDeadLetterQueue());
+ //
+ // deliveryContext.deliver(unacked.message, getDeadLetterQueue(), false);
+ //
+ }
+ }
+ else
+ {
+ _log.warn("Requested requeue of message:" + deliveryTag + " but no such delivery tag exists."
+ + _unacknowledgedMessageMap.size());
+
+ if (_log.isDebugEnabled())
+ {
+ _unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor()
+ {
+ int count = 0;
+
+ public boolean callback(UnacknowledgedMessage message) throws AMQException
+ {
+ _log.debug(
+ (count++) + ": (" + message.getMessage().debugIdentity() + ")" + "[" + message.deliveryTag + "]");
+
+ return false; // Continue
+ }
+
+ public void visitComplete()
+ {
+ }
+ });
+ }
+ }
+
+ }
+
+ /**
+ * 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 List<UnacknowledgedMessage> msgToRequeue = new LinkedList<UnacknowledgedMessage>();
+ final List<UnacknowledgedMessage> msgToResend = new LinkedList<UnacknowledgedMessage>();
+
+ 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 UnacknowledgedMessageMap.Visitor()
+ {
+ public boolean callback(UnacknowledgedMessage message) throws AMQException
+ {
+ AMQShortString consumerTag = message.consumerTag;
+ AMQMessage msg = message.getMessage();
+ msg.setRedelivered(true);
+ if (consumerTag != null)
+ {
+ // Consumer exists
+ if (_consumerTag2QueueMap.containsKey(consumerTag))
+ {
+ msgToResend.add(message);
+ }
+ else // consumer has gone
+ {
+ msgToRequeue.add(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 (requeue)
+ {
+ msgToRequeue.add(message);
+ }
+ else
+ {
+ _log.info("No DeadLetter Queue and requeue not requested so dropping message:" + message);
+ }
+ }
+ else
+ {
+ _log.info("Message.queue is null and no DeadLetter Queue so dropping message:" + message);
+ }
+ }
+
+ // false means continue processing
+ return false;
+ }
+
+ public void visitComplete()
+ {
+ }
+ });
+
+ // 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 (UnacknowledgedMessage message : msgToResend)
+ {
+ AMQMessage msg = message.getMessage();
+
+ // 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
+ message.entry.release();
+
+ // 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.entry.getDeliveredSubscription();
+
+ if (sub != null)
+ {
+ // Get the lock so we can tell if the sub scription has closed.
+ // will stop delivery to this subscription until the lock is released.
+ // note: this approach would allow the use of a single queue if the
+ // PreDeliveryQueue would allow head additions.
+ // In the Java Qpid client we are suspended whilst doing this so it is all rather Mute..
+ // needs guidance from AMQP WG Model SIG
+ synchronized (sub.getSendLock())
+ {
+ if (sub.isClosed())
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Subscription(" + System.identityHashCode(sub)
+ + ") closed during resend so requeuing message");
+ }
+ // move this message to requeue
+ msgToRequeue.add(message);
+ }
+ else
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Requeuing " + msg.debugIdentity() + " for resend via sub:"
+ + System.identityHashCode(sub));
+ }
+
+ sub.addToResendQueue(message.entry);
+ _unacknowledgedMessageMap.remove(message.deliveryTag);
+ }
+ } // sync(sub.getSendLock)
+ }
+ else
+ {
+
+ if (_log.isInfoEnabled())
+ {
+ _log.info("DeliveredSubscription not recorded so just requeueing(" + message.toString()
+ + ")to prevent loss");
+ }
+ // move this message to requeue
+ msgToRequeue.add(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))
+ {
+ if (_nonTransactedContext == null)
+ {
+ _nonTransactedContext =
+ new NonTransactionalContext(_messageStore, _storeContext, this, _returnMessages, _browsedAcks);
+ }
+
+ deliveryContext = _nonTransactedContext;
+ }
+ else
+ {
+ deliveryContext = _txnContext;
+ }
+
+ // Process Messages to Requeue at the front of the queue
+ for (UnacknowledgedMessage message : msgToRequeue)
+ {
+ message.entry.release();
+ message.entry.setRedelivered(true);
+
+ deliveryContext.deliver(message.entry, true);
+
+ _unacknowledgedMessageMap.remove(message.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
+ *
+ * @throws org.apache.qpid.AMQException if there is an error processing the unacked messages
+ */
+ public void queueDeleted(final AMQQueue queue) throws AMQException
+ {
+ _unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor()
+ {
+ public boolean callback(UnacknowledgedMessage message) throws AMQException
+ {
+ if (message.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);
+ }
+ }
+
+ return false;
+ }
+
+ public void visitComplete()
+ {
+ }
+ });
+ }
+
+ /**
+ * Acknowledge one or more messages.
+ *
+ * @param deliveryTag the last delivery tag
+ * @param multiple if true will acknowledge all messages up to an including the delivery tag. if false only
+ * acknowledges the single message specified by the delivery tag
+ *
+ * @throws AMQException if the delivery tag is unknown (e.g. not outstanding) on this channel
+ */
+ public void acknowledgeMessage(long deliveryTag, boolean multiple) throws AMQException
+ {
+ synchronized (_unacknowledgedMessageMap.getLock())
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Unacked (PreAck) Size:" + _unacknowledgedMessageMap.size());
+ }
+
+ _unacknowledgedMessageMap.acknowledgeMessage(deliveryTag, multiple, _txnContext);
+
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Unacked (PostAck) Size:" + _unacknowledgedMessageMap.size());
+ }
+
+ }
+
+ checkSuspension();
+ }
+
+ /**
+ * Used only for testing purposes.
+ *
+ * @return the map of unacknowledged messages
+ */
+ public UnacknowledgedMessageMap getUnacknowledgedMessageMap()
+ {
+ return _unacknowledgedMessageMap;
+ }
+
+ private void checkSuspension()
+ {
+ boolean suspend;
+
+ suspend =
+ ((_prefetch_HighWaterMark != 0) && (_unacknowledgedMessageMap.size() >= _prefetch_HighWaterMark))
+ || ((_prefetchSize != 0) && (_prefetchSize < _unacknowledgedMessageMap.getUnacknowledgeBytes()));
+
+ setSuspended(suspend);
+ }
+
+ public void setSuspended(boolean suspended)
+ {
+ boolean isSuspended = _suspended.get();
+
+ if (isSuspended && !suspended)
+ {
+ // Continue being suspended if we are above the _prefetch_LowWaterMark
+ suspended = _unacknowledgedMessageMap.size() > _prefetch_LowWaterMark;
+ }
+
+ boolean wasSuspended = _suspended.getAndSet(suspended);
+ if (wasSuspended != suspended)
+ {
+ if (wasSuspended)
+ {
+ _log.debug("Unsuspending channel " + this);
+ // may need to deliver queued messages
+ for (AMQQueue q : _consumerTag2QueueMap.values())
+ {
+ q.deliverAsync();
+ }
+ }
+ else
+ {
+ _log.debug("Suspending channel " + this);
+ }
+ }
+ }
+
+ public boolean isSuspended()
+ {
+ return _suspended.get();
+ }
+
+ public void commit() throws AMQException
+ {
+ if (!isTransactional())
+ {
+ throw new AMQException("Fatal error: commit called on non-transactional channel");
+ }
+
+ _txnContext.commit();
+ }
+
+ public void rollback() throws AMQException
+ {
+ _txnContext.rollback();
+ }
+
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(30);
+ sb.append("Channel: id ").append(_channelId).append(", transaction mode: ").append(isTransactional());
+ sb.append(", prefetch marks: ").append(_prefetch_LowWaterMark);
+ sb.append("/").append(_prefetch_HighWaterMark);
+
+ return sb.toString();
+ }
+
+ public void setDefaultQueue(AMQQueue queue)
+ {
+ _defaultQueue = queue;
+ }
+
+ public AMQQueue getDefaultQueue()
+ {
+ return _defaultQueue;
+ }
+
+ public StoreContext getStoreContext()
+ {
+ return _storeContext;
+ }
+
+ public void processReturns(AMQProtocolSession session) 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 boolean wouldSuspend(AMQMessage msg)
+ {
+ if (isSuspended())
+ {
+ return true;
+ }
+ else
+ {
+ boolean willSuspend =
+ ((_prefetch_HighWaterMark != 0) && ((_unacknowledgedMessageMap.size() + 1) > _prefetch_HighWaterMark));
+ if (!willSuspend)
+ {
+ final long unackedSize = _unacknowledgedMessageMap.getUnacknowledgeBytes();
+
+ willSuspend = (_prefetchSize != 0) && (unackedSize != 0) && (_prefetchSize < (msg.getSize() + unackedSize));
+ }
+
+ if (willSuspend)
+ {
+ setSuspended(true);
+ }
+
+ return willSuspend;
+ }
+
+ }
+
+ public TransactionalContext getTransactionalContext()
+ {
+ return _txnContext;
+ }
+
+ public boolean isClosing()
+ {
+ return _closing;
+ }
+}
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/Main.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java
new file mode 100644
index 0000000000..d8a8cfb6d1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java
@@ -0,0 +1,522 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.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.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.Logger;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.apache.mina.common.ByteBuffer;
+import org.apache.mina.common.IoAcceptor;
+import org.apache.mina.common.SimpleByteBufferAllocator;
+import org.apache.mina.common.FixedSizeByteBufferAllocator;
+import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
+import org.apache.mina.transport.socket.nio.SocketSessionConfig;
+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.VirtualHostConfiguration;
+import org.apache.qpid.server.management.JMXManagedObjectRegistry;
+import org.apache.qpid.server.protocol.AMQPFastProtocolHandler;
+import org.apache.qpid.server.protocol.AMQPProtocolProvider;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry;
+import org.apache.qpid.server.transport.ConnectorConfiguration;
+import org.apache.qpid.url.URLSyntaxException;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.BindException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 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());
+
+ }
+ catch (ConfigurationException e)
+ {
+ System.out.println("Error configuring message broker: " + e);
+ _brokerLogger.error("Error configuring message broker: " + e);
+ e.printStackTrace();
+ }
+ catch (Exception e)
+ {
+ System.out.println("Error intialising message broker: " + e);
+ _brokerLogger.error("Error intialising message broker: " + e);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ protected void startup() throws InitException, ConfigurationException, Exception
+ {
+ final String QpidHome = System.getProperty(QPID_HOME);
+ final File defaultConfigFile = new File(QpidHome, DEFAULT_CONFIG_FILE);
+ final File configFile = new File(commandLine.getOptionValue("c", defaultConfigFile.getPath()));
+ if (!configFile.exists())
+ {
+ String error = "File " + configFile + " could not be found. Check the file exists and is readable.";
+
+ if (QpidHome == null)
+ {
+ error = error + "\nNote: " + QPID_HOME + " is not set.";
+ }
+
+ throw new InitException(error, null);
+ }
+ else
+ {
+ System.out.println("Using configuration file " + configFile.getAbsolutePath());
+ }
+
+ String logConfig = commandLine.getOptionValue("l");
+ String logWatchConfig = commandLine.getOptionValue("w", "0");
+ if (logConfig != null)
+ {
+ File logConfigFile = new File(logConfig);
+ configureLogging(logConfigFile, logWatchConfig);
+ }
+ else
+ {
+ File configFileDirectory = configFile.getParentFile();
+ File logConfigFile = new File(configFileDirectory, DEFAULT_LOG_CONFIG_FILENAME);
+ configureLogging(logConfigFile, logWatchConfig);
+ }
+
+ ConfigurationFileApplicationRegistry config = new ConfigurationFileApplicationRegistry(configFile);
+
+
+ updateManagementPort(config.getConfiguration(), commandLine.getOptionValue("m"));
+
+
+
+ ApplicationRegistry.initialise(config);
+
+
+ //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());
+
+ ConnectorConfiguration connectorConfig =
+ ApplicationRegistry.getInstance().getConfiguredObject(ConnectorConfiguration.class);
+
+ ByteBuffer.setUseDirectBuffers(connectorConfig.enableDirectBuffers);
+
+ // the MINA default is currently to use the pooled allocator although this may change in future
+ // once more testing of the performance of the simple allocator has been done
+ if (!connectorConfig.enablePooledAllocator)
+ {
+ ByteBuffer.setAllocator(new FixedSizeByteBufferAllocator());
+ }
+
+ int port = connectorConfig.port;
+
+ String portStr = commandLine.getOptionValue("p");
+ if (portStr != null)
+ {
+ try
+ {
+ port = Integer.parseInt(portStr);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new InitException("Invalid port: " + portStr, e);
+ }
+ }
+
+ String VIRTUAL_HOSTS = "virtualhosts";
+
+ Object virtualHosts = ApplicationRegistry.getInstance().getConfiguration().getProperty(VIRTUAL_HOSTS);
+
+ if (virtualHosts != null)
+ {
+ if (virtualHosts instanceof Collection)
+ {
+ int totalVHosts = ((Collection) virtualHosts).size();
+ for (int vhost = 0; vhost < totalVHosts; vhost++)
+ {
+ setupVirtualHosts(configFile.getParent(), (String) ((List) virtualHosts).get(vhost));
+ }
+ }
+ else
+ {
+ setupVirtualHosts(configFile.getParent(), (String) virtualHosts);
+ }
+ }
+
+ bind(port, connectorConfig);
+
+ }
+
+ /**
+ * Update the configuration data with the management port.
+ * @param configuration
+ * @param managementPort The string from the command line
+ */
+ private void updateManagementPort(Configuration configuration, String managementPort)
+ {
+ if (managementPort != null)
+ {
+ int mport;
+ int defaultMPort = configuration.getInt(JMXManagedObjectRegistry.MANAGEMENT_PORT_CONFIG_PATH);
+ try
+ {
+ mport = Integer.parseInt(managementPort);
+ configuration.setProperty(JMXManagedObjectRegistry.MANAGEMENT_PORT_CONFIG_PATH, mport);
+ }
+ catch (NumberFormatException e)
+ {
+ _logger.warn("Invalid management port: " + managementPort + " will use default:" + defaultMPort, e);
+ }
+ }
+ }
+
+ protected void setupVirtualHosts(String configFileParent, String configFilePath)
+ throws ConfigurationException, AMQException, URLSyntaxException
+ {
+ String configVar = "${conf}";
+
+ if (configFilePath.startsWith(configVar))
+ {
+ configFilePath = configFileParent + configFilePath.substring(configVar.length());
+ }
+
+ if (configFilePath.indexOf(".xml") != -1)
+ {
+ VirtualHostConfiguration vHostConfig = new VirtualHostConfiguration(configFilePath);
+ vHostConfig.performBindings();
+ }
+ else
+ {
+ // the virtualhosts value is a path. Search it for XML files.
+
+ File virtualHostDir = new File(configFilePath);
+
+ String[] fileNames = virtualHostDir.list();
+
+ for (int each = 0; each < fileNames.length; each++)
+ {
+ if (fileNames[each].endsWith(".xml"))
+ {
+ VirtualHostConfiguration vHostConfig =
+ new VirtualHostConfiguration(configFilePath + "/" + fileNames[each]);
+ vHostConfig.performBindings();
+ }
+ }
+ }
+ }
+
+ protected void bind(int port, ConnectorConfiguration connectorConfig) throws BindException
+ {
+ String bindAddr = commandLine.getOptionValue("b");
+ if (bindAddr == null)
+ {
+ bindAddr = connectorConfig.bindAddress;
+ }
+
+ try
+ {
+ // IoAcceptor acceptor = new SocketAcceptor(connectorConfig.processors);
+ IoAcceptor acceptor = connectorConfig.createAcceptor();
+ SocketAcceptorConfig sconfig = (SocketAcceptorConfig) acceptor.getDefaultConfig();
+ SocketSessionConfig sc = (SocketSessionConfig) sconfig.getSessionConfig();
+
+ sc.setReceiveBufferSize(connectorConfig.socketReceiveBufferSize);
+ sc.setSendBufferSize(connectorConfig.socketWriteBuferSize);
+ sc.setTcpNoDelay(connectorConfig.tcpNoDelay);
+
+ // if we do not use the executor pool threading model we get the default leader follower
+ // implementation provided by MINA
+ if (connectorConfig.enableExecutorPool)
+ {
+ sconfig.setThreadModel(ReadWriteThreadModel.getInstance());
+ }
+
+ if (!connectorConfig.enableSSL || !connectorConfig.sslOnly)
+ {
+ AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler();
+ InetSocketAddress bindAddress;
+ if (bindAddr.equals("wildcard"))
+ {
+ bindAddress = new InetSocketAddress(port);
+ }
+ else
+ {
+ bindAddress = new InetSocketAddress(InetAddress.getByAddress(parseIP(bindAddr)), port);
+ }
+
+ acceptor.bind(bindAddress, handler, sconfig);
+ //fixme qpid.AMQP should be using qpidproperties to get value
+ _brokerLogger.info("Qpid.AMQP listening on non-SSL address " + bindAddress);
+ }
+
+ if (connectorConfig.enableSSL)
+ {
+ AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler();
+ try
+ {
+
+ acceptor.bind(new InetSocketAddress(connectorConfig.sslPort), handler, sconfig);
+ //fixme qpid.AMQP should be using qpidproperties to get value
+ _brokerLogger.info("Qpid.AMQP listening on SSL port " + connectorConfig.sslPort);
+
+ }
+ 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());
+ }
+ }
+
+ 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, String logWatchConfig)
+ {
+ int logWatchTime = 0;
+ try
+ {
+ logWatchTime = Integer.parseInt(logWatchConfig);
+ }
+ catch (NumberFormatException e)
+ {
+ System.err.println("Log watch configuration value of " + logWatchConfig + " is invalid. Must be "
+ + "a non-negative integer. Using default of zero (no watching configured");
+ }
+
+ if (logConfigFile.exists() && logConfigFile.canRead())
+ {
+ System.out.println("Configuring logger using configuration file " + logConfigFile.getAbsolutePath());
+ if (logWatchTime > 0)
+ {
+ System.out.println("log file " + logConfigFile.getAbsolutePath() + " will be checked for changes every "
+ + logWatchTime + " seconds");
+ // log4j expects the watch interval in milliseconds
+ DOMConfigurator.configureAndWatch(logConfigFile.getAbsolutePath(), logWatchTime * 1000);
+ }
+ else
+ {
+ DOMConfigurator.configure(logConfigFile.getAbsolutePath());
+ }
+ }
+ else
+ {
+ System.err.println("Logging configuration error: unable to read file " + logConfigFile.getAbsolutePath());
+ System.err.println("Using basic log4j configuration");
+ BasicConfigurator.configure();
+ }
+ }
+
+}
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..d61bb8916a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.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 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 final AMQMessage _amqMessage;
+
+ public RequiredDeliveryException(String message, AMQMessage payload)
+ {
+ super(message);
+
+ // 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();
+
+ // payload.incrementReference();
+ }
+
+ 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..c62a7880a8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java
@@ -0,0 +1,132 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.ack;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.txn.TxnOp;
+
+/**
+ * A TxnOp implementation for handling accumulated acks
+ */
+public class TxAck implements TxnOp
+{
+ private final UnacknowledgedMessageMap _map;
+ private final List <UnacknowledgedMessage> _unacked = new LinkedList<UnacknowledgedMessage>();
+ private final List<Long> _individual = new LinkedList<Long>();
+ private long _deliveryTag;
+ private boolean _multiple;
+
+ public TxAck(UnacknowledgedMessageMap map)
+ {
+ _map = map;
+ }
+
+ public void update(long deliveryTag, boolean multiple)
+ {
+ if (!multiple)
+ {
+ //have acked a single message that is not part of
+ //the previously acked region so record
+ //individually
+ _individual.add(deliveryTag);//_multiple && !multiple
+ }
+ else if (deliveryTag > _deliveryTag)
+ {
+ //have simply moved the last acked message on a
+ //bit
+ _deliveryTag = deliveryTag;
+ _multiple = true;
+ }
+ }
+
+ public void consolidate()
+ {
+ //lookup all the unacked messages that have been acked in this transaction
+ if (_multiple)
+ {
+ //get all the unacked messages for the accumulated
+ //multiple acks
+ _map.collect(_deliveryTag, true, _unacked);
+ }
+ //get any unacked messages for individual acks outside the
+ //range covered by multiple acks
+ for (long tag : _individual)
+ {
+ if(_deliveryTag < tag)
+ {
+ _map.collect(tag, false, _unacked);
+ }
+ }
+ }
+
+ public boolean checkPersistent() throws AMQException
+ {
+ //if any of the messages in unacked are persistent the txn
+ //buffer must be marked as persistent:
+ for (UnacknowledgedMessage msg : _unacked)
+ {
+ if (msg.getMessage().isPersistent())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void prepare(StoreContext storeContext) throws AMQException
+ {
+ //make persistent changes, i.e. dequeue and decrementReference
+ for (UnacknowledgedMessage msg : _unacked)
+ {
+ //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 (UnacknowledgedMessage msg : _unacked)
+ {
+ 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/UnacknowledgedMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessage.java
new file mode 100644
index 0000000000..df7cecc940
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessage.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.ack;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.store.StoreContext;
+
+public class UnacknowledgedMessage
+{
+ public final QueueEntry entry;
+ public final AMQShortString consumerTag;
+ public final long deliveryTag;
+
+ private boolean _queueDeleted;
+
+
+ public UnacknowledgedMessage(QueueEntry entry, AMQShortString consumerTag, long deliveryTag)
+ {
+ this.entry = entry;
+ this.consumerTag = consumerTag;
+ this.deliveryTag = deliveryTag;
+ }
+
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Q:");
+ sb.append(entry.getQueue());
+ sb.append(" M:");
+ sb.append(entry.getMessage());
+ sb.append(" CT:");
+ sb.append(consumerTag);
+ sb.append(" DT:");
+ sb.append(deliveryTag);
+
+ return sb.toString();
+ }
+
+ public void discard(StoreContext storeContext) throws AMQException
+ {
+ if (entry.getQueue() != null)
+ {
+ entry.getQueue().dequeue(storeContext, entry);
+ }
+ //if the queue is null then the message is waiting to be acked, but has been removed.
+ entry.getMessage().decrementReference(storeContext);
+ }
+
+ public AMQMessage getMessage()
+ {
+ return entry.getMessage();
+ }
+
+ public AMQQueue getQueue()
+ {
+ return entry.getQueue();
+ }
+
+ public void setQueueDeleted(boolean queueDeleted)
+ {
+ _queueDeleted = queueDeleted;
+ }
+
+ public boolean isQueueDeleted()
+ {
+ return _queueDeleted;
+ }
+}
+
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..b69a917081
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java
@@ -0,0 +1,80 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.ack;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.txn.TransactionalContext;
+
+public interface UnacknowledgedMessageMap
+{
+ public interface Visitor
+ {
+ /**
+ * @param message the message being iterated over
+ * @return true to stop iteration, false to continue
+ * @throws AMQException
+ */
+ boolean callback(UnacknowledgedMessage message) throws AMQException;
+
+ void visitComplete();
+ }
+
+ void visit(Visitor visitor) throws AMQException;
+
+ Object getLock();
+
+ void add(long deliveryTag, UnacknowledgedMessage message);
+
+ void collect(long deliveryTag, boolean multiple, List<UnacknowledgedMessage> msgs);
+
+ boolean contains(long deliveryTag) throws AMQException;
+
+ void remove(List<UnacknowledgedMessage> msgs);
+
+ UnacknowledgedMessage remove(long deliveryTag);
+
+ void drainTo(Collection<UnacknowledgedMessage> destination, long deliveryTag) throws AMQException;
+
+ Collection<UnacknowledgedMessage> cancelAllMessages();
+
+ void acknowledgeMessage(long deliveryTag, boolean multiple, TransactionalContext txnContext) throws AMQException;
+
+ int size();
+
+ void clear();
+
+ UnacknowledgedMessage 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..20ee646a40
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.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.ack;
+
+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.txn.TransactionalContext;
+
+public class UnacknowledgedMessageMapImpl implements UnacknowledgedMessageMap
+{
+ private final Object _lock = new Object();
+
+ private long _unackedSize;
+
+ private Map<Long, UnacknowledgedMessage> _map;
+
+ private long _lastDeliveryTag;
+
+ private final int _prefetchLimit;
+
+ public UnacknowledgedMessageMapImpl(int prefetchLimit)
+ {
+ _prefetchLimit = prefetchLimit;
+ _map = new LinkedHashMap<Long, UnacknowledgedMessage>(prefetchLimit);
+ }
+
+ /*public UnacknowledgedMessageMapImpl(Object lock, Map<Long, UnacknowledgedMessage> map)
+ {
+ _lock = lock;
+ _map = map;
+ } */
+
+ public void collect(long deliveryTag, boolean multiple, List<UnacknowledgedMessage> msgs)
+ {
+ if (multiple)
+ {
+ collect(deliveryTag, msgs);
+ }
+ else
+ {
+ msgs.add(get(deliveryTag));
+ }
+
+ }
+
+ public boolean contains(long deliveryTag) throws AMQException
+ {
+ synchronized (_lock)
+ {
+ return _map.containsKey(deliveryTag);
+ }
+ }
+
+ public void remove(List<UnacknowledgedMessage> msgs)
+ {
+ synchronized (_lock)
+ {
+ for (UnacknowledgedMessage msg : msgs)
+ {
+ remove(msg.deliveryTag);
+ }
+ }
+ }
+
+ public UnacknowledgedMessage remove(long deliveryTag)
+ {
+ synchronized (_lock)
+ {
+
+ UnacknowledgedMessage message = _map.remove(deliveryTag);
+ if(message != null)
+ {
+ _unackedSize -= message.getMessage().getSize();
+ }
+
+ return message;
+ }
+ }
+
+ public void visit(Visitor visitor) throws AMQException
+ {
+ synchronized (_lock)
+ {
+ Collection<UnacknowledgedMessage> currentEntries = _map.values();
+ for (UnacknowledgedMessage msg : currentEntries)
+ {
+ visitor.callback(msg);
+ }
+ visitor.visitComplete();
+ }
+ }
+
+ public Object getLock()
+ {
+ return _lock;
+ }
+
+ public void add(long deliveryTag, UnacknowledgedMessage message)
+ {
+ synchronized (_lock)
+ {
+ _map.put(deliveryTag, message);
+ _unackedSize += message.getMessage().getSize();
+ _lastDeliveryTag = deliveryTag;
+ }
+ }
+
+ public Collection<UnacknowledgedMessage> cancelAllMessages()
+ {
+ synchronized (_lock)
+ {
+ Collection<UnacknowledgedMessage> currentEntries = _map.values();
+ _map = new LinkedHashMap<Long, UnacknowledgedMessage>(_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(Collection<UnacknowledgedMessage> destination, long deliveryTag) throws AMQException
+ {
+ synchronized (_lock)
+ {
+ Iterator<Map.Entry<Long, UnacknowledgedMessage>> it = _map.entrySet().iterator();
+ while (it.hasNext())
+ {
+ Map.Entry<Long, UnacknowledgedMessage> unacked = it.next();
+
+ if (unacked.getKey() > deliveryTag)
+ {
+ //This should not occur now.
+ throw new AMQException("UnacknowledgedMessageMap is out of order:" + unacked.getKey() +
+ " When deliveryTag is:" + deliveryTag + "ES:" + _map.entrySet().toString());
+ }
+
+ it.remove();
+ _unackedSize -= unacked.getValue().getMessage().getSize();
+
+ destination.add(unacked.getValue());
+ if (unacked.getKey() == deliveryTag)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ public UnacknowledgedMessage get(long key)
+ {
+ synchronized (_lock)
+ {
+ return _map.get(key);
+ }
+ }
+
+ public Set<Long> getDeliveryTags()
+ {
+ synchronized (_lock)
+ {
+ return _map.keySet();
+ }
+ }
+
+ private void collect(long key, List<UnacknowledgedMessage> msgs)
+ {
+ synchronized (_lock)
+ {
+ for (Map.Entry<Long, UnacknowledgedMessage> entry : _map.entrySet())
+ {
+ msgs.add(entry.getValue());
+ if (entry.getKey() == key)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ public long getUnacknowledgeBytes()
+ {
+ return _unackedSize;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java
new file mode 100644
index 0000000000..31c1b61a21
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/Configurator.java
@@ -0,0 +1,118 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.configuration;
+
+import java.lang.reflect.Field;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.log4j.Logger;
+import org.apache.qpid.configuration.Configured;
+import org.apache.qpid.configuration.PropertyException;
+import org.apache.qpid.configuration.PropertyUtils;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+/**
+ * This class contains utilities for populating classes automatically from values pulled from configuration
+ * files.
+ */
+public class Configurator
+{
+ private static final Logger _logger = Logger.getLogger(Configurator.class);
+
+
+ /**
+ * Configure a given instance using the supplied configuration. Note that superclasses are <b>not</b>
+ * currently configured but this could easily be added if required.
+ * @param instance the instance to configure
+ * @param config the configuration to use to configure the object
+ */
+ public static void configure(Object instance, Configuration config)
+ {
+
+ for (Field f : instance.getClass().getDeclaredFields())
+ {
+ Configured annotation = f.getAnnotation(Configured.class);
+ if (annotation != null)
+ {
+ setValueInField(f, instance, config, annotation);
+ }
+ }
+ }
+
+
+
+ /**
+ * Configure a given instance using the application configuration. Note that superclasses are <b>not</b>
+ * currently configured but this could easily be added if required.
+ * @param instance the instance to configure
+ */
+ public static void configure(Object instance)
+ {
+ configure(instance, ApplicationRegistry.getInstance().getConfiguration());
+ }
+
+ private static void setValueInField(Field f, Object instance, Configuration config, Configured annotation)
+ {
+ Class fieldClass = f.getType();
+ String configPath = annotation.path();
+ try
+ {
+ if (fieldClass == String.class)
+ {
+ String val = config.getString(configPath, annotation.defaultValue());
+ val = PropertyUtils.replaceProperties(val);
+ f.set(instance, val);
+ }
+ else if (fieldClass == int.class)
+ {
+ int val = config.getInt(configPath, Integer.parseInt(annotation.defaultValue()));
+ f.setInt(instance, val);
+ }
+ else if (fieldClass == long.class)
+ {
+ long val = config.getLong(configPath, Long.parseLong(annotation.defaultValue()));
+ f.setLong(instance, val);
+ }
+ else if (fieldClass == double.class)
+ {
+ double val = config.getDouble(configPath, Double.parseDouble(annotation.defaultValue()));
+ f.setDouble(instance, val);
+ }
+ else if (fieldClass == boolean.class)
+ {
+ boolean val = config.getBoolean(configPath, Boolean.parseBoolean(annotation.defaultValue()));
+ f.setBoolean(instance, val);
+ }
+ else
+ {
+ _logger.error("Unsupported field type " + fieldClass + " for " + f + " IGNORING configured value");
+ }
+ }
+ catch (PropertyException e)
+ {
+ _logger.error("Unable to expand property: " + e + " INGORING field " + f, e);
+ }
+ catch (IllegalAccessException e)
+ {
+ _logger.error("Unable to access field " + f + " IGNORING configured value");
+ }
+ }
+}
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..8573902af4
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java
@@ -0,0 +1,269 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.Collections;
+import java.util.List;
+
+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.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class VirtualHostConfiguration
+{
+ private static final Logger _logger = Logger.getLogger(VirtualHostConfiguration.class);
+
+ private static XMLConfiguration _config;
+
+ private static final String VIRTUALHOST_PROPERTY_BASE = "virtualhost.";
+
+
+ public VirtualHostConfiguration(String configFile) throws ConfigurationException
+ {
+ _logger.info("Loading Config file:" + configFile);
+
+ _config = new XMLConfiguration(configFile);
+
+ }
+
+
+
+ private void configureVirtualHost(String virtualHostName, Configuration configuration) throws ConfigurationException, AMQException
+ {
+ _logger.debug("Loding configuration for virtaulhost: "+virtualHostName);
+
+
+ VirtualHost virtualHost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost(virtualHostName);
+
+
+
+ if(virtualHost == null)
+ {
+ throw new ConfigurationException("Unknown virtual host: " + virtualHostName);
+ }
+
+ List exchangeNames = configuration.getList("exchanges.exchange.name");
+
+ for(Object exchangeNameObj : exchangeNames)
+ {
+ String exchangeName = String.valueOf(exchangeNameObj);
+ configureExchange(virtualHost, exchangeName, configuration);
+ }
+
+
+ List queueNames = configuration.getList("queues.queue.name");
+
+ for(Object queueNameObj : queueNames)
+ {
+ String queueName = String.valueOf(queueNameObj);
+ configureQueue(virtualHost, queueName, configuration);
+ }
+
+ }
+
+ private void configureExchange(VirtualHost virtualHost, String exchangeNameString, Configuration configuration) throws AMQException
+ {
+
+ CompositeConfiguration exchangeConfiguration = new CompositeConfiguration();
+
+ exchangeConfiguration.addConfiguration(configuration.subset("exchanges.exchange."+ exchangeNameString));
+ exchangeConfiguration.addConfiguration(configuration.subset("exchanges"));
+
+ QueueRegistry queueRegistry = virtualHost.getQueueRegistry();
+ MessageStore messageStore = virtualHost.getMessageStore();
+ ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry();
+ ExchangeFactory exchangeFactory = virtualHost.getExchangeFactory();
+
+ AMQShortString exchangeName = new AMQShortString(exchangeNameString);
+
+
+ Exchange exchange;
+
+
+
+ synchronized (exchangeRegistry)
+ {
+ exchange = exchangeRegistry.getExchange(exchangeName);
+ if(exchange == null)
+ {
+
+ AMQShortString type = new AMQShortString(exchangeConfiguration.getString("type","direct"));
+ boolean durable = exchangeConfiguration.getBoolean("durable",false);
+ boolean autodelete = exchangeConfiguration.getBoolean("autodelete",false);
+
+ Exchange newExchange = exchangeFactory.createExchange(exchangeName,type,durable,autodelete,0);
+ exchangeRegistry.registerExchange(newExchange);
+ }
+
+ }
+ }
+
+ public static CompositeConfiguration getDefaultQueueConfiguration(AMQQueue queue)
+ {
+ CompositeConfiguration queueConfiguration = null;
+ if (_config == null)
+ return null;
+
+ Configuration vHostConfiguration = _config.subset(VIRTUALHOST_PROPERTY_BASE + queue.getVirtualHost().getName());
+
+ if (vHostConfiguration == null)
+ return null;
+
+ Configuration defaultQueueConfiguration = vHostConfiguration.subset("queues");
+ if (defaultQueueConfiguration != null)
+ {
+ queueConfiguration = new CompositeConfiguration();
+ queueConfiguration.addConfiguration(defaultQueueConfiguration);
+ }
+
+ return queueConfiguration;
+ }
+
+ private void configureQueue(VirtualHost virtualHost, String queueNameString, Configuration configuration) throws AMQException, ConfigurationException
+ {
+ CompositeConfiguration queueConfiguration = new CompositeConfiguration();
+
+ queueConfiguration.addConfiguration(configuration.subset("queues.queue."+ queueNameString));
+ queueConfiguration.addConfiguration(configuration.subset("queues"));
+
+ QueueRegistry queueRegistry = virtualHost.getQueueRegistry();
+ MessageStore messageStore = virtualHost.getMessageStore();
+ ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry();
+
+
+ AMQShortString queueName = new AMQShortString(queueNameString);
+
+ AMQQueue queue;
+
+ synchronized (queueRegistry)
+ {
+ queue = queueRegistry.getQueue(queueName);
+
+ if (queue == null)
+ {
+ _logger.info("Creating queue '" + queueName + "' on virtual host " + virtualHost.getName());
+
+ boolean durable = queueConfiguration.getBoolean("durable" ,false);
+ boolean autodelete = queueConfiguration.getBoolean("autodelete", false);
+ String owner = queueConfiguration.getString("owner", null);
+
+ queue = new AMQQueue(queueName,
+ durable,
+ owner == null ? null : new AMQShortString(owner) /* These queues will have no owner */,
+ autodelete /* Therefore autodelete makes no sence */, virtualHost);
+
+ if (queue.isDurable())
+ {
+ messageStore.createQueue(queue);
+ }
+
+ queueRegistry.registerQueue(queue);
+ }
+ else
+ {
+ _logger.info("Queue '" + queueNameString + "' already exists on virtual host "+virtualHost.getName()+", not creating.");
+ }
+
+ String exchangeName = queueConfiguration.getString("exchange", null);
+
+ Exchange exchange = exchangeRegistry.getExchange(exchangeName == null ? null : new AMQShortString(exchangeName));
+
+ if(exchange == null)
+ {
+ exchange = virtualHost.getExchangeRegistry().getDefaultExchange();
+ }
+
+ if (exchange == null)
+ {
+ throw new ConfigurationException("Attempt to bind queue to unknown exchange:" + exchangeName);
+ }
+
+ synchronized (exchange)
+ {
+ List routingKeys = queueConfiguration.getList("routingKey");
+ if(routingKeys == null || routingKeys.isEmpty())
+ {
+ routingKeys = Collections.singletonList(queue.getName());
+ }
+
+ for(Object routingKeyNameObj : routingKeys)
+ {
+ AMQShortString routingKey = new AMQShortString(String.valueOf(routingKeyNameObj));
+
+
+ queue.bind(routingKey, null, exchange);
+
+
+ _logger.info("Queue '" + queue.getName() + "' bound to exchange:" + exchangeName + " RK:'" + routingKey + "'");
+ }
+
+ if(exchange != virtualHost.getExchangeRegistry().getDefaultExchange())
+ {
+ queue.bind(queue.getName(), null, virtualHost.getExchangeRegistry().getDefaultExchange());
+ }
+ }
+
+ }
+
+
+
+ Configurator.configure(queue, queueConfiguration);
+ }
+
+
+ public void performBindings() throws AMQException, ConfigurationException
+ {
+ List virtualHostNames = _config.getList(VIRTUALHOST_PROPERTY_BASE + "name");
+ String defaultVirtualHostName = _config.getString("default");
+ if(defaultVirtualHostName != null)
+ {
+ ApplicationRegistry.getInstance().getVirtualHostRegistry().setDefaultVirtualHostName(defaultVirtualHostName);
+ }
+ _logger.info("Configuring " + virtualHostNames == null ? 0 : virtualHostNames.size() + " virtual hosts: " + virtualHostNames);
+
+ for(Object nameObject : virtualHostNames)
+ {
+ String name = String.valueOf(nameObject);
+ configureVirtualHost(name, _config.subset(VIRTUALHOST_PROPERTY_BASE + name));
+ }
+
+ if (virtualHostNames == null || virtualHostNames.isEmpty())
+ {
+ throw new ConfigurationException(
+ "Virtualhost Configuration document does not contain a valid virtualhost.");
+ }
+ }
+
+
+
+}
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..9ebb893362
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java
@@ -0,0 +1,217 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.queue.AMQQueue;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import java.util.List;
+import java.util.Map;
+
+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);
+ }
+
+ 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();
+ }
+ }
+
+ abstract public Map<AMQShortString, List<AMQQueue>> getBindings();
+
+ public String toString()
+ {
+ return getClass().getName() + "[" + 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..1a9dc6673a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java
@@ -0,0 +1,113 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.commons.configuration.Configuration;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQUnknownExchangeType;
+import org.apache.qpid.framing.AMQShortString;
+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(DestNameExchange.TYPE);
+ registerExchangeType(DestWildExchange.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);
+ }
+ Exchange e = exchType.newInstance(_host, exchange, durable, ticket, autoDelete);
+ return e;
+ }
+
+ public void initialise(Configuration hostConfig)
+ {
+
+ if (hostConfig == null)
+ {
+ return;
+ }
+
+ for(Object className : hostConfig.getList("custom-exchanges.class-name"))
+ {
+ 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..98abf7977a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.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.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.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(AMQMessage payload) throws AMQException
+ {
+ final AMQShortString exchange = payload.getMessagePublishInfo().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/DestNameExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DestNameExchange.java
new file mode 100644
index 0000000000..12347c0278
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DestNameExchange.java
@@ -0,0 +1,260 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class DestNameExchange extends AbstractExchange
+{
+ private static final Logger _logger = Logger.getLogger(DestNameExchange.class);
+
+ /**
+ * Maps from queue name to queue instances
+ */
+ private final Index _index = new Index();
+
+ public static final ExchangeType<DestNameExchange> TYPE = new ExchangeType<DestNameExchange>()
+ {
+
+ public AMQShortString getName()
+ {
+ return ExchangeDefaults.DIRECT_EXCHANGE_CLASS;
+ }
+
+ public Class<DestNameExchange> getExchangeClass()
+ {
+ return DestNameExchange.class;
+ }
+
+ public DestNameExchange newInstance(VirtualHost host,
+ AMQShortString name,
+ boolean durable,
+ int ticket,
+ boolean autoDelete) throws AMQException
+ {
+ DestNameExchange exch = new DestNameExchange();
+ 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 DestNameExchangeMBean extends ExchangeMBean
+ {
+ @MBeanConstructor("Creates an MBean for AMQ direct exchange")
+ public DestNameExchangeMBean() 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(new AMQShortString(binding), null, DestNameExchange.this);
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex);
+ }
+ }
+
+ }// End of MBean class
+
+
+ protected ExchangeMBean createMBean() throws AMQException
+ {
+ try
+ {
+ return new DestNameExchangeMBean();
+ }
+ 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))
+ {
+ _logger.debug("Queue " + queue + " is already registered with routing key " + routingKey);
+ }
+ else
+ {
+ _logger.debug("Binding queue " + queue + " with routing key " + routingKey + " to exchange " + this);
+ }
+ }
+
+ public void deregisterQueue(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(AMQMessage payload) throws AMQException
+ {
+ final MessagePublishInfo info = payload.getMessagePublishInfo();
+ final AMQShortString routingKey = info.getRoutingKey() == null ? AMQShortString.EMPTY_STRING : info.getRoutingKey();
+ final List<AMQQueue> queues = (routingKey == null) ? null : _index.get(routingKey);
+ if (queues == null || queues.isEmpty())
+ {
+ String msg = "Routing key " + routingKey + " is not known to " + this;
+ if (info.isMandatory() || info.isImmediate())
+ {
+ throw new NoRouteException(msg, payload);
+ }
+ else
+ {
+ _logger.error("MESSAGE LOSS: Message should be sent on a Dead Letter Queue");
+ _logger.warn(msg);
+ }
+ }
+ else
+ {
+ 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/DestWildExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java
new file mode 100644
index 0000000000..6fa3686152
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java
@@ -0,0 +1,579 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.framing.AMQShortStringTokenizer;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.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.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class DestWildExchange extends AbstractExchange
+{
+
+ public static final ExchangeType<DestWildExchange> TYPE = new ExchangeType<DestWildExchange>()
+ {
+
+ public AMQShortString getName()
+ {
+ return ExchangeDefaults.TOPIC_EXCHANGE_CLASS;
+ }
+
+ public Class<DestWildExchange> getExchangeClass()
+ {
+ return DestWildExchange.class;
+ }
+
+ public DestWildExchange newInstance(VirtualHost host,
+ AMQShortString name,
+ boolean durable,
+ int ticket,
+ boolean autoDelete) throws AMQException
+ {
+ DestWildExchange exch = new DestWildExchange();
+ exch.initialise(host, name, durable, ticket, autoDelete);
+ return exch;
+ }
+
+ public AMQShortString getDefaultExchangeName()
+ {
+ return ExchangeDefaults.TOPIC_EXCHANGE_NAME;
+ }
+ };
+
+
+ private static final Logger _logger = Logger.getLogger(DestWildExchange.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 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 ConcurrentHashMap<AMQShortString, AMQShortString[]> _bindingKey2Tokenized =
+ new ConcurrentHashMap<AMQShortString, AMQShortString[]>();
+ private static final byte HASH_BYTE = (byte)'#';
+ private static final byte STAR_BYTE = (byte)'*';
+
+ /** DestWildExchangeMBean class implements the management interface for the Topic exchanges. */
+ @MBeanDescription("Management Bean for Topic Exchange")
+ private final class DestWildExchangeMBean extends ExchangeMBean
+ {
+ @MBeanConstructor("Creates an MBean for AMQ topic exchange")
+ public DestWildExchangeMBean() throws JMException
+ {
+ super();
+ _exchangeType = "topic";
+ init();
+ }
+
+ /** returns exchange bindings in tabular form */
+ public TabularData bindings() throws OpenDataException
+ {
+ _bindingList = new TabularDataSupport(_bindinglistDataType);
+ for (Map.Entry<AMQShortString, List<AMQQueue>> entry : _bindingKey2queues.entrySet())
+ {
+ AMQShortString key = entry.getKey();
+ List<String> queueList = new ArrayList<String>();
+
+ List<AMQQueue> queues = getMatchedQueues(key);
+ 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(new AMQShortString(binding), null, DestWildExchange.this);
+ }
+ 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);
+
+ // we need to use putIfAbsent, which is an atomic operation, to avoid a race condition
+ List<AMQQueue> queueList = _bindingKey2queues.putIfAbsent(rKey, new CopyOnWriteArrayList<AMQQueue>());
+
+
+
+
+
+
+
+ // if we got null back, no previous value was associated with the specified routing key hence
+ // we need to read back the new value just put into the map
+ if (queueList == null)
+ {
+ queueList = _bindingKey2queues.get(rKey);
+ }
+
+
+
+ if (!queueList.contains(queue))
+ {
+ queueList.add(queue);
+
+
+ if(rKey.contains(HASH_BYTE) || rKey.contains(STAR_BYTE))
+ {
+ AMQShortString routingKey = normalize(rKey);
+ List<AMQQueue> queueList2 = _wildCardBindingKey2queues.putIfAbsent(routingKey, new CopyOnWriteArrayList<AMQQueue>());
+
+ if(queueList2 == null)
+ {
+ queueList2 = _wildCardBindingKey2queues.get(routingKey);
+ AMQShortStringTokenizer keyTok = routingKey.tokenize(TOPIC_SEPARATOR);
+
+ ArrayList<AMQShortString> keyTokList = new ArrayList<AMQShortString>(keyTok.countTokens());
+
+ while (keyTok.hasMoreTokens())
+ {
+ keyTokList.add(keyTok.nextToken());
+ }
+
+ _bindingKey2Tokenized.put(routingKey, keyTokList.toArray(new AMQShortString[keyTokList.size()]));
+ }
+ queueList2.add(queue);
+
+ }
+ else
+ {
+ List<AMQQueue> queueList2 = _simpleBindingKey2queues.putIfAbsent(rKey, new CopyOnWriteArrayList<AMQQueue>());
+ if(queueList2 == null)
+ {
+ queueList2 = _simpleBindingKey2queues.get(rKey);
+ }
+ queueList2.add(queue);
+
+ }
+
+
+
+
+ }
+ else if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Queue " + queue + " is already registered with routing key " + rKey);
+ }
+
+
+
+ }
+
+ 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(AMQMessage payload) throws AMQException
+ {
+ MessagePublishInfo info = payload.getMessagePublishInfo();
+
+ final AMQShortString routingKey = info.getRoutingKey();
+
+ List<AMQQueue> queues = getMatchedQueues(routingKey);
+ // if we have no registered queues we have nothing to do
+ // TODO: add support for the immediate flag
+ if ((queues == null) || queues.isEmpty())
+ {
+ if (info.isMandatory() || info.isImmediate())
+ {
+ String msg = "Topic " + routingKey + " is not known to " + this;
+ throw new NoRouteException(msg, payload);
+ }
+ else
+ {
+ _logger.warn("No queues found for routing key " + routingKey);
+ _logger.warn("Routing map contains: " + _bindingKey2queues);
+
+ return;
+ }
+ }
+
+ payload.enqueue(queues);
+
+ }
+
+ public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue)
+ {
+ return isBound(routingKey, queue);
+ }
+
+ public boolean isBound(AMQShortString routingKey, AMQQueue queue)
+ {
+ List<AMQQueue> queues = _bindingKey2queues.get(normalize(routingKey));
+
+ return (queues != null) && queues.contains(queue);
+ }
+
+ public boolean isBound(AMQShortString routingKey)
+ {
+ List<AMQQueue> queues = _bindingKey2queues.get(normalize(routingKey));
+
+ return (queues != null) && !queues.isEmpty();
+ }
+
+ public boolean isBound(AMQQueue queue)
+ {
+ for (List<AMQQueue> queues : _bindingKey2queues.values())
+ {
+ if (queues.contains(queue))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean hasBindings()
+ {
+ return !_bindingKey2queues.isEmpty();
+ }
+
+ public synchronized void deregisterQueue(AMQShortString rKey, AMQQueue queue, FieldTable args) throws AMQException
+ {
+ assert queue != null;
+ assert rKey != null;
+
+ List<AMQQueue> queues = _bindingKey2queues.get(rKey);
+ if (queues == null)
+ {
+ throw new AMQException(AMQConstant.NOT_FOUND, "Queue " + queue + " was not registered with exchange " + this.getName()
+ + " with routing key " + rKey + ". No queue was registered with that _routing key");
+
+ }
+
+ boolean removedQ = queues.remove(queue);
+ if (!removedQ)
+ {
+ throw new AMQException(AMQConstant.NOT_FOUND, "Queue " + queue + " was not registered with exchange " + this.getName()
+ + " with routing key " + rKey);
+ }
+
+
+ if(rKey.contains(HASH_BYTE) || rKey.contains(STAR_BYTE))
+ {
+ AMQShortString bindingKey = normalize(rKey);
+ List<AMQQueue> queues2 = _wildCardBindingKey2queues.get(bindingKey);
+ queues2.remove(queue);
+ if(queues2.isEmpty())
+ {
+ _wildCardBindingKey2queues.remove(bindingKey);
+ _bindingKey2Tokenized.remove(bindingKey);
+ }
+
+ }
+ else
+ {
+ List<AMQQueue> queues2 = _simpleBindingKey2queues.get(rKey);
+ queues2.remove(queue);
+ if(queues2.isEmpty())
+ {
+ _simpleBindingKey2queues.remove(rKey);
+ }
+
+ }
+
+
+
+
+ if (queues.isEmpty())
+ {
+ _bindingKey2queues.remove(rKey);
+ }
+ }
+
+ protected ExchangeMBean createMBean() throws AMQException
+ {
+ try
+ {
+ return new DestWildExchangeMBean();
+ }
+ 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);
+ }
+ }
+
+ public Map<AMQShortString, List<AMQQueue>> getBindings()
+ {
+ return _bindingKey2queues;
+ }
+
+ private List<AMQQueue> getMatchedQueues(AMQShortString routingKey)
+ {
+
+ List<AMQQueue> list = null;
+
+ if(!_wildCardBindingKey2queues.isEmpty())
+ {
+
+
+ AMQShortStringTokenizer routingTokens = routingKey.tokenize(TOPIC_SEPARATOR);
+
+ final int routingTokensCount = routingTokens.countTokens();
+
+
+ AMQShortString[] routingkeyTokens = new AMQShortString[routingTokensCount];
+
+ if(routingTokensCount == 1)
+ {
+ routingkeyTokens[0] =routingKey;
+ }
+ else
+ {
+
+
+ int token = 0;
+ while (routingTokens.hasMoreTokens())
+ {
+
+ AMQShortString next = routingTokens.nextToken();
+
+ routingkeyTokens[token++] = next;
+ }
+ }
+ for (AMQShortString bindingKey : _wildCardBindingKey2queues.keySet())
+ {
+
+ AMQShortString[] bindingKeyTokens = _bindingKey2Tokenized.get(bindingKey);
+
+
+ boolean matching = true;
+ boolean done = false;
+
+ int depthPlusRoutingSkip = 0;
+ int depthPlusQueueSkip = 0;
+
+ final int bindingKeyTokensCount = bindingKeyTokens.length;
+
+ while (matching && !done)
+ {
+
+ if ((bindingKeyTokensCount == depthPlusQueueSkip) || (routingTokensCount == depthPlusRoutingSkip))
+ {
+ done = true;
+
+ // if it was the routing key that ran out of digits
+ if (routingTokensCount == depthPlusRoutingSkip)
+ {
+ if (bindingKeyTokensCount > depthPlusQueueSkip)
+ { // a hash and it is the last entry
+ matching =
+ bindingKeyTokens[depthPlusQueueSkip].equals(AMQP_HASH_TOKEN)
+ && (bindingKeyTokensCount == (depthPlusQueueSkip + 1));
+ }
+ }
+ else if (routingTokensCount > depthPlusRoutingSkip)
+ {
+ // There is still more routing key to check
+ matching = false;
+ }
+
+ continue;
+ }
+
+ // if the values on the two topics don't match
+ if (!bindingKeyTokens[depthPlusQueueSkip].equals(routingkeyTokens[depthPlusRoutingSkip]))
+ {
+ if (bindingKeyTokens[depthPlusQueueSkip].equals(AMQP_STAR_TOKEN))
+ {
+ depthPlusQueueSkip++;
+ depthPlusRoutingSkip++;
+
+ continue;
+ }
+ else if (bindingKeyTokens[depthPlusQueueSkip].equals(AMQP_HASH_TOKEN))
+ {
+ // Is this a # at the end
+ if (bindingKeyTokensCount == (depthPlusQueueSkip + 1))
+ {
+ done = true;
+
+ continue;
+ }
+
+ // otherwise # in the middle
+ while (routingTokensCount > depthPlusRoutingSkip)
+ {
+ if (routingkeyTokens[depthPlusRoutingSkip].equals(bindingKeyTokens[depthPlusQueueSkip + 1]))
+ {
+ depthPlusQueueSkip += 2;
+ depthPlusRoutingSkip++;
+
+ break;
+ }
+
+ depthPlusRoutingSkip++;
+ }
+
+ continue;
+ }
+
+ matching = false;
+ }
+
+ depthPlusQueueSkip++;
+ depthPlusRoutingSkip++;
+ }
+
+ if (matching)
+ {
+ if(list == null)
+ {
+ list = new ArrayList<AMQQueue>(_wildCardBindingKey2queues.get(bindingKey));
+ }
+ else
+ {
+ list.addAll(_wildCardBindingKey2queues.get(bindingKey));
+ }
+ }
+ }
+
+ }
+ if(!_simpleBindingKey2queues.isEmpty())
+ {
+ List<AMQQueue> queues = _simpleBindingKey2queues.get(routingKey);
+ if(list == null)
+ {
+ if(queues == null)
+ {
+ list = Collections.EMPTY_LIST;
+ }
+ else
+ {
+ list = new ArrayList<AMQQueue>(queues);
+ }
+ }
+ else if(queues != null)
+ {
+ list.addAll(queues);
+ }
+
+ }
+
+ return list;
+
+ }
+}
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..37cd85a8f8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.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.exchange;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.queue.AMQMessage;
+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(AMQMessage 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();
+
+ Map<AMQShortString, List<AMQQueue>> getBindings();
+
+}
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..0bcfec7181
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java
@@ -0,0 +1,40 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import java.util.Collection;
+
+import org.apache.commons.configuration.Configuration;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+
+
+public interface ExchangeFactory
+{
+ Exchange createExchange(AMQShortString exchange, AMQShortString type, boolean durable, boolean autoDelete,
+ int ticket)
+ throws AMQException;
+
+ void initialise(Configuration 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..e7c887f306
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java
@@ -0,0 +1,240 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.server.management.MBeanConstructor;
+import org.apache.qpid.server.management.MBeanDescription;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.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(new AMQShortString(binding), null, FanoutExchange.this);
+ }
+ 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(AMQMessage payload) throws AMQException
+ {
+ final MessagePublishInfo publishInfo = payload.getMessagePublishInfo();
+ final AMQShortString routingKey = publishInfo.getRoutingKey();
+ if ((_queues == null) || _queues.isEmpty())
+ {
+ String msg = "No queues bound to " + this;
+ if (publishInfo.isMandatory() || publishInfo.isImmediate())
+ {
+ throw new NoRouteException(msg, payload);
+ }
+ else
+ {
+ _logger.warn(msg);
+ }
+ }
+ else
+ {
+ 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..68ad88c4cb
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java
@@ -0,0 +1,360 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.AMQMessage;
+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.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(AMQMessage payload) throws AMQException
+ {
+ FieldTable headers = getHeaders(payload.getContentHeaderBody());
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Exchange " + getName() + ": routing message with headers " + headers);
+ }
+ boolean routed = false;
+ for (Registration e : _bindings)
+ {
+ if (e.binding.matches(headers))
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Exchange " + getName() + ": delivering message with headers " +
+ headers + " to " + e.queue.getName());
+ }
+ payload.enqueue(e.queue);
+ routed = true;
+ }
+ }
+ if (!routed)
+ {
+
+ String msg = "Exchange " + getName() + ": message not routable.";
+
+ if (payload.getMessagePublishInfo().isMandatory() || payload.getMessagePublishInfo().isImmediate())
+ {
+ throw new NoRouteException(msg, payload);
+ }
+ else
+ {
+ _logger.warn(msg);
+ }
+
+ }
+ }
+
+ 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..eacdad8a8e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.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.server.exchange;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+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 DestNameExchange.
+ */
+class Index
+{
+ private ConcurrentMap<AMQShortString, List<AMQQueue>> _index
+ = new ConcurrentHashMap<AMQShortString, List<AMQQueue>>();
+
+ synchronized boolean add(AMQShortString key, AMQQueue queue)
+ {
+ List<AMQQueue> queues = _index.get(key);
+ if(queues == null)
+ {
+ queues = new CopyOnWriteArrayList<AMQQueue>();
+ //next call is atomic, so there is no race to create the list
+ List<AMQQueue> active = _index.putIfAbsent(key, queues);
+ if(active != null)
+ {
+ //someone added the new one in faster than we did, so use theirs
+ queues = active;
+ }
+ }
+ if(queues.contains(queue))
+ {
+ return false;
+ }
+ else
+ {
+ return queues.add(queue);
+ }
+ }
+
+ synchronized boolean remove(AMQShortString key, AMQQueue queue)
+ {
+ List<AMQQueue> queues = _index.get(key);
+ if (queues != null)
+ {
+ boolean removed = queues.remove(queue);
+ if (queues.size() == 0)
+ {
+ _index.remove(key);
+ }
+ return removed;
+ }
+ return false;
+ }
+
+ List<AMQQueue> get(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..5d6d68b3c8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.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 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";
+
+ /**
+ * 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..7508e80f7f
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java
@@ -0,0 +1,40 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+
+/**
+ * Separated out from the ExchangeRegistry interface to allow components
+ * that use only this part to have a dependency with a reduced footprint.
+ *
+ */
+public interface MessageRouter
+{
+ /**
+ * Routes content through exchanges, delivering it to 1 or more queues.
+ * @param message the message to be routed
+ *
+ * @throws org.apache.qpid.AMQException if something goes wrong delivering data
+ */
+ void routeContent(AMQMessage message) throws AMQException;
+}
diff --git a/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..1d6ab3842d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java
@@ -0,0 +1,48 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.queue.AMQMessage;
+
+/**
+ * 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 message)
+ {
+ super(msg, message);
+ }
+
+ public AMQConstant getReplyCode()
+ {
+ return AMQConstant.NO_ROUTE;
+ }
+}
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..fb5220f4da
--- /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.AMQMessage;
+
+/**
+ * An expression which performs an operation on two expression values
+ */
+public abstract class ArithmeticExpression extends BinaryExpression
+{
+
+ 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(AMQMessage message) throws AMQException
+ {
+ 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..024257bea9
--- /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 implements Expression
+{
+ protected Expression left;
+ protected Expression right;
+
+ public BinaryExpression(Expression left, Expression right)
+ {
+ this.left = left;
+ this.right = right;
+ }
+
+ public Expression getLeft()
+ {
+ return left;
+ }
+
+ public Expression 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 expression)
+ {
+ right = expression;
+ }
+
+ /**
+ * @param expression
+ */
+ public void setLeft(Expression 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..e28ff79820
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BooleanExpression.java
@@ -0,0 +1,40 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.server.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;
+
+/**
+ * A BooleanExpression is an expression that always
+ * produces a Boolean result.
+ */
+public interface BooleanExpression extends Expression
+{
+
+ /**
+ * @param message
+ * @return true if the expression evaluates to Boolean.TRUE.
+ * @throws AMQException
+ */
+ public boolean matches(AMQMessage message) throws AMQException;
+
+}
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..72a9ef7969
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ComparisonExpression.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.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;
+
+/**
+ * A filter performing a comparison of two objects
+ */
+public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression
+{
+
+ public static BooleanExpression createBetween(Expression value, Expression left, Expression right)
+ {
+ return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right));
+ }
+
+ public static BooleanExpression createNotBetween(Expression value, Expression left, Expression 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 extends UnaryExpression implements BooleanExpression
+ {
+
+ Pattern likePattern;
+
+ /**
+ * @param right
+ */
+ public LikeExpression(Expression 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(AMQMessage message) throws AMQException
+ {
+
+ 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(AMQMessage message) throws AMQException
+ {
+ 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 BooleanExpression doCreateEqual(Expression left, Expression right)
+ {
+ return new ComparisonExpression(left, right)
+ {
+
+ public Object evaluate(AMQMessage message) throws AMQException
+ {
+ 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 "=";
+ }
+ };
+ }
+
+ 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(AMQMessage message) throws AMQException
+ {
+ 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(AMQMessage 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/ConstantExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java
new file mode 100644
index 0000000000..0e729cc521
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java
@@ -0,0 +1,210 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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;
+
+/**
+ * Represents a constant expression
+ */
+public class ConstantExpression implements Expression
+{
+
+ static class BooleanConstantExpression extends ConstantExpression implements BooleanExpression
+ {
+ public BooleanConstantExpression(Object value)
+ {
+ super(value);
+ }
+
+ public boolean matches(AMQMessage message) throws AMQException
+ {
+ 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(AMQMessage message) throws AMQException
+ {
+ 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..5f646c15db
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/Expression.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.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;
+
+/**
+ * Represents an expression
+ */
+public interface Expression
+{
+
+ /**
+ * @return the value of this expression
+ */
+ public Object evaluate(AMQMessage message) throws AMQException;
+
+}
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..c82de9fa15
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.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.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;
+
+public interface FilterManager
+{
+ void add(MessageFilter filter);
+
+ void remove(MessageFilter filter);
+
+ boolean allAllow(AMQMessage 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..311f0680ec
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.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 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)
+ {
+
+ manager = new SimpleFilterManager();
+
+ if(filters.containsKey(AMQPFilterTypes.JMS_SELECTOR.getValue()))
+ {
+ String selector = filters.getString(AMQPFilterTypes.JMS_SELECTOR.getValue());
+
+ if (selector != null && !selector.equals(""))
+ {
+ manager.add(new JMSSelectorFilter(selector));
+ }
+
+ }
+
+ if (filters.containsKey(AMQPFilterTypes.NO_CONSUME.getValue()))
+ {
+ manager.add(new NoConsumerFilter());
+ }
+
+
+
+ //If we added no filters don't bear the overhead of having an filter manager
+ if (!manager.hasFilters())
+ {
+ manager = null;
+ }
+ }
+ 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..32f58ed666
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.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.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.AMQMessage;
+
+
+
+public class JMSSelectorFilter implements MessageFilter
+{
+ private final static Logger _logger = org.apache.log4j.Logger.getLogger(JMSSelectorFilter.class);
+
+ private String _selector;
+ private BooleanExpression _matcher;
+
+ public JMSSelectorFilter(String selector) throws AMQException
+ {
+ _selector = selector;
+ _logger.info("Created JMSSelectorFilter with selector:" + _selector);
+
+
+ _matcher = new SelectorParser().parse(selector);
+
+
+ }
+
+ public boolean matches(AMQMessage message)
+ {
+ try
+ {
+ boolean match = _matcher.matches(message);
+ _logger.info(message + " match(" + match + ") selector(" + System.identityHashCode(_selector) + "):" + _selector);
+ return match;
+ }
+ catch (AMQException e)
+ {
+ //fixme this needs to be sorted.. it shouldn't happen
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ 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..c8cbdb2125
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/LogicExpression.java
@@ -0,0 +1,110 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.server.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;
+
+/**
+ * A filter performing a comparison of two objects
+ */
+public abstract class LogicExpression extends BinaryExpression implements BooleanExpression
+{
+
+ public static BooleanExpression createOR(BooleanExpression lvalue, BooleanExpression rvalue)
+ {
+ return new LogicExpression(lvalue, rvalue)
+ {
+
+ public Object evaluate(AMQMessage message) throws AMQException
+ {
+
+ 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";
+ }
+ };
+ }
+
+ public static BooleanExpression createAND(BooleanExpression lvalue, BooleanExpression rvalue)
+ {
+ return new LogicExpression(lvalue, rvalue)
+ {
+
+ public Object evaluate(AMQMessage message) throws AMQException
+ {
+
+ 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";
+ }
+ };
+ }
+
+ /**
+ * @param left
+ * @param right
+ */
+ public LogicExpression(BooleanExpression left, BooleanExpression right)
+ {
+ super(left, right);
+ }
+
+ public abstract Object evaluate(AMQMessage message) throws AMQException;
+
+ public boolean matches(AMQMessage 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/MessageFilter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java
new file mode 100644
index 0000000000..e6bfe974d5
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.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.filter;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+
+public interface MessageFilter
+{
+ boolean matches(AMQMessage message) throws AMQException;
+}
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..47ca930d12
--- /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.AMQMessage;
+
+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(AMQMessage 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..5ab360ca19
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/PropertyExpression.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.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.AMQMessage;
+
+/**
+ * Represents a property expression
+ */
+public class PropertyExpression implements Expression
+{
+ // 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> JMS_PROPERTY_EXPRESSIONS = new HashMap<String, Expression>();
+
+ static
+ {
+ JMS_PROPERTY_EXPRESSIONS.put("JMSDestination", new Expression()
+ {
+ public Object evaluate(AMQMessage message)
+ {
+ //TODO
+ return null;
+ }
+ });
+ JMS_PROPERTY_EXPRESSIONS.put("JMSReplyTo", new Expression()
+ {
+ public Object evaluate(AMQMessage message)
+ {
+ try
+ {
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+ AMQShortString replyTo = _properties.getReplyTo();
+
+ return (replyTo == null) ? null : replyTo.toString();
+ }
+ catch (AMQException e)
+ {
+ _logger.warn(e);
+
+ return null;
+ }
+
+ }
+
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSType", new Expression()
+ {
+ public Object evaluate(AMQMessage message)
+ {
+ try
+ {
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+ AMQShortString type = _properties.getType();
+
+ return (type == null) ? null : type.toString();
+ }
+ catch (AMQException e)
+ {
+ _logger.warn(e);
+
+ return null;
+ }
+
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSDeliveryMode", new Expression()
+ {
+ public Object evaluate(AMQMessage message)
+ {
+ try
+ {
+ int mode = message.isPersistent() ? PERSISTENT : NON_PERSISTENT;
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("JMSDeliveryMode is :" + mode);
+ }
+
+ return mode;
+ }
+ catch (AMQException e)
+ {
+ _logger.warn(e);
+ }
+
+ return NON_PERSISTENT;
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSPriority", new Expression()
+ {
+ public Object evaluate(AMQMessage message)
+ {
+ try
+ {
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+
+ return (int) _properties.getPriority();
+ }
+ catch (AMQException e)
+ {
+ _logger.warn(e);
+ }
+
+ return DEFAULT_PRIORITY;
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("AMQMessageID", new Expression()
+ {
+ public Object evaluate(AMQMessage message)
+ {
+
+ try
+ {
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+ AMQShortString messageId = _properties.getMessageId();
+
+ return (messageId == null) ? null : messageId;
+ }
+ catch (AMQException e)
+ {
+ _logger.warn(e);
+
+ return null;
+ }
+
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSTimestamp", new Expression()
+ {
+ public Object evaluate(AMQMessage message)
+ {
+
+ try
+ {
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+
+ return _properties.getTimestamp();
+ }
+ catch (AMQException e)
+ {
+ _logger.warn(e);
+
+ return null;
+ }
+
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSCorrelationID", new Expression()
+ {
+ public Object evaluate(AMQMessage message)
+ {
+
+ try
+ {
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+ AMQShortString correlationId = _properties.getCorrelationId();
+
+ return (correlationId == null) ? null : correlationId.toString();
+ }
+ catch (AMQException e)
+ {
+ _logger.warn(e);
+
+ return null;
+ }
+
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSExpiration", new Expression()
+ {
+ public Object evaluate(AMQMessage message)
+ {
+
+ try
+ {
+ CommonContentHeaderProperties _properties =
+ (CommonContentHeaderProperties)
+ message.getContentHeaderBody().properties;
+
+ return _properties.getExpiration();
+ }
+ catch (AMQException e)
+ {
+ _logger.warn(e);
+
+ return null;
+ }
+
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSRedelivered", new Expression()
+ {
+ public Object evaluate(AMQMessage message)
+ {
+ return message.isRedelivered();
+ }
+ });
+
+ }
+
+ private final String name;
+ private final Expression jmsPropertyExpression;
+
+ public PropertyExpression(String name)
+ {
+ this.name = name;
+ jmsPropertyExpression = JMS_PROPERTY_EXPRESSIONS.get(name);
+ }
+
+ public Object evaluate(AMQMessage message) throws AMQException
+ {
+
+ 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);
+
+ }
+
+}
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..62a45f5420
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.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.filter;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+
+public class SimpleFilterManager implements FilterManager
+{
+ private final Logger _logger = Logger.getLogger(SimpleFilterManager.class);
+
+ private final ConcurrentLinkedQueue<MessageFilter> _filters;
+
+ public SimpleFilterManager()
+ {
+ _logger.debug("Creating SimpleFilterManager");
+ _filters = new ConcurrentLinkedQueue<MessageFilter>();
+ }
+
+ public void add(MessageFilter filter)
+ {
+ _filters.add(filter);
+ }
+
+ public void remove(MessageFilter filter)
+ {
+ _filters.remove(filter);
+ }
+
+ public boolean allAllow(AMQMessage msg)
+ {
+ for (MessageFilter 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..83b4ed5358
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/UnaryExpression.java
@@ -0,0 +1,337 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.AMQMessage;
+
+/**
+ * An expression which performs an operation on two expression values
+ */
+public abstract class UnaryExpression implements Expression
+{
+
+ private static final BigDecimal BD_LONG_MIN_VALUE = BigDecimal.valueOf(Long.MIN_VALUE);
+ protected Expression right;
+
+ public static Expression createNegate(Expression left)
+ {
+ return new UnaryExpression(left)
+ {
+ public Object evaluate(AMQMessage message) throws AMQException
+ {
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null)
+ {
+ return null;
+ }
+
+ if (rvalue instanceof Number)
+ {
+ return negate((Number) rvalue);
+ }
+
+ return null;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "-";
+ }
+ };
+ }
+
+ public static BooleanExpression createInExpression(PropertyExpression 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 BooleanUnaryExpression(right)
+ {
+ public Object evaluate(AMQMessage message) throws AMQException
+ {
+
+ 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";
+ }
+ }
+ };
+ }
+
+ abstract static class BooleanUnaryExpression extends UnaryExpression implements BooleanExpression
+ {
+ public BooleanUnaryExpression(Expression left)
+ {
+ super(left);
+ }
+
+ public boolean matches(AMQMessage message) throws AMQException
+ {
+ Object object = evaluate(message);
+
+ return (object != null) && (object == Boolean.TRUE);
+ }
+ }
+ ;
+
+ public static BooleanExpression createNOT(BooleanExpression left)
+ {
+ return new BooleanUnaryExpression(left)
+ {
+ public Object evaluate(AMQMessage message) throws AMQException
+ {
+ Boolean lvalue = (Boolean) right.evaluate(message);
+ if (lvalue == null)
+ {
+ return null;
+ }
+
+ return lvalue.booleanValue() ? Boolean.FALSE : Boolean.TRUE;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "NOT";
+ }
+ };
+ }
+
+ public static BooleanExpression createXPath(final String xpath)
+ {
+ return new XPathExpression(xpath);
+ }
+
+ public static BooleanExpression createXQuery(final String xpath)
+ {
+ return new XQueryExpression(xpath);
+ }
+
+ public static BooleanExpression createBooleanCast(Expression left)
+ {
+ return new BooleanUnaryExpression(left)
+ {
+ public Object evaluate(AMQMessage message) throws AMQException
+ {
+ 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 "";
+ }
+ };
+ }
+
+ 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 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();
+
+}
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..f5454afae5
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XPathExpression.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.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 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(AMQMessage 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(AMQMessage 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(AMQMessage 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..f5debb607a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XQueryExpression.java
@@ -0,0 +1,57 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.server.filter;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.queue.AMQMessage;
+
+//
+// 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(AMQMessage 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(AMQMessage 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..35d770fd5d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XalanXPathEvaluator.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.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.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(AMQMessage 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/handler/AccessRequestHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java
new file mode 100644
index 0000000000..6ace626c28
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java
@@ -0,0 +1,44 @@
+package org.apache.qpid.server.handler;
+
+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.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.exchange.Exchange;
+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..bda1c16cf6
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.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.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.BasicCancelBody;
+import org.apache.qpid.framing.BasicCancelOkBody;
+import org.apache.qpid.framing.MethodRegistry;
+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;
+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(session, 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..7cd4afdb77
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.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.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;
+
+ //Perform ACLs
+ vHost.getAccessManager().authorise(session, Permission.CONSUME, body, queue);
+
+ if (body.getConsumerTag() != null)
+ {
+ consumerTagName = body.getConsumerTag().intern();
+ }
+ else
+ {
+ consumerTagName = null;
+ }
+
+ try
+ {
+ AMQShortString consumerTag = channel.subscribeToQueue(consumerTagName, queue, session, !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));
+
+ }
+
+ //now allow queue to start async processing of any backlog of messages
+ queue.deliverAsync();
+ }
+ 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..f8f9127809
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicGetMethodHandler.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.protocol.AMQConstant;
+import org.apache.qpid.server.AMQChannel;
+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 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
+ vHost.getAccessManager().authorise(session, Permission.CONSUME, body, queue);
+
+ if (!queue.performGet(session, channel, !body.getNoAck()))
+ {
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ // TODO - set clusterId
+ BasicGetEmptyBody responseBody = methodRegistry.createBasicGetEmptyBody(null);
+
+
+ session.writeFrame(responseBody.generateFrame(channelId));
+ }
+ }
+ }
+ }
+}
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..0f99a21ee5
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java
@@ -0,0 +1,101 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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
+ vHost.getAccessManager().authorise(session, Permission.PUBLISH, body, e);
+
+ MessagePublishInfo info = session.getMethodRegistry().getProtocolVersionMethodConverter().convertToInfo(body);
+ info.setExchange(exchange);
+ channel.setPublishFrame(info, session, 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..3c95180dca
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.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.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicQosBody;
+import org.apache.qpid.framing.BasicQosOkBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.AMQMethodBody;
+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 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.setPrefetchCount(body.getPrefetchCount());
+ channel.setPrefetchSize(body.getPrefetchSize());
+
+ 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..15484273c8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverSyncMethodHandler.java
@@ -0,0 +1,54 @@
+package org.apache.qpid.server.handler;
+
+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..069cc6ea2c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRejectMethodHandler.java
@@ -0,0 +1,130 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicRejectBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.ack.UnacknowledgedMessage;
+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();
+
+
+
+// if (_logger.isDebugEnabled())
+// {
+// _logger.debug("Rejecting:" + evt.getMethod().deliveryTag +
+// ": Requeue:" + evt.getMethod().requeue +
+//// ": Resend:" + evt.getMethod().resend +
+// " on channel:" + channelId);
+// }
+
+ 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();
+
+ UnacknowledgedMessage 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() || message.getQueue().isDeleted())
+ {
+ _logger.warn("Message's Queue as already been purged, unable to Reject. " +
+ "Dropping message should use Dead Letter Queue");
+ //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.entry.reject();
+ }
+
+ if (body.getRequeue())
+ {
+ channel.requeue(deliveryTag);
+ }
+ else
+ {
+ _logger.warn("Dropping message as requeue not required and there is no dead letter queue");
+ //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..f99e650979
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.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.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
+ virtualHost.getAccessManager().authorise(session, Permission.ACCESS ,body, virtualHost);
+
+ // 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..193c3a088b
--- /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.*;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.HeartbeatConfig;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+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();
+
+
+ //fixme Vhost not defined yet
+ //session.getVirtualHost().getAuthenticationManager();
+ 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:
+ // Can't do this as we violate protocol. Need to send Close
+ // throw new AMQException(AMQConstant.NOT_ALLOWED.getCode(), AMQConstant.NOT_ALLOWED.getName());
+ _logger.info("Authentication failed");
+ stateManager.changeState(AMQState.CONNECTION_CLOSING);
+
+
+ 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(),
+ HeartbeatConfig.getInstance().getDelay());
+ 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..f02121c89f
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java
@@ -0,0 +1,161 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.commons.configuration.Configuration;
+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.protocol.AMQConstant;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.protocol.HeartbeatConfig;
+import org.apache.qpid.server.protocol.AMQMinaProtocolSession;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+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();
+
+ private static final int DEFAULT_FRAME_SIZE = 65536;
+
+ 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:
+ _logger.info("Authentication failed");
+ 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(),
+ HeartbeatConfig.getInstance().getDelay());
+ 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 Configuration config = ApplicationRegistry.getInstance().getConfiguration();
+ final int framesize = config.getInt("advanced.framesize", DEFAULT_FRAME_SIZE);
+ _logger.info("Framesize set to " + framesize);
+ return framesize;
+ }
+}
+
+
+
diff --git a/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..491a2f80db
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeBoundHandler.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.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0;
+import org.apache.qpid.protocol.AMQMethodEvent;
+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.queue.QueueRegistry;
+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..bc4476e969
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java
@@ -0,0 +1,113 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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();
+
+ //Perform ACL
+ virtualHost.getAccessManager().authorise(session, Permission.CREATE, body);
+
+ 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());
+ }
+
+ }
+ 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..888ffcb2e5
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java
@@ -0,0 +1,71 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ExchangeDeleteBody;
+import org.apache.qpid.framing.ExchangeDeleteOkBody;
+import org.apache.qpid.server.exchange.ExchangeInUseException;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.protocol.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
+ virtualHost.getAccessManager().authorise(session, Permission.DELETE,body,
+ exchangeRegistry.getExchange(body.getExchange()));
+
+ 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..0f6dc7a19d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.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.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
+ virtualHost.getAccessManager().authorise(session, Permission.BIND, body, exch, queue, routingKey);
+
+ if (!exch.isBound(routingKey, body.getArguments(), queue))
+ {
+ queue.bind(routingKey, body.getArguments(), exch);
+ }
+ }
+ 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..89a56b9a3c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.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.handler;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.UUID;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.configuration.Configured;
+
+import org.apache.qpid.framing.*;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.configuration.Configurator;
+import org.apache.qpid.server.configuration.VirtualHostConfiguration;
+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.store.MessageStore;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.commons.configuration.Configuration;
+
+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;
+ }
+
+ @Configured(path = "queue.auto_register", defaultValue = "false")
+ public boolean autoRegister;
+
+ private final AtomicInteger _counter = new AtomicInteger();
+
+
+ protected QueueDeclareHandler()
+ {
+ Configurator.configure(this);
+ }
+
+ 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();
+
+ // Perform ACL on queue Creation
+ virtualHost.getAccessManager().authorise(session, Permission.CREATE, body);
+
+
+
+ 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);
+ }
+ queueRegistry.registerQueue(queue);
+ if (autoRegister)
+ {
+ Exchange defaultExchange = exchangeRegistry.getDefaultExchange();
+
+ // Perform ACL to control bindings
+ virtualHost.getAccessManager().authorise(session, Permission.BIND, body,
+ defaultExchange, queue, queueName);
+
+ queue.bind(queueName, null, defaultExchange);
+ _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 = new AMQQueue(queueName, body.getDurable(), owner, body.getAutoDelete(), virtualHost);
+
+
+ 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
+
+ Configuration virtualHostDefaultQueueConfiguration = VirtualHostConfiguration.getDefaultQueueConfiguration(queue);
+ if (virtualHostDefaultQueueConfiguration != null)
+ {
+ Configurator.configure(queue, virtualHostDefaultQueueConfiguration);
+ }
+
+ 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..310a73ffeb
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.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.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.AMQMethodEvent;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.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
+ virtualHost.getAccessManager().authorise(session, Permission.DELETE, body, queue);
+
+ int purged = queue.delete(body.getIfUnused(), body.getIfEmpty());
+
+ if (queue.isDurable())
+ {
+ store.removeQueue(queue.getName());
+ }
+
+ 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..cce49f13c7
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueuePurgeHandler.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.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.QueuePurgeBody;
+import org.apache.qpid.framing.QueuePurgeOkBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.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
+ virtualHost.getAccessManager().authorise(session, Permission.PURGE, body, queue);
+
+ 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..e758e315aa
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueUnbindHandler.java
@@ -0,0 +1,113 @@
+package org.apache.qpid.server.handler;
+
+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
+ virtualHost.getAccessManager().authorise(session, Permission.UNBIND, body, queue);
+
+ try
+ {
+ queue.unBind(routingKey, body.getArguments(), exch);
+ }
+ 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..79cc722e0e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java
@@ -0,0 +1,80 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.TxCommitBody;
+import org.apache.qpid.framing.TxCommitOkBody;
+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 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(session);
+ }
+ 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/jms/JmsConsumer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/jms/JmsConsumer.java
new file mode 100644
index 0000000000..c08fae4e4e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/jms/JmsConsumer.java
@@ -0,0 +1,110 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.jms;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+
+public class JmsConsumer
+{
+ private int _prefetchValue;
+
+ private PrefetchUnits _prefetchUnits;
+
+ private boolean _noLocal;
+
+ private boolean _autoAck;
+
+ private boolean _exclusive;
+
+ private AMQProtocolSession _protocolSession;
+
+ public enum PrefetchUnits
+ {
+ OCTETS,
+ MESSAGES
+ }
+
+ public int getPrefetchValue()
+ {
+ return _prefetchValue;
+ }
+
+ public void setPrefetchValue(int prefetchValue)
+ {
+ _prefetchValue = prefetchValue;
+ }
+
+ public PrefetchUnits getPrefetchUnits()
+ {
+ return _prefetchUnits;
+ }
+
+ public void setPrefetchUnits(PrefetchUnits prefetchUnits)
+ {
+ _prefetchUnits = prefetchUnits;
+ }
+
+ public boolean isNoLocal()
+ {
+ return _noLocal;
+ }
+
+ public void setNoLocal(boolean noLocal)
+ {
+ _noLocal = noLocal;
+ }
+
+ public boolean isAutoAck()
+ {
+ return _autoAck;
+ }
+
+ public void setAutoAck(boolean autoAck)
+ {
+ _autoAck = autoAck;
+ }
+
+ public boolean isExclusive()
+ {
+ return _exclusive;
+ }
+
+ public void setExclusive(boolean exclusive)
+ {
+ _exclusive = exclusive;
+ }
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _protocolSession;
+ }
+
+ public void setProtocolSession(AMQProtocolSession protocolSession)
+ {
+ _protocolSession = protocolSession;
+ }
+
+ public void deliverMessage() throws AMQException
+ {
+
+ }
+}
diff --git a/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..a2c2bd62a2
--- /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)
+ throws NotCompliantMBeanException
+ {
+ super(managementInterface, typeName);
+ buildMBeanInfo();
+ }
+
+ @Override
+ public MBeanInfo getMBeanInfo()
+ {
+ return _mbeanInfo;
+ }
+
+ private void buildMBeanInfo() throws NotCompliantMBeanException
+ {
+ _mbeanInfo = new MBeanInfo(this.getClass().getName(),
+ MBeanIntrospector.getMBeanDescription(this.getClass()),
+ MBeanIntrospector.getMBeanAttributesInfo(getManagementInterface()),
+ MBeanIntrospector.getMBeanConstructorsInfo(this.getClass()),
+ MBeanIntrospector.getMBeanOperationsInfo(getManagementInterface()),
+ this.getNotificationInfo());
+ }
+
+
+
+ // notification broadcaster implementation
+
+ public void addNotificationListener(NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ {
+ _broadcaster.addNotificationListener(listener, filter, handback);
+ }
+
+ public void removeNotificationListener(NotificationListener listener)
+ throws ListenerNotFoundException
+ {
+ _broadcaster.removeNotificationListener(listener);
+ }
+
+ public MBeanNotificationInfo[] getNotificationInfo()
+ {
+ return null;
+ }
+}
diff --git a/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..84526dbc11
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java
@@ -0,0 +1,191 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.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;
+
+ protected DefaultManagedObject(Class<?> managementInterface, String typeName)
+ throws NotCompliantMBeanException
+ {
+ super(managementInterface);
+ _managementInterface = managementInterface;
+ _typeName = typeName;
+ }
+
+ public String getType()
+ {
+ return _typeName;
+ }
+
+ public Class<?> getManagementInterface()
+ {
+ return _managementInterface;
+ }
+
+ public ManagedObject getParentObject()
+ {
+ return null;
+ }
+
+ public void register() throws AMQException
+ {
+ try
+ {
+ 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);
+
+ 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(",")));
+ }
+
+ 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..4caae2b26f
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.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.management;
+
+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.Base64MD5PasswordFilePrincipalDatabase;
+import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HashedInitialiser;
+
+import javax.management.JMException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.MBeanServerForwarder;
+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 java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+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 is being used then there are no security features
+ * implemented. To use the security features like user authentication, turn off the jmx options in the "QPID_OPTS" env
+ * variable and use JMXMP connector server. If JMXMP connector is not available, then the standard JMXConnector will be
+ * used, which again doesn't have user authentication.
+ */
+public class JMXManagedObjectRegistry implements ManagedObjectRegistry
+{
+ private static final Logger _log = Logger.getLogger(JMXManagedObjectRegistry.class);
+
+ private final MBeanServer _mbeanServer;
+ private Registry _rmiRegistry;
+ private JMXServiceURL _jmxURL;
+
+ public static final String MANAGEMENT_PORT_CONFIG_PATH = "management.jmxport";
+ public static final int MANAGEMENT_PORT_DEFAULT = 8999;
+
+ 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().getBoolean("management.platform-mbeanserver", true);
+
+ _mbeanServer =
+ platformServer ? ManagementFactory.getPlatformMBeanServer()
+ : MBeanServerFactory.createMBeanServer(ManagedObject.DOMAIN);
+ }
+
+
+ public void start() throws IOException
+ {
+ // Check if the "QPID_OPTS" is set to use Out of the Box JMXAgent
+ if (areOutOfTheBoxJMXOptionsSet())
+ {
+ _log.info("JMX: Using the out of the box JMX Agent");
+ return;
+ }
+
+ IApplicationRegistry appRegistry = ApplicationRegistry.getInstance();
+
+ boolean security = appRegistry.getConfiguration().getBoolean("management.security-enabled", false);
+ int port = appRegistry.getConfiguration().getInt(MANAGEMENT_PORT_CONFIG_PATH, MANAGEMENT_PORT_DEFAULT);
+
+ if (security)
+ {
+ // For SASL using JMXMP
+ _jmxURL = new JMXServiceURL("jmxmp", null, port);
+
+ Map env = new HashMap();
+ Map<String, PrincipalDatabase> map = appRegistry.getDatabaseManager().getDatabases();
+ PrincipalDatabase db = null;
+
+ for (Map.Entry<String, PrincipalDatabase> entry : map.entrySet())
+ {
+ if (entry.getValue() instanceof Base64MD5PasswordFilePrincipalDatabase)
+ {
+ db = entry.getValue();
+ break;
+ }
+ else if (entry.getValue() instanceof PlainPasswordFilePrincipalDatabase)
+ {
+ db = entry.getValue();
+ }
+ }
+
+ if (db instanceof Base64MD5PasswordFilePrincipalDatabase)
+ {
+ env.put("jmx.remote.profiles", "SASL/CRAM-MD5");
+ CRAMMD5HashedInitialiser initialiser = new CRAMMD5HashedInitialiser();
+ initialiser.initialise(db);
+ env.put("jmx.remote.sasl.callback.handler", initialiser.getCallbackHandler());
+ }
+ else if (db instanceof PlainPasswordFilePrincipalDatabase)
+ {
+ env.put("jmx.remote.profiles", "SASL/PLAIN");
+ env.put("jmx.remote.sasl.callback.handler", new UserCallbackHandler(db));
+ }
+
+ // Enable the SSL security and server authentication
+ /*
+ SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
+ SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory();
+ env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
+ env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf);
+ */
+
+ JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(_jmxURL, env, _mbeanServer);
+ MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance();
+ cs.setMBeanServerForwarder(mbsf);
+ cs.start();
+ _log.warn("JMX: Started JMXConnector server on port '" + port + "' with SASL");
+
+ }
+ else
+ {
+ startJMXConnectorServer(port);
+ _log.warn("JMX: Started JMXConnector server on port '" + port + "' with security disabled");
+ }
+ }
+
+ /**
+ * Starts up an RMIRegistry at configured port and attaches a JMXConnectorServer to it.
+ *
+ * @param port
+ *
+ * @throws IOException
+ */
+ private void startJMXConnectorServer(int port) throws IOException
+ {
+ startRMIRegistry(port);
+ _jmxURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + port + "/jmxrmi");
+ JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(_jmxURL, null, _mbeanServer);
+ cs.start();
+ }
+
+ public void registerObject(ManagedObject managedObject) throws JMException
+ {
+ _mbeanServer.registerMBean(managedObject, managedObject.getObjectName());
+ }
+
+ public void unregisterObject(ManagedObject managedObject) throws JMException
+ {
+ _mbeanServer.unregisterMBean(managedObject.getObjectName());
+ }
+
+ /**
+ * Checks is the "QPID_OPTS" env variable is set to use the out of the box JMXAgent.
+ *
+ * @return
+ */
+ 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;
+ }
+
+ /**
+ * Starts the rmi registry at given port
+ *
+ * @param port
+ *
+ * @throws RemoteException
+ */
+ private void startRMIRegistry(int port) throws RemoteException
+ {
+ System.setProperty("java.rmi.server.randomIDs", "true");
+ _rmiRegistry = LocateRegistry.createRegistry(port);
+ }
+
+ // stops the RMIRegistry, if it was running and bound to a port
+ public void close() throws RemoteException
+ {
+ if (_rmiRegistry != null)
+ {
+ // Stopping the RMI registry
+ UnicastRemoteObject.unexportObject(_rmiRegistry, true);
+ }
+ }
+
+ /** This class is used for SASL enabled JMXConnector for performing user authentication. */
+ private class UserCallbackHandler implements CallbackHandler
+ {
+ private final PrincipalDatabase _principalDatabase;
+
+ protected UserCallbackHandler(PrincipalDatabase database)
+ {
+ _principalDatabase = database;
+ }
+
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
+ {
+ // Retrieve callbacks
+ NameCallback ncb = null;
+ PasswordCallback pcb = null;
+ for (int i = 0; i < callbacks.length; i++)
+ {
+ if (callbacks[i] instanceof NameCallback)
+ {
+ ncb = (NameCallback) callbacks[i];
+ }
+ else if (callbacks[i] instanceof PasswordCallback)
+ {
+ pcb = (PasswordCallback) callbacks[i];
+ }
+ else if (callbacks[i] instanceof AuthorizeCallback)
+ {
+ ((AuthorizeCallback) callbacks[i]).setAuthorized(true);
+ }
+ else
+ {
+ throw new UnsupportedCallbackException(callbacks[i]);
+ }
+ }
+
+ boolean authorized = false;
+ // Process retrieval of password; can get password if username is available in NameCallback
+ if ((ncb != null) && (pcb != null))
+ {
+ String username = ncb.getDefaultName();
+ try
+ {
+ authorized = _principalDatabase.verifyPassword(username, pcb.getPassword());
+ }
+ catch (AccountNotFoundException e)
+ {
+ IOException ioe = new IOException("User not authorized. " + e);
+ ioe.initCause(e);
+ throw ioe;
+ }
+ }
+
+ if (!authorized)
+ {
+ throw new IOException("User not authorized.");
+ }
+ }
+ }
+}
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..a0ecc2bd85
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java
@@ -0,0 +1,239 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.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.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();
+
+ 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 UserManagement.TYPE.equals(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();
+ if (methodName.startsWith("query") || methodName.startsWith("get"))
+ {
+ return true;
+ }
+ else if (methodName.startsWith("set"))
+ {
+ return false;
+ }
+
+ if ((args[0] instanceof ObjectName) && (methodName.equals("invoke")))
+ {
+ String mbeanMethod = (args.length > 1) ? (String) args[1] : null;
+ if (mbeanMethod == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ 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..45e2e91ed7
--- /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";
+
+ /**
+ * Creates a new Exchange.
+ * @param name
+ * @param type
+ * @param durable
+ * @param passive
+ * @throws IOException
+ * @throws JMException
+ */
+ @MBeanOperation(name="createNewExchange", description="Creates a new Exchange", impact= MBeanOperationInfo.ACTION)
+ void createNewExchange(@MBeanOperationParameter(name="name", description="Name of the new exchange")String name,
+ @MBeanOperationParameter(name="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
+ * @param autoDelete
+ * @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..d8d87ef881
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java
@@ -0,0 +1,48 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.management;
+
+import javax.management.JMException;
+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;
+
+ 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/ManagementConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagementConfiguration.java
new file mode 100644
index 0000000000..042f626e8b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagementConfiguration.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.management;
+
+import org.apache.qpid.configuration.Configured;
+
+public class ManagementConfiguration
+{
+ @Configured(path = "management.enabled",
+ defaultValue = "true")
+ public boolean enabled;
+}
diff --git a/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..d7a879180a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_8/ProtocolOutputConverterImpl.java
@@ -0,0 +1,285 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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 Long messageId = message.getMessageId();
+
+ final int bodyCount = messageHandle.getBodyCount(storeContext,messageId);
+
+ 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,messageId, 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,messageId, 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();
+ final long messageId = message.getMessageId();
+
+ AMQDataBlock deliver = createEncodedGetOkFrame(message, channelId, deliveryTag, queueSize);
+
+
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ message.getContentHeaderBody());
+
+ final int bodyCount = messageHandle.getBodyCount(storeContext,messageId);
+ 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,messageId, 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, messageId, 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..646ef43826
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9/ProtocolOutputConverterImpl.java
@@ -0,0 +1,372 @@
+package org.apache.qpid.server.output.amqp0_9;
+
+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 Long messageId = message.getMessageId();
+
+ final int bodyCount = messageHandle.getBodyCount(storeContext,messageId);
+
+ 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,messageId, 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,messageId, 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();
+ final long messageId = message.getMessageId();
+
+ AMQFrame deliver = createEncodedGetOkFrame(message, channelId, deliveryTag, queueSize);
+
+
+ AMQDataBlock contentHeader = createContentHeaderBlock(channelId, message.getContentHeaderBody());
+
+ final int bodyCount = messageHandle.getBodyCount(storeContext,messageId);
+ 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,messageId, 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, messageId, 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..9191ecf6ed
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.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.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.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 Activator _activator = null;
+ private boolean _empty;
+
+ 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();
+ }
+ catch (BundleException e)
+ {
+ throw new Exception("Could not create bundle");
+ }
+ }
+
+ public Map<String, ExchangeType<?>> getExchanges()
+ {
+ if (_empty)
+ {
+ return null;
+ }
+ Map<String, ExchangeType<?>>exchanges = new HashMap<String, ExchangeType<?>>();
+ for (Object service : _exchangeTracker.getServices())
+ {
+ if (service instanceof ExchangeType<?>)
+ {
+ exchanges.put(service.getClass().getName(), (ExchangeType<?>) service);
+ }
+ }
+
+ return exchanges;
+ }
+
+}
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..6f40594cb4
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java
@@ -0,0 +1,788 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.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.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 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;
+
+ private 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;
+
+ 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;
+
+ }
+
+ // this(session, queueRegistry, exchangeRegistry, codecFactory, new AMQStateManager());
+ }
+
+ 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 ignoring");
+ }
+
+ 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.error("Received incorrect protocol initiation", e);
+
+ _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());
+ }
+
+ closeSession();
+
+ AMQConnectionException ce =
+ evt.getMethod().getConnectionException(AMQConstant.CHANNEL_ERROR,
+ AMQConstant.CHANNEL_ERROR.getName().toString());
+
+ _stateManager.changeState(AMQState.CONNECTION_CLOSING);
+ writeFrame(ce.getCloseFrame(channelId));
+ }
+ }
+ catch (AMQConnectionException e)
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Closing connection due to: " + e.getMessage());
+ }
+
+ markChannelawaitingCloseOk(channelId);
+ closeSession();
+ _stateManager.changeState(AMQState.CONNECTION_CLOSING);
+ writeFrame(e.getCloseFrame(channelId));
+ }
+ }
+ catch (Exception e)
+ {
+
+ for (AMQMethodListener listener : _frameListeners)
+ {
+ listener.error(e);
+ }
+
+ _minaProtocolSession.close();
+ }
+ }
+
+ public void contentHeaderReceived(int channelId, ContentHeaderBody body) throws AMQException
+ {
+
+ AMQChannel channel = getAndAssertChannel(channelId);
+
+ channel.publishContentHeader(body, this);
+
+ }
+
+ public void contentBodyReceived(int channelId, ContentBody body) throws AMQException
+ {
+ AMQChannel channel = getAndAssertChannel(channelId);
+
+ channel.publishContentBody(body, this);
+ }
+
+ 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;
+ _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.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(this);
+ markChannelawaitingCloseOk(channelId);
+ }
+ finally
+ {
+ removeChannel(channelId);
+ }
+ }
+ }
+
+ public void closeChannelOk(int channelId)
+ {
+ 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, HeartbeatConfig.getInstance().getTimeout(delay));
+ }
+ }
+
+ /**
+ * Closes all channels that were opened by this protocol session. This frees up all resources used by the channel.
+ *
+ * @throws AMQException if an error occurs while closing any channel
+ */
+ private void closeAllChannels() throws AMQException
+ {
+ for (AMQChannel channel : _channelMap.values())
+ {
+ channel.close(this);
+ }
+
+ _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;
+ closeAllChannels();
+ if (_managedObject != null)
+ {
+ _managedObject.unregister();
+ }
+
+ for (Task task : _taskList)
+ {
+ task.doTask(this);
+ }
+ }
+ }
+
+ public String toString()
+ {
+ return "AMQProtocolSession(" + _minaProtocolSession.getRemoteAddress() + ")";
+ }
+
+ public String dump()
+ {
+ return this + " last_sent=" + _lastSent + " last_received=" + _lastReceived;
+ }
+
+ /** @return an object that can be used to identity */
+ public Object getKey()
+ {
+ return _minaProtocolSession.getRemoteAddress();
+ }
+
+ /**
+ * Get the fully qualified domain name of the local address to which this session is bound. Since some servers may
+ * be bound to multiple addresses this could vary depending on the acceptor this session was created from.
+ *
+ * @return a String FQDN
+ */
+ public String getLocalFQDN()
+ {
+ SocketAddress address = _minaProtocolSession.getLocalAddress();
+ // we use the vmpipe address in some tests hence the need for this rather ugly test. The host
+ // information is used by SASL primary.
+ if (address instanceof InetSocketAddress)
+ {
+ return ((InetSocketAddress) address).getHostName();
+ }
+ else if (address instanceof VmPipeAddress)
+ {
+ return "vmpipe:" + ((VmPipeAddress) address).getPort();
+ }
+ else
+ {
+ throw new IllegalArgumentException("Unsupported socket address class: " + address);
+ }
+ }
+
+ public SaslServer getSaslServer()
+ {
+ return _saslServer;
+ }
+
+ public void setSaslServer(SaslServer saslServer)
+ {
+ _saslServer = saslServer;
+ }
+
+ 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()));
+ }
+ }
+ }
+
+ 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.getRemoteAddress();
+ }
+
+ public VirtualHost getVirtualHost()
+ {
+ return _virtualHost;
+ }
+
+ public void setVirtualHost(VirtualHost virtualHost) throws AMQException
+ {
+ _virtualHost = virtualHost;
+ _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 String getClientVersion()
+ {
+ return (_clientVersion == null) ? null : _clientVersion.toString();
+ }
+}
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..db5d882f51
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.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.protocol;
+
+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.ProtocolCodecFilter;
+import org.apache.mina.filter.codec.QpidProtocolCodecFilter;
+import org.apache.mina.filter.executor.ExecutorFilter;
+import org.apache.mina.transport.socket.nio.SocketSessionConfig;
+import org.apache.mina.util.SessionUtil;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.server.transport.ConnectorConfiguration;
+import org.apache.qpid.ssl.SSLContextFactory;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+/**
+ * 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;
+
+
+ public AMQPFastProtocolHandler(Integer applicationRegistryInstance)
+ {
+ this(ApplicationRegistry.getInstance(applicationRegistryInstance));
+ }
+
+ public AMQPFastProtocolHandler(IApplicationRegistry applicationRegistry)
+ {
+ _applicationRegistry = applicationRegistry;
+ _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);
+
+ ConnectorConfiguration connectorConfig = ApplicationRegistry.getInstance().
+ getConfiguredObject(ConnectorConfiguration.class);
+ if (connectorConfig.enableExecutorPool)
+ {
+ if (connectorConfig.enableSSL && isSSLClient(connectorConfig, protocolSession))
+ {
+ String keystorePath = connectorConfig.keystorePath;
+ String keystorePassword = connectorConfig.keystorePassword;
+ String certType = connectorConfig.certType;
+ SSLContextFactory sslContextFactory = new SSLContextFactory(keystorePath, keystorePassword, certType);
+ protocolSession.getFilterChain().addAfter("AsynchronousReadFilter", "sslFilter",
+ new SSLFilter(sslContextFactory.buildServerContext()));
+ }
+ protocolSession.getFilterChain().addBefore("AsynchronousWriteFilter", "protocolFilter", pcf);
+ }
+ else
+ {
+ protocolSession.getFilterChain().addLast("protocolFilter", pcf);
+ if (connectorConfig.enableSSL && isSSLClient(connectorConfig, protocolSession))
+ {
+ String keystorePath = connectorConfig.keystorePath;
+ String keystorePassword = connectorConfig.keystorePassword;
+ String certType = connectorConfig.certType;
+ SSLContextFactory sslContextFactory = new SSLContextFactory(keystorePath, keystorePassword, certType);
+ protocolSession.getFilterChain().addBefore("protocolFilter", "sslFilter",
+ new SSLFilter(sslContextFactory.buildServerContext()));
+ }
+
+ }
+
+ if (ApplicationRegistry.getInstance().getConfiguration().getBoolean("broker.connector.protectio", false))
+ {
+ try
+ {
+// //Add IO Protection Filters
+ IoFilterChain chain = protocolSession.getFilterChain();
+
+ int buf_size = 32768;
+ if (protocolSession.getConfig() instanceof SocketSessionConfig)
+ {
+ buf_size = ((SocketSessionConfig) protocolSession.getConfig()).getReceiveBufferSize();
+ }
+
+ protocolSession.getFilterChain().addLast("tempExecutorFilterForFilterBuilder", new ExecutorFilter());
+
+ ReadThrottleFilterBuilder readfilter = new ReadThrottleFilterBuilder();
+ readfilter.setMaximumConnectionBufferSize(buf_size);
+ readfilter.attach(chain);
+
+ WriteBufferLimitFilterBuilder writefilter = new WriteBufferLimitFilterBuilder();
+ writefilter.setMaximumConnectionBufferSize(buf_size * 2);
+ 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(ConnectorConfiguration connectionConfig,
+ IoSession protocolSession)
+ {
+ InetSocketAddress addr = (InetSocketAddress) protocolSession.getLocalAddress();
+ return addr.getPort() == connectionConfig.sslPort;
+ }
+}
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..c9316f7405
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.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.protocol;
+
+import javax.security.sasl.SaslServer;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.*;
+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 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;
+
+ /** @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();
+
+}
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..bd072985c4
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBean.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.
+ *
+ */
+/*
+ *
+ * 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.security.Principal;
+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.AMQFrame;
+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);
+ _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/HeartbeatConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/HeartbeatConfig.java
new file mode 100644
index 0000000000..310deaaf55
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/HeartbeatConfig.java
@@ -0,0 +1,67 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.protocol;
+
+import org.apache.qpid.configuration.Configured;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+public class HeartbeatConfig
+{
+ @Configured(path = "heartbeat.delay", defaultValue = "5")
+ public int delay = 5;//in secs
+ @Configured(path = "heartbeat.timeoutFactor", defaultValue = "2.0")
+ public double timeoutFactor = 2;
+
+ public double getTimeoutFactor()
+ {
+ return timeoutFactor;
+ }
+
+ public void setTimeoutFactor(double timeoutFactor)
+ {
+ this.timeoutFactor = timeoutFactor;
+ }
+
+ public int getDelay()
+ {
+ return delay;
+ }
+
+ public void setDelay(int delay)
+ {
+ this.delay = delay;
+ }
+
+ int getTimeout(int writeDelay)
+ {
+ return (int) (timeoutFactor * writeDelay);
+ }
+
+ public static HeartbeatConfig getInstance()
+ {
+ return ApplicationRegistry.getInstance().getConfiguredObject(HeartbeatConfig.class);
+ }
+
+ public String toString()
+ {
+ return "HeartBeatConfig{delay = " + delay + " timeoutFactor = " + timeoutFactor + "}";
+ }
+}
diff --git a/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..e6e713ac6d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java
@@ -0,0 +1,135 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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";
+
+ @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..dcc2becbc5
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java
@@ -0,0 +1,719 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.BasicContentHeaderProperties;
+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.registry.ApplicationRegistry;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.txn.TransactionalContext;
+import org.apache.qpid.server.exchange.Exchange;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A deliverable message.
+ */
+public class AMQMessage
+{
+ /** Used for debugging purposes. */
+ private static final Logger _log = Logger.getLogger(AMQMessage.class);
+
+ /** Used in clustering. @todo What for? */
+ private Set<Object> _tokens;
+
+ /** Only use in clustering. @todo What for? */
+ private AMQProtocolSession _publisher;
+
+ private final Long _messageId;
+
+ private final AtomicInteger _referenceCount = new AtomicInteger(1);
+
+ private AMQMessageHandle _messageHandle;
+
+ /** Holds the transactional context in which this message is being processed. */
+ private TransactionalContext _txnContext;
+
+ /**
+ * 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 boolean _deliveredToConsumer;
+
+ /** Flag to indicate that this message requires 'immediate' delivery. */
+ private boolean _immediate;
+
+ private TransientMessageData _transientMessageData = new TransientMessageData();
+
+ private long _expiration;
+
+
+
+
+ private Exchange _exchange;
+ private static final boolean SYNCED_CLOCKS =
+ ApplicationRegistry.getInstance().getConfiguration().getBoolean("advanced.synced-clocks", false);
+
+
+ public String debugIdentity()
+ {
+ return "(HC:" + System.identityHashCode(this) + " ID:" + _messageId + " Ref:" + _referenceCount.get() + ")";
+ }
+
+ public void setExpiration()
+ {
+ long expiration =
+ ((BasicContentHeaderProperties) _transientMessageData.getContentHeaderBody().properties).getExpiration();
+ long timestamp =
+ ((BasicContentHeaderProperties) _transientMessageData.getContentHeaderBody().properties).getTimestamp();
+
+ if (SYNCED_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 boolean isReferenced()
+ {
+ return _referenceCount.get() > 0;
+ }
+
+ public void setExchange(final Exchange exchange)
+ {
+ _exchange = exchange;
+ }
+
+ public void route() throws AMQException
+ {
+ _exchange.route(this);
+ }
+
+ public void enqueue(final List<AMQQueue> queues)
+ {
+ _transientMessageData.setDestinationQueues(queues);
+ }
+
+ /**
+ * 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(), _messageId) - 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(),
+ _messageId, ++_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 StoreContext getStoreContext()
+ {
+ return _txnContext.getStoreContext();
+ }
+
+ private class BodyContentIterator implements Iterator<ContentChunk>
+ {
+
+ private int _index = -1;
+
+ public boolean hasNext()
+ {
+ try
+ {
+ return _index < (_messageHandle.getBodyCount(getStoreContext(), _messageId) - 1);
+ }
+ catch (AMQException e)
+ {
+ _log.error("Error getting body count: " + e, e);
+
+ return false;
+ }
+ }
+
+ public ContentChunk next()
+ {
+ try
+ {
+ return _messageHandle.getContentChunk(getStoreContext(), _messageId, ++_index);
+ }
+ catch (AMQException e)
+ {
+ throw new RuntimeException("Error getting content body: " + e, e);
+ }
+ }
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ public AMQMessage(Long messageId, MessagePublishInfo info, TransactionalContext txnContext)
+ {
+ _messageId = messageId;
+ _txnContext = txnContext;
+ _immediate = info.isImmediate();
+ _transientMessageData.setMessagePublishInfo(info);
+
+ }
+
+ /**
+ * 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
+ {
+ _messageId = messageId;
+ _messageHandle = factory.createMessageHandle(messageId, store, true);
+ _txnContext = txnConext;
+ _transientMessageData = null;
+ }
+
+ /**
+ * Used in testing only. This allows the passing of the content header immediately on construction.
+ *
+ * @param messageId
+ * @param info
+ * @param txnContext
+ * @param contentHeader
+ */
+ public AMQMessage(Long messageId, MessagePublishInfo info, TransactionalContext txnContext,
+ ContentHeaderBody contentHeader) throws AMQException
+ {
+ this(messageId, info, txnContext);
+ setContentHeaderBody(contentHeader);
+ }
+
+ /* *
+ * Used in testing only. This allows the passing of the content header and some body fragments on construction.
+ *
+ * @param messageId
+ * @param info
+ * @param txnContext
+ * @param contentHeader
+ * @param destinationQueues
+ * @param contentBodies
+ *
+ * @throws AMQException
+ */ /*
+ public AMQMessage(Long messageId, MessagePublishInfo info, TransactionalContext txnContext,
+ ContentHeaderBody contentHeader, List<AMQQueue> destinationQueues, List<ContentChunk> contentBodies,
+ MessageStore messageStore, StoreContext storeContext, MessageHandleFactory messageHandleFactory) throws AMQException
+ {
+ this(messageId, info, txnContext, contentHeader);
+ _transientMessageData.setDestinationQueues(destinationQueues);
+ routingComplete(messageStore, storeContext, messageHandleFactory);
+ for (ContentChunk cb : contentBodies)
+ {
+ addContentBodyFrame(storeContext, cb);
+ }
+ }
+ */
+ protected AMQMessage(AMQMessage msg) throws AMQException
+ {
+ _messageId = msg._messageId;
+ _messageHandle = msg._messageHandle;
+ _txnContext = msg._txnContext;
+ _deliveredToConsumer = msg._deliveredToConsumer;
+ _transientMessageData = msg._transientMessageData;
+ }
+
+ 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
+ {
+ if (_transientMessageData != null)
+ {
+ return _transientMessageData.getContentHeaderBody();
+ }
+ else
+ {
+ return _messageHandle.getContentHeaderBody(getStoreContext(), _messageId);
+ }
+ }
+
+ public void setContentHeaderBody(ContentHeaderBody contentHeaderBody) throws AMQException
+ {
+ _transientMessageData.setContentHeaderBody(contentHeaderBody);
+ }
+
+ public void routingComplete(MessageStore store, StoreContext storeContext, MessageHandleFactory factory)
+ throws AMQException
+ {
+ final boolean persistent = isPersistent();
+ _messageHandle = factory.createMessageHandle(_messageId, store, persistent);
+ if (persistent)
+ {
+ _txnContext.beginTranIfNecessary();
+ }
+
+ // enqueuing the messages ensure that if required the destinations are recorded to a
+ // persistent store
+
+ for (AMQQueue q : _transientMessageData.getDestinationQueues())
+ {
+ _messageHandle.enqueue(storeContext, _messageId, q);
+ }
+
+ if (_transientMessageData.getContentHeaderBody().bodySize == 0)
+ {
+ deliver(storeContext);
+ }
+ }
+
+ public boolean addContentBodyFrame(StoreContext storeContext, ContentChunk contentChunk) throws AMQException
+ {
+ _transientMessageData.addBodyLength(contentChunk.getSize());
+ final boolean allContentReceived = isAllContentReceived();
+ _messageHandle.addContentBodyFrame(storeContext, _messageId, contentChunk, allContentReceived);
+ if (allContentReceived)
+ {
+ deliver(storeContext);
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public boolean isAllContentReceived() throws AMQException
+ {
+ return _transientMessageData.isAllContentReceived();
+ }
+
+ public Long getMessageId()
+ {
+ return _messageId;
+ }
+
+ /**
+ * 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;
+ }
+
+ /** Threadsafe. Increment the reference count on the message. */
+ public void incrementReference()
+ {
+ _referenceCount.incrementAndGet();
+ // if (_log.isDebugEnabled())
+ // {
+ // _log.debug("Ref count on message " + debugIdentity() + " incremented " + Arrays.asList(Thread.currentThread().getStackTrace()).subList(3, 6));
+ // }
+ }
+
+ /**
+ * 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)
+ {
+ try
+ {
+ // if (_log.isDebugEnabled())
+ // {
+ // _log.debug("Decremented ref count on message " + debugIdentity() + " is zero; removing message" + Arrays.asList(Thread.currentThread().getStackTrace()).subList(3, 6));
+ // }
+
+ // 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, _messageId);
+ }
+ }
+ catch (AMQException e)
+ {
+ // to maintain consistency, we revert the count
+ incrementReference();
+ throw new MessageCleanupException(_messageId, e);
+ }
+ }
+ else
+ {
+ if (count < 0)
+ {
+ throw new MessageCleanupException("Reference count for message id " + debugIdentity()
+ + " has gone below 0.");
+ }
+ }
+ }
+
+ public void setPublisher(AMQProtocolSession publisher)
+ {
+ _publisher = publisher;
+ }
+
+ public AMQProtocolSession getPublisher()
+ {
+ return _publisher;
+ }
+
+ /**
+ * Called selectors to determin if the message has already been sent
+ *
+ * @return _deliveredToConsumer
+ */
+ public boolean getDeliveredToConsumer()
+ {
+ return _deliveredToConsumer;
+ }
+
+
+ public boolean checkToken(Object token)
+ {
+
+ if (_tokens == null)
+ {
+ _tokens = new HashSet<Object>();
+ }
+
+ if (_tokens.contains(token))
+ {
+ return true;
+ }
+ else
+ {
+ _tokens.add(token);
+
+ return false;
+ }
+ }
+
+ /**
+ * Registers a queue to which this message is to be delivered. This is called from the exchange when it is routing
+ * the message. This will be called before any content bodies have been received so that the choice of
+ * AMQMessageHandle implementation can be picked based on various criteria.
+ *
+ * @param queue the queue
+ *
+ * @throws org.apache.qpid.AMQException if there is an error enqueuing the message
+ */
+ public void enqueue(AMQQueue queue) throws AMQException
+ {
+ _transientMessageData.addDestinationQueue(queue);
+ }
+
+ /**
+ * NOTE: Think about why you are using this method. Normal usages would want to do
+ * AMQQueue.dequeue(StoreContext, AMQMessage)
+ * This will keep the queue statistics up-to-date.
+ * Currently this method is only called _correctly_ from AMQQueue dequeue.
+ * Ideally we would have a better way for the queue to dequeue the message.
+ * Especially since enqueue isn't the recipriocal of this method.
+ * @deprecated
+ * @param storeContext
+ * @param queue
+ * @throws AMQException
+ */
+ void dequeue(StoreContext storeContext, AMQQueue queue) throws AMQException
+ {
+ _messageHandle.dequeue(storeContext, _messageId, queue);
+ }
+
+ public boolean isPersistent() throws AMQException
+ {
+ if (_transientMessageData != null)
+ {
+ return _transientMessageData.isPersistent();
+ }
+ else
+ {
+ return _messageHandle.isPersistent(getStoreContext(), _messageId);
+ }
+ }
+
+ /**
+ * Called to enforce the 'immediate' flag.
+ *
+ * @throws NoConsumersException if the message is marked for immediate delivery but has not been marked as delivered
+ * to a consumer
+ */
+ public void checkDeliveredToConsumer() throws NoConsumersException
+ {
+
+ if (_immediate && !_deliveredToConsumer)
+ {
+ throw new NoConsumersException(this);
+ }
+ }
+
+ public MessagePublishInfo getMessagePublishInfo() throws AMQException
+ {
+ MessagePublishInfo pb;
+ if (_transientMessageData != null)
+ {
+ pb = _transientMessageData.getMessagePublishInfo();
+ }
+ else
+ {
+ pb = _messageHandle.getMessagePublishInfo(getStoreContext(), _messageId);
+ }
+
+ return pb;
+ }
+
+ 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()
+ {
+ _deliveredToConsumer = true;
+ }
+
+ private void deliver(StoreContext storeContext) throws AMQException
+ {
+ // we get a reference to the destination queues now so that we can clear the
+ // transient message data as quickly as possible
+ List<AMQQueue> destinationQueues = _transientMessageData.getDestinationQueues();
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Delivering message " + debugIdentity() + " to " + destinationQueues);
+ }
+
+ 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(storeContext, _messageId,
+ _transientMessageData.getMessagePublishInfo(), _transientMessageData.getContentHeaderBody());
+
+ // 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());
+
+ _transientMessageData = null;
+
+ for (AMQQueue q : destinationQueues)
+ {
+ // Increment the references to this message for each queue delivery.
+ incrementReference();
+ // normal deliver so add this message at the end.
+ _txnContext.deliver(q.createEntry(this), false);
+ }
+ }
+ finally
+ {
+
+ // Remove refence for routing process . Reference count should now == delivered queue count
+ decrementReference(storeContext);
+ }
+ }
+
+
+ public AMQMessageHandle getMessageHandle()
+ {
+ return _messageHandle;
+ }
+
+ public long getSize()
+ {
+ try
+ {
+ long size = getContentHeaderBody().bodySize;
+
+ return size;
+ }
+ catch (AMQException e)
+ {
+ _log.error(e.toString(), e);
+
+ return 0;
+ }
+
+ }
+
+ public void restoreTransientMessageData() throws AMQException
+ {
+ TransientMessageData transientMessageData = new TransientMessageData();
+ transientMessageData.setMessagePublishInfo(getMessagePublishInfo());
+ transientMessageData.setContentHeaderBody(getContentHeaderBody());
+ transientMessageData.addBodyLength(getContentHeaderBody().getSize());
+ _transientMessageData = transientMessageData;
+ }
+
+
+ public String toString()
+ {
+ // return "Message[" + debugIdentity() + "]: " + _messageId + "; ref count: " + _referenceCount + "; taken : " +
+ // _taken + " by :" + _takenBySubcription;
+
+ return "Message[" + debugIdentity() + "]: " + _messageId + "; 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..ede55b3bbf
--- /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.
+ *
+ * The method all take a messageId to avoid having to store it in the instance - the AMQMessage container
+ * must already keen the messageId so it is pointless storing it twice.
+ */
+public interface AMQMessageHandle
+{
+ ContentHeaderBody getContentHeaderBody(StoreContext context, Long messageId) throws AMQException;
+
+ /**
+ * @return the number of body frames associated with this message
+ */
+ int getBodyCount(StoreContext context, Long messageId) throws AMQException;
+
+ /**
+ * @return the size of the body
+ */
+ long getBodySize(StoreContext context, Long messageId) throws AMQException;
+
+ /**
+ * Get a particular content body
+ * @param index the index of the body to retrieve, must be between 0 and getBodyCount() - 1
+ * @return a content body
+ * @throws IllegalArgumentException if the index is invalid
+ */
+ ContentChunk getContentChunk(StoreContext context, Long messageId, int index) throws IllegalArgumentException, AMQException;
+
+ void addContentBodyFrame(StoreContext storeContext, Long messageId, ContentChunk contentBody, boolean isLastContentBody) throws AMQException;
+
+ MessagePublishInfo getMessagePublishInfo(StoreContext context, Long messageId) throws AMQException;
+
+ boolean isRedelivered();
+
+ void setRedelivered(boolean redelivered);
+
+ boolean isPersistent(StoreContext context, Long messageId) throws AMQException;
+
+ void setPublishAndContentHeaderBody(StoreContext storeContext, Long messageId, MessagePublishInfo messagePublishInfo,
+ ContentHeaderBody contentHeaderBody)
+ throws AMQException;
+
+ void removeMessage(StoreContext storeContext, Long messageId) throws AMQException;
+
+ void enqueue(StoreContext storeContext, Long messageId, AMQQueue queue) throws AMQException;
+
+ void dequeue(StoreContext storeContext, Long messageId, AMQQueue queue) throws AMQException;
+
+ long getArrivalTime();
+}
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..4a0121700c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java
@@ -0,0 +1,1022 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.configuration.Configured;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.management.Managable;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import javax.management.JMException;
+import java.text.MessageFormat;
+import java.util.*;
+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;
+
+/**
+ * This is an AMQ Queue, and should not be confused with a JMS queue or any other abstraction like that. It is described
+ * fully in RFC 006.
+ */
+public class AMQQueue implements Managable, Comparable
+{
+
+ /**
+ * 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.
+ */
+ public 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.
+ */
+ public static final class ExistingSubscriptionPreventsExclusive extends AMQException
+ {
+ public ExistingSubscriptionPreventsExclusive()
+ {
+ super("");
+ }
+ }
+
+ private static final Logger _logger = Logger.getLogger(AMQQueue.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;
+
+ /** Holds subscribers to the queue. */
+ private final SubscriptionSet _subscribers;
+
+ private final SubscriptionFactory _subscriptionFactory;
+
+ private final AtomicInteger _subscriberCount = new AtomicInteger();
+
+ private final AtomicBoolean _isExclusive = new AtomicBoolean();
+
+ private final AtomicBoolean _deleted = new AtomicBoolean(false);
+
+ private List<Task> _deleteTaskList = new CopyOnWriteArrayList<Task>();
+
+ /** Manages message delivery. */
+ private final DeliveryManager _deliveryMgr;
+
+ /** Used to track bindings to exchanges so that on deletion they can easily be cancelled. */
+ private final ExchangeBindings _bindings = new ExchangeBindings(this);
+
+ /** Executor on which asynchronous delivery will be carriedout where required */
+ private final Executor _asyncDelivery;
+
+ private final AMQQueueMBean _managedObject;
+
+ private final VirtualHost _virtualHost;
+
+ /** max allowed size(KB) of a single message */
+ @Configured(path = "maximumMessageSize", defaultValue = "0")
+ public long _maximumMessageSize;
+
+ /** max allowed number of messages on a queue. */
+ @Configured(path = "maximumMessageCount", defaultValue = "0")
+ public long _maximumMessageCount;
+
+ /** max queue depth for the queue */
+ @Configured(path = "maximumQueueDepth", defaultValue = "0")
+ public long _maximumQueueDepth;
+
+ /** maximum message age before alerts occur */
+ @Configured(path = "maximumMessageAge", defaultValue = "0")
+ public long _maximumMessageAge;
+
+ /** the minimum interval between sending out consequetive alerts of the same type */
+ @Configured(path = "minimumAlertRepeatGap", defaultValue = "0")
+ public long _minimumAlertRepeatGap;
+
+ /** total messages received by the queue since startup. */
+ public AtomicLong _totalMessagesReceived = new AtomicLong();
+
+
+ private final Set<NotificationCheck> _notificationChecks = EnumSet.noneOf(NotificationCheck.class);
+
+
+ public AMQQueue(AMQShortString name, boolean durable, AMQShortString owner, boolean autoDelete, VirtualHost virtualHost)
+ throws AMQException
+ {
+ this(name, durable, owner, autoDelete, virtualHost, AsyncDeliveryConfig.getAsyncDeliveryExecutor(),
+ new SubscriptionSet(), new SubscriptionImpl.Factory());
+ }
+
+ protected AMQQueue(AMQShortString name, boolean durable, AMQShortString owner, boolean autoDelete,
+ VirtualHost virtualHost, SubscriptionSet subscribers) throws AMQException
+ {
+ this(name, durable, owner, autoDelete, virtualHost, AsyncDeliveryConfig.getAsyncDeliveryExecutor(), subscribers,
+ new SubscriptionImpl.Factory());
+ }
+
+ protected AMQQueue(AMQShortString name, boolean durable, AMQShortString owner, boolean autoDelete,
+ VirtualHost virtualHost, Executor asyncDelivery, SubscriptionSet subscribers,
+ SubscriptionFactory subscriptionFactory) 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;
+ _asyncDelivery = asyncDelivery;
+
+ _managedObject = createMBean();
+ _managedObject.register();
+
+ _subscribers = subscribers;
+ _subscriptionFactory = subscriptionFactory;
+ _deliveryMgr = new ConcurrentSelectorDeliveryManager(_subscribers, this);
+
+ // This ensure that the notification checks for the configured alerts are created.
+ setMaximumMessageAge(_maximumMessageAge);
+ setMaximumMessageCount(_maximumMessageCount);
+ setMaximumMessageSize(_maximumMessageSize);
+ setMaximumQueueDepth(_maximumQueueDepth);
+
+ }
+
+ private AMQQueueMBean createMBean() throws AMQException
+ {
+ try
+ {
+ return new AMQQueueMBean(this);
+ }
+ catch (JMException ex)
+ {
+ throw new AMQException("AMQQueue MBean creation has failed ", ex);
+ }
+ }
+
+ public final AMQShortString getName()
+ {
+ return _name;
+ }
+
+ public boolean isShared()
+ {
+ return _owner == null;
+ }
+
+ public boolean isDurable()
+ {
+ return _durable;
+ }
+
+ public AMQShortString getOwner()
+ {
+ return _owner;
+ }
+
+ public boolean isAutoDelete()
+ {
+ return _autoDelete;
+ }
+
+ public boolean isDeleted()
+ {
+ return _deleted.get();
+ }
+
+ /** @return no of messages(undelivered) on the queue. */
+ public int getMessageCount()
+ {
+ return _deliveryMgr.getQueueMessageCount();
+ }
+
+ /** @return List of messages(undelivered) on the queue. */
+ public List<QueueEntry> getMessagesOnTheQueue()
+ {
+ return _deliveryMgr.getMessages();
+ }
+
+ /**
+ * Returns messages within the given range of message Ids.
+ *
+ * @param fromMessageId
+ * @param toMessageId
+ *
+ * @return List of messages
+ */
+ public List<QueueEntry> getMessagesOnTheQueue(long fromMessageId, long toMessageId)
+ {
+ return _deliveryMgr.getMessages(fromMessageId, toMessageId);
+ }
+
+ public long getQueueDepth()
+ {
+ return _deliveryMgr.getTotalMessageSize();
+ }
+
+ /**
+ * @param messageId
+ *
+ * @return QueueEntry with give id if exists. null if QueueEntry with given id doesn't exist.
+ */
+ public QueueEntry getMessageOnTheQueue(long messageId)
+ {
+ List<QueueEntry> list = getMessagesOnTheQueue(messageId, messageId);
+ if ((list == null) || (list.size() == 0))
+ {
+ return null;
+ }
+
+ return list.get(0);
+ }
+
+ /**
+ * Moves messages from this queue to another queue, and also commits the move on the message store. Delivery activity
+ * on the queues being moved between is suspended during the move.
+ *
+ * @param fromMessageId The first message id to move.
+ * @param toMessageId The last message id to move.
+ * @param queueName The queue to move the messages to.
+ * @param storeContext The context of the message store under which to perform the move. This is associated with
+ * the stores transactional context.
+ */
+ public synchronized void moveMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName,
+ StoreContext storeContext)
+ {
+ AMQQueue toQueue = getVirtualHost().getQueueRegistry().getQueue(new AMQShortString(queueName));
+
+ MessageStore fromStore = getVirtualHost().getMessageStore();
+ MessageStore toStore = toQueue.getVirtualHost().getMessageStore();
+
+ if (toStore != fromStore)
+ {
+ throw new RuntimeException("Can only move messages between queues on the same message store.");
+ }
+
+ try
+ {
+ // Obtain locks to prevent activity on the queues being moved between.
+ startMovingMessages();
+ toQueue.startMovingMessages();
+
+ // Get the list of messages to move.
+ List<QueueEntry> foundMessagesList = getMessagesOnTheQueue(fromMessageId, toMessageId);
+
+ try
+ {
+ fromStore.beginTran(storeContext);
+
+ // Move the messages in on the message store.
+ for (QueueEntry entry : foundMessagesList)
+ {
+ AMQMessage message = entry.getMessage();
+ fromStore.dequeueMessage(storeContext, _name, message.getMessageId());
+ toStore.enqueueMessage(storeContext, toQueue._name, message.getMessageId());
+ }
+
+ // Commit and flush the move transcations.
+ try
+ {
+ fromStore.commitTran(storeContext);
+ }
+ catch (AMQException e)
+ {
+ throw new RuntimeException("Failed to commit transaction whilst moving messages on message store.", e);
+ }
+
+ // Move the messages on the in-memory queues.
+ toQueue.enqueueMovedMessages(storeContext, foundMessagesList);
+ _deliveryMgr.removeMovedMessages(foundMessagesList);
+ }
+ // Abort the move transactions on move failures.
+ catch (AMQException e)
+ {
+ try
+ {
+ fromStore.abortTran(storeContext);
+ }
+ catch (AMQException ae)
+ {
+ throw new RuntimeException("Failed to abort transaction whilst moving messages on message store.", ae);
+ }
+ }
+ }
+ // Release locks to allow activity on the queues being moved between to continue.
+ finally
+ {
+ toQueue.stopMovingMessages();
+ stopMovingMessages();
+ }
+ }
+
+ /**
+ * Copies messages on this queue to another queue, and also commits the move on the message store. Delivery activity
+ * on the queues being moved between is suspended during the move.
+ *
+ * @param fromMessageId The first message id to move.
+ * @param toMessageId The last message id to move.
+ * @param queueName The queue to move the messages to.
+ * @param storeContext The context of the message store under which to perform the move. This is associated with
+ * the stores transactional context.
+ */
+ public synchronized void copyMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName,
+ StoreContext storeContext)
+ {
+ AMQQueue toQueue = getVirtualHost().getQueueRegistry().getQueue(new AMQShortString(queueName));
+
+ MessageStore fromStore = getVirtualHost().getMessageStore();
+ MessageStore toStore = toQueue.getVirtualHost().getMessageStore();
+
+ if (toStore != fromStore)
+ {
+ throw new RuntimeException("Can only move messages between queues on the same message store.");
+ }
+
+ try
+ {
+ // Obtain locks to prevent activity on the queues being moved between.
+ startMovingMessages();
+ toQueue.startMovingMessages();
+
+ // Get the list of messages to move.
+ List<QueueEntry> foundMessagesList = getMessagesOnTheQueue(fromMessageId, toMessageId);
+
+ try
+ {
+ fromStore.beginTran(storeContext);
+
+ // Move the messages in on the message store.
+ for (QueueEntry entry : foundMessagesList)
+ {
+ AMQMessage message = entry.getMessage();
+ toStore.enqueueMessage(storeContext, toQueue._name, message.getMessageId());
+ message.takeReference();
+ }
+
+ // Commit and flush the move transcations.
+ try
+ {
+ fromStore.commitTran(storeContext);
+ }
+ catch (AMQException e)
+ {
+ throw new RuntimeException("Failed to commit transaction whilst moving messages on message store.", e);
+ }
+
+ // Move the messages on the in-memory queues.
+ toQueue.enqueueMovedMessages(storeContext, foundMessagesList);
+ }
+ // Abort the move transactions on move failures.
+ catch (AMQException e)
+ {
+ try
+ {
+ fromStore.abortTran(storeContext);
+ }
+ catch (AMQException ae)
+ {
+ throw new RuntimeException("Failed to abort transaction whilst moving messages on message store.", ae);
+ }
+ }
+ }
+ // Release locks to allow activity on the queues being moved between to continue.
+ finally
+ {
+ toQueue.stopMovingMessages();
+ stopMovingMessages();
+ }
+ }
+
+ /**
+ * Removes messages from this queue, and also commits the remove on the message store. Delivery activity
+ * on the queues being moved between is suspended during the remove.
+ *
+ * @param fromMessageId The first message id to move.
+ * @param toMessageId The last message id to move.
+ * @param storeContext The context of the message store under which to perform the move. This is associated with
+ * the stores transactional context.
+ */
+ public synchronized void removeMessagesFromQueue(long fromMessageId, long toMessageId, StoreContext storeContext)
+ {
+ MessageStore fromStore = getVirtualHost().getMessageStore();
+
+ try
+ {
+ // Obtain locks to prevent activity on the queues being moved between.
+ startMovingMessages();
+
+ // Get the list of messages to move.
+ List<QueueEntry> foundMessagesList = getMessagesOnTheQueue(fromMessageId, toMessageId);
+
+ try
+ {
+ fromStore.beginTran(storeContext);
+
+ // remove the messages in on the message store.
+ for (QueueEntry entry : foundMessagesList)
+ {
+ AMQMessage message = entry.getMessage();
+ fromStore.dequeueMessage(storeContext, _name, message.getMessageId());
+ }
+
+ // Commit and flush the move transcations.
+ try
+ {
+ fromStore.commitTran(storeContext);
+ }
+ catch (AMQException e)
+ {
+ throw new RuntimeException("Failed to commit transaction whilst moving messages on message store.", e);
+ }
+
+ // remove the messages on the in-memory queues.
+ _deliveryMgr.removeMovedMessages(foundMessagesList);
+ }
+ // Abort the move transactions on move failures.
+ catch (AMQException e)
+ {
+ try
+ {
+ fromStore.abortTran(storeContext);
+ }
+ catch (AMQException ae)
+ {
+ throw new RuntimeException("Failed to abort transaction whilst moving messages on message store.", ae);
+ }
+ }
+ }
+ // Release locks to allow activity on the queues being moved between to continue.
+ finally
+ {
+ stopMovingMessages();
+ }
+ }
+
+ public void startMovingMessages()
+ {
+ _deliveryMgr.startMovingMessages();
+ }
+
+ private void enqueueMovedMessages(StoreContext storeContext, List<QueueEntry> messageList)
+ {
+ _deliveryMgr.enqueueMovedMessages(storeContext, messageList);
+ _totalMessagesReceived.addAndGet(messageList.size());
+ }
+
+ public void stopMovingMessages()
+ {
+ _deliveryMgr.stopMovingMessages();
+ _deliveryMgr.processAsync(_asyncDelivery);
+ }
+
+ /** @return MBean object associated with this Queue */
+ public ManagedObject getManagedObject()
+ {
+ return _managedObject;
+ }
+
+ 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 int getConsumerCount()
+ {
+ return _subscribers.size();
+ }
+
+ public int getActiveConsumerCount()
+ {
+ return _subscribers.getWeight();
+ }
+
+ public long getReceivedMessageCount()
+ {
+ return _totalMessagesReceived.get();
+ }
+
+ 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 getOldestMessageArrivalTime()
+ {
+ return _deliveryMgr.getOldestMessageArrival();
+
+ }
+
+ /** Removes the QueueEntry from the top of the queue. */
+ public synchronized void deleteMessageFromTop(StoreContext storeContext) throws AMQException
+ {
+ _deliveryMgr.removeAMessageFromTop(storeContext, this);
+ }
+
+ /** removes all the messages from the queue. */
+ public synchronized long clearQueue(StoreContext storeContext) throws AMQException
+ {
+ return _deliveryMgr.clearAllMessages(storeContext);
+ }
+
+ public void bind(AMQShortString routingKey, FieldTable arguments, Exchange exchange) 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(AMQShortString routingKey, FieldTable arguments, Exchange exchange) throws AMQException
+ {
+ exchange.deregisterQueue(routingKey, this, arguments);
+ if (isDurable() && exchange.isDurable())
+ {
+ _virtualHost.getMessageStore().unbindQueue(exchange, routingKey, this, arguments);
+ }
+
+ _bindings.remove(routingKey, arguments, exchange);
+ }
+
+ public void registerProtocolSession(AMQProtocolSession ps, int channel, AMQShortString consumerTag, boolean acks,
+ FieldTable filters, boolean noLocal, boolean exclusive) throws AMQException
+ {
+ if (incrementSubscriberCount() > 1)
+ {
+ if (isExclusive())
+ {
+ decrementSubscriberCount();
+ throw new ExistingExclusiveSubscription();
+ }
+ else if (exclusive)
+ {
+ decrementSubscriberCount();
+ throw new ExistingSubscriptionPreventsExclusive();
+ }
+
+ }
+ else if (exclusive)
+ {
+ setExclusive(true);
+ }
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug(MessageFormat.format("Registering protocol session {0} with channel {1} and "
+ + "consumer tag {2} with {3}", ps, channel, consumerTag, this));
+ }
+
+ Subscription subscription =
+ _subscriptionFactory.createSubscription(channel, ps, consumerTag, acks, filters, noLocal, this);
+
+ if (subscription.filtersMessages())
+ {
+ if (_deliveryMgr.hasQueuedMessages())
+ {
+ _deliveryMgr.populatePreDeliveryQueue(subscription);
+ }
+ }
+
+ _subscribers.addSubscriber(subscription);
+ if(exclusive)
+ {
+ _subscribers.setExclusive(true);
+ }
+ }
+
+ private boolean isExclusive()
+ {
+ return _isExclusive.get();
+ }
+
+ private void setExclusive(boolean exclusive)
+ {
+ _isExclusive.set(exclusive);
+ }
+
+ private int incrementSubscriberCount()
+ {
+ return _subscriberCount.incrementAndGet();
+ }
+
+ private int decrementSubscriberCount()
+ {
+ return _subscriberCount.decrementAndGet();
+ }
+
+ public void unregisterProtocolSession(AMQProtocolSession ps, int channel, AMQShortString consumerTag) throws AMQException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug(MessageFormat.format(
+ "Unregistering protocol session {0} with channel {1} and consumer tag {2} from {3}",
+ ps, channel, consumerTag, this));
+ }
+
+ _subscribers.setExclusive(false);
+ Subscription removedSubscription;
+ if ((removedSubscription = _subscribers.removeSubscriber(_subscriptionFactory.createSubscription(channel, ps,
+ consumerTag)))
+ == null)
+ {
+ throw new AMQException("Protocol session with channel " + channel + " and consumer tag " + consumerTag
+ + " and protocol session key " + ps.getKey() + " not registered with queue " + this);
+ }
+
+ removedSubscription.close();
+ setExclusive(false);
+ decrementSubscriberCount();
+
+ // if we are eligible for auto deletion, unregister from the queue registry
+ if (_autoDelete && _subscribers.isEmpty())
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Auto-deleteing queue:" + this);
+ }
+
+ autodelete();
+ // we need to manually fire the event to the removed subscription (which was the last one left for this
+ // queue. This is because the delete method uses the subscription set which has just been cleared
+ removedSubscription.queueDeleted(this);
+ }
+ }
+
+ public boolean isUnused()
+ {
+ return _subscribers.isEmpty();
+ }
+
+ public boolean isEmpty()
+ {
+ return !_deliveryMgr.hasQueuedMessages();
+ }
+
+ public int delete(boolean checkUnused, boolean checkEmpty) throws AMQException
+ {
+ if (checkUnused && !_subscribers.isEmpty())
+ {
+ _logger.info("Will not delete " + this + " as it is in use.");
+
+ return 0;
+ }
+ else if (checkEmpty && _deliveryMgr.hasQueuedMessages())
+ {
+ _logger.info("Will not delete " + this + " as it is not empty.");
+
+ return 0;
+ }
+ else
+ {
+ delete();
+
+ return _deliveryMgr.getQueueMessageCount();
+ }
+ }
+
+ public void delete() throws AMQException
+ {
+ if (!_deleted.getAndSet(true))
+ {
+ _subscribers.queueDeleted(this);
+ _bindings.deregister();
+ _virtualHost.getQueueRegistry().unregisterQueue(_name);
+ _managedObject.unregister();
+ for (Task task : _deleteTaskList)
+ {
+ task.doTask(this);
+ }
+
+ _deleteTaskList.clear();
+ }
+ }
+
+ protected void autodelete() throws AMQException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug(MessageFormat.format("autodeleting {0}", this));
+ }
+
+ delete();
+ }
+
+ /*public void processGet(StoreContext storeContext, AMQMessage msg, boolean deliverFirst) throws AMQException
+ {
+ // fixme not sure what this is doing. should we be passing deliverFirst through here?
+ // This code is not used so when it is perhaps it should
+ _deliveryMgr.deliver(storeContext, getName(), msg, deliverFirst);
+ try
+ {
+ msg.checkDeliveredToConsumer();
+ updateReceivedMessageCount(msg);
+ }
+ catch (NoConsumersException e)
+ {
+ // as this message will be returned, it should be removed
+ // from the queue:
+ dequeue(storeContext, msg);
+ }
+ }*/
+
+ // public DeliveryManager getDeliveryManager()
+ // {
+ // return _deliveryMgr;
+ // }
+
+ public void process(StoreContext storeContext, QueueEntry entry, boolean deliverFirst) throws AMQException
+ {
+ AMQMessage msg = entry.getMessage();
+ _deliveryMgr.deliver(storeContext, _name, entry, deliverFirst);
+ try
+ {
+ msg.checkDeliveredToConsumer();
+ updateReceivedMessageCount(entry);
+ }
+ catch (NoConsumersException e)
+ {
+ // as this message will be returned, it should be removed
+ // from the queue:
+ dequeue(storeContext, entry);
+ }
+ }
+
+ public void dequeue(StoreContext storeContext, QueueEntry entry) throws FailedDequeueException
+ {
+ try
+ {
+ entry.getMessage().dequeue(storeContext, this);
+ }
+ 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);
+ }
+ }
+
+ public void deliverAsync()
+ {
+ _deliveryMgr.processAsync(_asyncDelivery);
+ }
+
+ protected SubscriptionManager getSubscribers()
+ {
+ return _subscribers;
+ }
+
+ protected void updateReceivedMessageCount(QueueEntry entry) throws AMQException
+ {
+ AMQMessage msg = entry.getMessage();
+
+ if (!msg.isRedelivered())
+ {
+ _totalMessagesReceived.incrementAndGet();
+ }
+
+ try
+ {
+ _managedObject.checkForNotification(msg);
+ }
+ catch (JMException e)
+ {
+ throw new AMQException("Unable to get notification from manage queue: " + e, e);
+ }
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+
+ if ((o == null) || (getClass() != o.getClass()))
+ {
+ return false;
+ }
+
+ final AMQQueue amqQueue = (AMQQueue) o;
+
+ return (_name.equals(amqQueue._name));
+ }
+
+ public int hashCode()
+ {
+ return _name.hashCode();
+ }
+
+ public String toString()
+ {
+ return "Queue(" + _name + ")@" + System.identityHashCode(this);
+ }
+
+ public boolean performGet(AMQProtocolSession session, AMQChannel channel, boolean acks) throws AMQException
+ {
+ return _deliveryMgr.performGet(session, channel, acks);
+ }
+
+ public QueueRegistry getQueueRegistry()
+ {
+ return _virtualHost.getQueueRegistry();
+ }
+
+ public VirtualHost getVirtualHost()
+ {
+ return _virtualHost;
+ }
+
+ public static interface Task
+ {
+ public void doTask(AMQQueue queue) throws AMQException;
+ }
+
+ public void addQueueDeleteTask(Task task)
+ {
+ _deleteTaskList.add(task);
+ }
+
+ 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 void subscriberHasPendingResend(boolean hasContent, SubscriptionImpl subscription, QueueEntry entry)
+ {
+ _deliveryMgr.subscriberHasPendingResend(hasContent, subscription, entry);
+ }
+
+ public QueueEntry createEntry(AMQMessage amqMessage)
+ {
+ return new QueueEntry(this, amqMessage);
+ }
+
+ public int compareTo(Object o)
+ {
+ return _name.compareTo(((AMQQueue) o).getName());
+ }
+
+
+ public void removeExpiredIfNoSubscribers() throws AMQException
+ {
+ synchronized(_subscribers.getChangeLock())
+ {
+ if(_subscribers.isEmpty())
+ {
+ _deliveryMgr.removeExpired();
+ }
+ }
+ }
+
+ public final Set<NotificationCheck> getNotificationChecks()
+ {
+ return _notificationChecks;
+ }
+}
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..348a136f9d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueMBean.java
@@ -0,0 +1,479 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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);
+ _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);
+ }
+
+ public Long getMaximumQueueDepth()
+ {
+ long queueDepthInBytes = _queue.getMaximumQueueDepth();
+
+ return queueDepthInBytes >> 10;
+ }
+
+ public void setMaximumQueueDepth(Long value)
+ {
+ _queue.setMaximumQueueDepth(value);
+ }
+
+ /**
+ * returns the size of messages(KB) in the queue.
+ */
+ public Long getQueueDepth() throws JMException
+ {
+ long queueBytesSize = _queue.getQueueDepth();
+
+ return queueBytesSize >> 10;
+ }
+
+ /**
+ * Checks if there is any notification to be send to the listeners
+ */
+ public void checkForNotification(AMQMessage msg) throws AMQException, JMException
+ {
+
+ 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 org.apache.qpid.server.queue.AMQQueue#deleteMessageFromTop
+ */
+ public void deleteMessageFromTop() throws JMException
+ {
+ try
+ {
+ _queue.deleteMessageFromTop(_storeContext);
+ }
+ catch (AMQException ex)
+ {
+ throw new MBeanException(ex, ex.toString());
+ }
+ }
+
+ /**
+ * @see org.apache.qpid.server.queue.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/AsyncDeliveryConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AsyncDeliveryConfig.java
new file mode 100644
index 0000000000..290fedcf7b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AsyncDeliveryConfig.java
@@ -0,0 +1,56 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import org.apache.qpid.configuration.Configured;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+public class AsyncDeliveryConfig
+{
+ private Executor _executor;
+
+ @Configured(path = "delivery.poolsize", defaultValue = "0")
+ public int poolSize;
+
+ public Executor getExecutor()
+ {
+ if (_executor == null)
+ {
+ if (poolSize > 0)
+ {
+ _executor = Executors.newFixedThreadPool(poolSize);
+ }
+ else
+ {
+ _executor = Executors.newCachedThreadPool();
+ }
+ }
+ return _executor;
+ }
+
+ public static Executor getAsyncDeliveryExecutor()
+ {
+ return ApplicationRegistry.getInstance().getConfiguredObject(AsyncDeliveryConfig.class).getExecutor();
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ConcurrentSelectorDeliveryManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ConcurrentSelectorDeliveryManager.java
new file mode 100644
index 0000000000..a61d41e33b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ConcurrentSelectorDeliveryManager.java
@@ -0,0 +1,1077 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.configuration.Configured;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.configuration.Configurator;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.util.ConcurrentLinkedMessageQueueAtomicSize;
+import org.apache.qpid.util.MessageQueue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+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.locks.ReentrantLock;
+
+
+/** Manages delivery of messages on behalf of a queue */
+public class ConcurrentSelectorDeliveryManager implements DeliveryManager
+{
+ private static final Logger _log = Logger.getLogger(ConcurrentSelectorDeliveryManager.class);
+
+ @Configured(path = "advanced.compressBufferOnQueue",
+ defaultValue = "false")
+ public boolean compressBufferOnQueue;
+ /** Holds any queued messages */
+ private final MessageQueue<QueueEntry> _messages = new ConcurrentLinkedMessageQueueAtomicSize<QueueEntry>();
+
+ /** Ensures that only one asynchronous task is running for this manager at any time. */
+ private final AtomicBoolean _processing = new AtomicBoolean();
+ /** The subscriptions on the queue to whom messages are delivered */
+ private final SubscriptionManager _subscriptions;
+
+ /**
+ * A reference to the queue we are delivering messages for. We need this to be able to pass the code that handles
+ * acknowledgements a handle on the queue.
+ */
+ private final AMQQueue _queue;
+
+ /**
+ * Flag used while moving messages from this queue to another. For moving messages the async delivery should also
+ * stop. This flat should be set to true to stop async delivery and set to false to enable async delivery again.
+ */
+ private AtomicBoolean _movingMessages = new AtomicBoolean();
+
+ /**
+ * Lock used to ensure that an channel that becomes unsuspended during the start of the queueing process is forced
+ * to wait till the first message is added to the queue. This will ensure that the _queue has messages to be
+ * delivered via the async thread. <p/> Lock is used to control access to hasQueuedMessages() and over the addition
+ * of messages to the queue.
+ */
+ private ReentrantLock _lock = new ReentrantLock();
+ private AtomicLong _totalMessageSize = new AtomicLong();
+ private AtomicInteger _extraMessages = new AtomicInteger();
+ private Set<Subscription> _hasContent = Collections.synchronizedSet(new HashSet<Subscription>());
+ private final Object _queueHeadLock = new Object();
+ private String _processingThreadName = "";
+
+
+ /** Used by any reaping thread to purge messages */
+ private StoreContext _reapingStoreContext = new StoreContext();
+
+ ConcurrentSelectorDeliveryManager(SubscriptionManager subscriptions, AMQQueue queue)
+ {
+
+ //Set values from configuration
+ Configurator.configure(this);
+
+ if (compressBufferOnQueue)
+ {
+ _log.warn("Compressing Buffers on queue.");
+ }
+
+ _subscriptions = subscriptions;
+ _queue = queue;
+ }
+
+
+ private boolean addMessageToQueue(QueueEntry entry, boolean deliverFirst)
+ {
+ AMQMessage msg = entry.getMessage();
+ // Shrink the ContentBodies to their actual size to save memory.
+ if (compressBufferOnQueue)
+ {
+ Iterator<ContentChunk> it = msg.getContentBodyIterator();
+ while (it.hasNext())
+ {
+ ContentChunk cb = it.next();
+ cb.reduceToFit();
+ }
+ }
+
+ if (deliverFirst)
+ {
+ synchronized (_queueHeadLock)
+ {
+ _messages.pushHead(entry);
+ }
+ }
+ else
+ {
+ _messages.offer(entry);
+ }
+
+ _totalMessageSize.addAndGet(msg.getSize());
+
+ return true;
+ }
+
+
+ public boolean hasQueuedMessages()
+ {
+ _lock.lock();
+ try
+ {
+ return !(_messages.isEmpty() && _hasContent.isEmpty());
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ public int getQueueMessageCount()
+ {
+ return getMessageCount();
+ }
+
+ /**
+ * This is an EXPENSIVE opperation to perform with a ConcurrentLinkedQueue as it must run the queue to determine
+ * size. The ConcurrentLinkedQueueAtomicSize uses an AtomicInteger to record the number of elements on the queue.
+ *
+ * @return int the number of messages in the delivery queue.
+ */
+ private int getMessageCount()
+ {
+ return _messages.size() + _extraMessages.get();
+ }
+
+
+ public long getTotalMessageSize()
+ {
+ return _totalMessageSize.get();
+ }
+
+ public long getOldestMessageArrival()
+ {
+ QueueEntry entry = _messages.peek();
+ return entry == null ? Long.MAX_VALUE : entry.getMessage().getArrivalTime();
+ }
+
+ public void subscriberHasPendingResend(boolean hasContent, Subscription subscription, QueueEntry entry)
+ {
+ _lock.lock();
+ try
+ {
+ if (hasContent)
+ {
+ _log.debug("Queue has adding subscriber content");
+ _hasContent.add(subscription);
+ _totalMessageSize.addAndGet(entry.getSize());
+ _extraMessages.addAndGet(1);
+ }
+ else
+ {
+ _log.debug("Queue has removing subscriber content");
+ if (entry == null)
+ {
+ _hasContent.remove(subscription);
+ }
+ else
+ {
+ _totalMessageSize.addAndGet(-entry.getSize());
+ _extraMessages.addAndGet(-1);
+ }
+ }
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ /**
+ * NOTE : This method should only be called when there are no active subscribers
+ */
+ public void removeExpired() throws AMQException
+ {
+ _lock.lock();
+
+
+ for(Iterator<QueueEntry> iter = _messages.iterator(); iter.hasNext();)
+ {
+ QueueEntry entry = iter.next();
+ if(entry.expired())
+ {
+ // fixme: Currently we have to update the total byte size here for the data in the queue
+ _totalMessageSize.addAndGet(-entry.getSize());
+ _queue.dequeue(_reapingStoreContext,entry);
+ iter.remove();
+ }
+ }
+
+
+ _lock.unlock();
+ }
+
+ /** @return the state of the async processor. */
+ public boolean isProcessingAsync()
+ {
+ return _processing.get();
+ }
+
+ /**
+ * Returns all the messages in the Queue
+ *
+ * @return List of messages
+ */
+ public List<QueueEntry> getMessages()
+ {
+ _lock.lock();
+ List<QueueEntry> list = new ArrayList<QueueEntry>();
+
+ for (QueueEntry entry : _messages)
+ {
+ list.add(entry);
+ }
+ _lock.unlock();
+
+ return list;
+ }
+
+ /**
+ * Returns messages within the range of given messageIds
+ *
+ * @param fromMessageId
+ * @param toMessageId
+ *
+ * @return
+ */
+ public List<QueueEntry> getMessages(long fromMessageId, long toMessageId)
+ {
+ if (fromMessageId <= 0 || toMessageId <= 0)
+ {
+ return null;
+ }
+
+ long maxMessageCount = toMessageId - fromMessageId + 1;
+
+ _lock.lock();
+
+ List<QueueEntry> foundMessagesList = new ArrayList<QueueEntry>();
+
+ for (QueueEntry entry : _messages)
+ {
+ long msgId = entry.getMessage().getMessageId();
+ if (msgId >= fromMessageId && msgId <= toMessageId)
+ {
+ foundMessagesList.add(entry);
+ }
+ // break if the no of messages are found
+ if (foundMessagesList.size() == maxMessageCount)
+ {
+ break;
+ }
+ }
+ _lock.unlock();
+
+ return foundMessagesList;
+ }
+
+ public void populatePreDeliveryQueue(Subscription subscription)
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Populating PreDeliveryQueue for Subscription(" + System.identityHashCode(subscription) + ")");
+ }
+
+ Iterator<QueueEntry> currentQueue = _messages.iterator();
+
+ while (currentQueue.hasNext())
+ {
+ QueueEntry entry = currentQueue.next();
+
+ if (!entry.getDeliveredToConsumer())
+ {
+ if (subscription.hasInterest(entry))
+ {
+ subscription.enqueueForPreDelivery(entry, false);
+ }
+ }
+ }
+ }
+
+ public boolean performGet(AMQProtocolSession protocolSession, AMQChannel channel, boolean acks) throws AMQException
+ {
+ QueueEntry entry = getNextMessage();
+ if (entry == null)
+ {
+ return false;
+ }
+ else
+ {
+
+ 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.
+ if (!acks)
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("No ack mode so dequeuing message immediately: " + entry.getMessage().getMessageId());
+ }
+ _queue.dequeue(channel.getStoreContext(), entry);
+ }
+ synchronized (channel)
+ {
+ long deliveryTag = channel.getNextDeliveryTag();
+
+ if (acks)
+ {
+ channel.addUnacknowledgedMessage(entry, deliveryTag, null);
+ }
+
+ protocolSession.getProtocolOutputConverter().writeGetOk(entry.getMessage(), channel.getChannelId(),
+ deliveryTag, _queue.getMessageCount());
+
+ }
+ _totalMessageSize.addAndGet(-entry.getSize());
+
+ if (!acks)
+ {
+ entry.getMessage().decrementReference(channel.getStoreContext());
+ }
+ }
+ finally
+ {
+ entry.setDeliveredToConsumer();
+ }
+ return true;
+
+ }
+ }
+
+ /**
+ * For feature of moving messages, this method is used. It sets the lock and sets the movingMessages flag, so that
+ * the asyn delivery is also stopped.
+ */
+ public void startMovingMessages()
+ {
+ _movingMessages.set(true);
+ }
+
+ /**
+ * Once moving messages to another queue is done or aborted, remove lock and unset the movingMessages flag, so that
+ * the async delivery can start again.
+ */
+ public void stopMovingMessages()
+ {
+ _movingMessages.set(false);
+ if (_lock.isHeldByCurrentThread())
+ {
+ _lock.unlock();
+ }
+ }
+
+ /**
+ * Messages will be removed from this queue and all preDeliveryQueues
+ *
+ * @param messageList
+ */
+ public void removeMovedMessages(List<QueueEntry> messageList)
+ {
+ // Remove from the
+ boolean hasSubscribers = _subscriptions.hasActiveSubscribers();
+ if (hasSubscribers)
+ {
+ for (Subscription sub : _subscriptions.getSubscriptions())
+ {
+ if (!sub.isSuspended() && sub.filtersMessages())
+ {
+ Queue<QueueEntry> preDeliveryQueue = sub.getPreDeliveryQueue();
+ for (QueueEntry entry : messageList)
+ {
+ preDeliveryQueue.remove(entry);
+ }
+ }
+ }
+ }
+
+ for (QueueEntry entry : messageList)
+ {
+ if (_messages.remove(entry))
+ {
+ _totalMessageSize.getAndAdd(-entry.getSize());
+ }
+ }
+ }
+
+ /**
+ * Now with implementation of predelivery queues, this method will mark the message on the top as taken.
+ *
+ * @param storeContext
+ *
+ * @throws AMQException
+ */
+ public void removeAMessageFromTop(StoreContext storeContext, AMQQueue queue) throws AMQException
+ {
+ _lock.lock();
+
+ QueueEntry entry = _messages.poll();
+
+ if (entry != null)
+ {
+ queue.dequeue(storeContext, entry);
+
+ _totalMessageSize.addAndGet(-entry.getSize());
+
+ //If this causes ref count to hit zero then data will be purged so message.getSize() will NPE.
+ entry.getMessage().decrementReference(storeContext);
+
+ }
+
+ _lock.unlock();
+ }
+
+ public long clearAllMessages(StoreContext storeContext) throws AMQException
+ {
+ long count = 0;
+ _lock.lock();
+
+ synchronized (_queueHeadLock)
+ {
+ QueueEntry entry = getNextMessage();
+ while (entry != null)
+ {
+ //and remove it
+ _messages.poll();
+
+ _queue.dequeue(storeContext, entry);
+
+ entry.getMessage().decrementReference(_reapingStoreContext);
+
+ entry = getNextMessage();
+ count++;
+ }
+ _totalMessageSize.set(0L);
+ }
+ _lock.unlock();
+ return count;
+ }
+
+ /**
+ * This can only be used to clear the _messages queue. Any subscriber resend queue will not be purged.
+ *
+ * @return the next message or null
+ *
+ * @throws org.apache.qpid.AMQException
+ */
+ private QueueEntry getNextMessage() throws AMQException
+ {
+ return getNextMessage(_messages, null, false);
+ }
+
+ private QueueEntry getNextMessage(Queue<QueueEntry> messages, Subscription sub, boolean purgeOnly) throws AMQException
+ {
+ QueueEntry entry = messages.peek();
+
+ //while (we have a message) && ((The subscriber is not a browser or message is taken ) or we are clearing) && (Check message is taken.)
+ while (purgeMessage(entry, sub, purgeOnly))
+ {
+ AMQMessage message = entry.getMessage();
+ // if we are purging then ensure we mark this message taken for the current subscriber
+ // the current subscriber may be null in the case of a get or a purge but this is ok.
+// boolean alreadyTaken = message.taken(_queue, sub);
+
+ //remove the already taken message or expired
+ QueueEntry removed = messages.poll();
+
+ assert removed == entry;
+
+ // if the message expired then the _totalMessageSize needs adjusting
+ if (message.expired(_queue) && !entry.getDeliveredToConsumer())
+ {
+ _totalMessageSize.addAndGet(-entry.getSize());
+
+ // Use the reapingStoreContext as any sub(if we have one) may be in a tx.
+ _queue.dequeue(_reapingStoreContext, entry);
+
+ message.decrementReference(_reapingStoreContext);
+
+ if (_log.isInfoEnabled())
+ {
+ _log.info(debugIdentity() + " Doing clean up of the main _message queue.");
+ }
+ }
+
+ //else the clean up is not required as the message has already been taken for this queue therefore
+ // it was the responsibility of the code that took the message to ensure the _totalMessageSize was updated.
+
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Removed taken message:" + message.debugIdentity());
+ }
+
+ // try the next message
+ entry = messages.peek();
+ }
+
+ return entry;
+ }
+
+ /**
+ * This method will return true if the message is to be purged from the queue.
+ *
+ *
+ * SIDE-EFFECT: The message will be taken by the Subscription(sub) for the current Queue(_queue)
+ *
+ * @param message
+ * @param sub
+ *
+ * @return
+ *
+ * @throws AMQException
+ */
+ private boolean purgeMessage(QueueEntry message, Subscription sub) throws AMQException
+ {
+ return purgeMessage(message, sub, false);
+ }
+
+ /**
+ * This method will return true if the message is to be purged from the queue.
+ * \
+ * SIDE-EFFECT: The msg will be taken by the Subscription(sub) for the current Queue(_queue) when purgeOnly is false
+ *
+ * @param message
+ * @param sub
+ * @param purgeOnly When set to false the message will be taken by the given Subscription.
+ *
+ * @return if the msg should be purged
+ *
+ * @throws AMQException
+ */
+ private boolean purgeMessage(QueueEntry message, Subscription sub, boolean purgeOnly) throws AMQException
+ {
+ //Original.. complicated while loop control
+// (message != null
+// && (
+// ((sub != null && !sub.isBrowser()) || message.isTaken(_queue))
+// || sub == null)
+// && message.taken(_queue, sub));
+
+ boolean purge = false;
+
+ // if the message is null then don't purge as we have no messagse.
+ if (message != null)
+ {
+ // Check that the message hasn't expired.
+ if (message.expired())
+ {
+ return true;
+ }
+
+ // if we have a subscriber perform message checks
+ if (sub != null)
+ {
+ // if we have a queue browser(we don't purge) so check mark the message as taken
+ purge = ((!sub.isBrowser() || message.isTaken()));
+ }
+ else
+ {
+ // if there is no subscription we are doing
+ // a get or purging so mark message as taken.
+ message.isTaken();
+ // and then ensure that it gets purged
+ purge = true;
+ }
+ }
+
+ if (purgeOnly)
+ {
+ // If we are simply purging the queue don't take the message
+ // just purge up to the next non-taken msg.
+ return purge && message.isTaken();
+ }
+ else
+ {
+ // if we are purging then ensure we mark this message taken for the current subscriber
+ // the current subscriber may be null in the case of a get or a purge but this is ok.
+ return purge && message.taken(sub);
+ }
+ }
+
+ public void sendNextMessage(Subscription sub, AMQQueue queue)
+ {
+
+ Queue<QueueEntry> messageQueue = sub.getNextQueue(_messages);
+
+ if (_log.isDebugEnabled())
+ {
+ _log.debug(debugIdentity() + "Async sendNextMessage for sub (" + System.identityHashCode(sub) +
+ ") from queue (" + System.identityHashCode(messageQueue) +
+ ") AMQQueue (" + System.identityHashCode(queue) + ")");
+ }
+
+ if (messageQueue == null)
+ {
+ // There is no queue with messages currently. This is ok... just means the queue has no msgs matching selector
+ if (_log.isInfoEnabled())
+ {
+ _log.info(debugIdentity() + sub + ": asked to send messages but has none on given queue:" + queue);
+ }
+ return;
+ }
+
+ QueueEntry entry = null;
+ QueueEntry removed = null;
+ try
+ {
+ synchronized (_queueHeadLock)
+ {
+ entry = getNextMessage(messageQueue, sub, false);
+
+ // message will be null if we have no messages in the messageQueue.
+ if (entry == null)
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug(debugIdentity() + "No messages for Subscriber(" + System.identityHashCode(sub) + ") from queue; (" + System.identityHashCode(messageQueue) + ")");
+ }
+ return;
+ }
+ if (_log.isDebugEnabled())
+ {
+ _log.debug(debugIdentity() + "Async Delivery Message :" + entry + "(" + System.identityHashCode(entry) +
+ ") by :" + System.identityHashCode(this) +
+ ") to :" + System.identityHashCode(sub));
+ }
+
+
+ if (messageQueue == _messages)
+ {
+ _totalMessageSize.addAndGet(-entry.getSize());
+ }
+
+ sub.send(entry, _queue);
+
+ //remove sent message from our queue.
+ removed = messageQueue.poll();
+ //If we don't remove the message from _messages
+ // Otherwise the Async send will never end
+ }
+
+ if (removed != entry)
+ {
+ _log.error("Just send message:" + entry.getMessage().debugIdentity() + " BUT removed this from queue:" + removed);
+ }
+
+ if (_log.isDebugEnabled())
+ {
+ _log.debug(debugIdentity() + "Async Delivered Message r:" + removed.getMessage().debugIdentity() + "d:" + entry +
+ ") by :" + System.identityHashCode(this) +
+ ") to :" + System.identityHashCode(sub));
+ }
+
+
+ if (messageQueue == sub.getResendQueue())
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug(debugIdentity() + "All messages sent from resendQueue for " + sub);
+ }
+ if (messageQueue.isEmpty())
+ {
+ subscriberHasPendingResend(false, sub, null);
+ //better to use the above method as this keeps all the tracking in one location.
+ // _hasContent.remove(sub);
+ }
+
+ _extraMessages.decrementAndGet();
+ }
+ else if (messageQueue == sub.getPreDeliveryQueue() && !sub.isBrowser())
+ {
+ if (_log.isInfoEnabled())
+ {
+ //fixme - we should do the clean up as the message remains on the _message queue
+ // this is resulting in the next consumer receiving the message and then attempting to purge it
+ //
+ cleanMainQueue(sub);
+ }
+ }
+
+ }
+ catch (AMQException e)
+ {
+ if (entry != null)
+ {
+ entry.release();
+ }
+ else
+ {
+ _log.error(debugIdentity() + "Unable to release message as it is null. " + e, e);
+ }
+ _log.error(debugIdentity() + "Unable to deliver message as dequeue failed: " + e, e);
+ }
+ }
+
+ private void cleanMainQueue(Subscription sub)
+ {
+ try
+ {
+ getNextMessage(_messages, sub, true);
+ }
+ catch (AMQException e)
+ {
+ _log.warn("Problem during main queue purge:" + e.getMessage());
+ }
+ }
+
+ /**
+ * enqueues the messages in the list on the queue and all required predelivery queues
+ *
+ * @param storeContext
+ * @param movedMessageList
+ */
+ public void enqueueMovedMessages(StoreContext storeContext, List<QueueEntry> movedMessageList)
+ {
+ _lock.lock();
+ for (QueueEntry entry : movedMessageList)
+ {
+ addMessageToQueue(entry, false);
+ }
+
+ // enqueue on the pre delivery queues
+ for (Subscription sub : _subscriptions.getSubscriptions())
+ {
+ for (QueueEntry entry : movedMessageList)
+ {
+ // Only give the message to those that want them.
+ if (sub.hasInterest(entry))
+ {
+ sub.enqueueForPreDelivery(entry, true);
+ }
+ }
+ }
+ _lock.unlock();
+ }
+
+ /**
+ * Only one thread should ever execute this method concurrently, but it can do so while other threads invoke
+ * deliver().
+ */
+ private void processQueue()
+ {
+ //record thread name
+ if (_log.isDebugEnabled())
+ {
+ _processingThreadName = Thread.currentThread().getName();
+ }
+
+ if (_log.isDebugEnabled())
+ {
+ _log.debug(debugIdentity() + "Running process Queue." + currentStatus());
+ }
+
+ // Continue to process delivery while we haveSubscribers and messages
+ boolean hasSubscribers = _subscriptions.hasActiveSubscribers();
+
+ while (hasSubscribers && hasQueuedMessages() && !_movingMessages.get())
+ {
+ hasSubscribers = false;
+
+ for (Subscription sub : _subscriptions.getSubscriptions())
+ {
+ synchronized (sub.getSendLock())
+ {
+ if (!sub.isSuspended())
+ {
+ sendNextMessage(sub, _queue);
+
+ hasSubscribers = true;
+ }
+ }
+ }
+ }
+
+ if (_log.isDebugEnabled())
+ {
+ _log.debug(debugIdentity() + "Done process Queue." + currentStatus());
+ }
+
+ }
+
+ public void deliver(StoreContext context, AMQShortString name, QueueEntry entry, boolean deliverFirst) throws AMQException
+ {
+
+ final boolean debugEnabled = _log.isDebugEnabled();
+ if (debugEnabled)
+ {
+ _log.debug(debugIdentity() + "deliver :first(" + deliverFirst + ") :" + entry);
+ }
+
+ //Check if we have someone to deliver the message to.
+ _lock.lock();
+ try
+ {
+ Subscription s = _subscriptions.nextSubscriber(entry);
+
+ if (s == null || (!s.filtersMessages() && hasQueuedMessages())) //no-one can take the message right now or we're queueing
+ {
+ if (debugEnabled)
+ {
+ _log.debug(debugIdentity() + "Testing Message(" + entry + ") for Queued Delivery:" + currentStatus());
+ }
+ if (!entry.getMessage().getMessagePublishInfo().isImmediate())
+ {
+ addMessageToQueue(entry, deliverFirst);
+
+ //release lock now message is on queue.
+ _lock.unlock();
+
+ //Pre Deliver to all subscriptions
+ if (debugEnabled)
+ {
+ _log.debug(debugIdentity() + "We have " + _subscriptions.getSubscriptions().size() +
+ " subscribers to give the message to:" + currentStatus());
+ }
+ for (Subscription sub : _subscriptions.getSubscriptions())
+ {
+
+ // stop if the message gets delivered whilst PreDelivering if we have a shared queue.
+ if (_queue.isShared() && entry.getDeliveredToConsumer())
+ {
+ if (debugEnabled)
+ {
+ _log.debug(debugIdentity() + "Stopping PreDelivery as message(" + System.identityHashCode(entry) +
+ ") is already delivered.");
+ }
+ continue;
+ }
+
+ // Only give the message to those that want them.
+ if (sub.hasInterest(entry))
+ {
+ if (debugEnabled)
+ {
+ _log.debug(debugIdentity() + "Queuing message(" + System.identityHashCode(entry) +
+ ") for PreDelivery for subscriber(" + System.identityHashCode(sub) + ")");
+ }
+ sub.enqueueForPreDelivery(entry, deliverFirst);
+ }
+ }
+
+ //if we have a non-filtering subscriber but queued messages && we're not Async && we have other Active subs then something is wrong!
+ if ((s != null && hasQueuedMessages()) && !isProcessingAsync() && _subscriptions.hasActiveSubscribers())
+ {
+ _queue.deliverAsync();
+ }
+
+ }
+ }
+ else
+ {
+
+ if (s.filtersMessages())
+ {
+ if (s.getPreDeliveryQueue().size() > 0)
+ {
+ _log.error("Direct delivery from PDQ with queued msgs:" + s.getPreDeliveryQueue().size());
+ }
+ }
+ else if (_messages.size() > 0)
+ {
+ _log.error("Direct delivery from MainQueue queued msgs:" + _messages.size());
+ }
+
+ //release lock now
+ _lock.unlock();
+ synchronized (s.getSendLock())
+ {
+ if (!s.isSuspended())
+ {
+ if (debugEnabled)
+ {
+ _log.debug(debugIdentity() + "Delivering Message:" + entry.getMessage().debugIdentity() + " to(" +
+ System.identityHashCode(s) + ") :" + s);
+ }
+
+ if (entry.taken(s))
+ {
+ //Message has been delivered so don't redeliver.
+ // This can currently occur because of the recursive call below
+ // During unit tests the send can occur
+ // client then rejects
+ // this reject then releases the message by the time the
+ // if(!msg.isTaken()) call is made below
+ // the message has been released so that thread loops to send the message again
+ // of course by the time it gets back to here. the thread that released the
+ // message is now ready to send it. Here is a sample trace for reference
+//1192627162613:Thread[pool-917-thread-4,5,main]:CSDM:delivery:(true)message:Message[(HC:5529738 ID:145 Ref:1)]: 145; ref count: 1; taken for queues: {Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=false} by Subs:{Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=null}:sub:[channel=Channel: id 1, transaction mode: true, prefetch marks: 2500/5000, consumerTag=41, session=anonymous(5050419), resendQueue=false]
+//1192627162613:Thread[pool-917-thread-4,5,main]:Msg:taken:Q:Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326:sub:[channel=Channel: id 1, transaction mode: true, prefetch marks: 2500/5000, consumerTag=41, session=anonymous(5050419), resendQueue=false]:this:Message[(HC:5529738 ID:145 Ref:1)]: 145; ref count: 1; taken for queues: {Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=false} by Subs:{Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=null}
+//1192627162613:Thread[pool-917-thread-4,5,main]:28398657 Sent :dt:214 msg:(HC:5529738 ID:145 Ref:1)
+//1192627162613:Thread[pool-917-thread-2,5,main]:Reject message by:[channel=Channel: id 1, transaction mode: true, prefetch marks: 2500/5000, consumerTag=41, session=anonymous(5050419), resendQueue=false]
+//1192627162613:Thread[pool-917-thread-2,5,main]:Releasing Message:(HC:5529738 ID:145 Ref:1)
+//1192627162613:Thread[pool-917-thread-2,5,main]:Msg:Release:Q:Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326:This:Message[(HC:5529738 ID:145 Ref:1)]: 145; ref count: 1; taken for queues: {Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=false} by Subs:{Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=[channel=Channel: id 1, transaction mode: true, prefetch marks: 2500/5000, consumerTag=41, session=anonymous(5050419), resendQueue=false]}
+//1192627162613:Thread[pool-917-thread-2,5,main]:CSDM:delivery:(true)message:Message[(HC:5529738 ID:145 Ref:1)]: 145; ref count: 1; taken for queues: {Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=false} by Subs:{Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=null}:sub:[channel=Channel: id 1, transaction mode: true, prefetch marks: 2500/5000, consumerTag=33, session=anonymous(26960027), resendQueue=false]
+//1192627162629:Thread[pool-917-thread-4,5,main]:CSDM:suspended: Message((HC:5529738 ID:145 Ref:1)) has not been taken so recursing!: Subscriber:28398657
+//1192627162629:Thread[pool-917-thread-4,5,main]:CSDM:delivery:(true)message:Message[(HC:5529738 ID:145 Ref:1)]: 145; ref count: 1; taken for queues: {Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=false} by Subs:{Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=null}:sub:[channel=Channel: id 1, transaction mode: true, prefetch marks: 2500/5000, consumerTag=33, session=anonymous(26960027), resendQueue=false]
+//1192627162629:Thread[pool-917-thread-2,5,main]:Msg:taken:Q:Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326:sub:[channel=Channel: id 1, transaction mode: true, prefetch marks: 2500/5000, consumerTag=33, session=anonymous(26960027), resendQueue=false]:this:Message[(HC:5529738 ID:145 Ref:1)]: 145; ref count: 1; taken for queues: {Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=false} by Subs:{Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=null}
+//1192627162629:Thread[pool-917-thread-2,5,main]:25386607 Sent :dt:172 msg:(HC:5529738 ID:145 Ref:1)
+//1192627162629:Thread[pool-917-thread-4,5,main]:Msg:taken:Q:Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326:sub:[channel=Channel: id 1, transaction mode: true, prefetch marks: 2500/5000, consumerTag=33, session=anonymous(26960027), resendQueue=false]:this:Message[(HC:5529738 ID:145 Ref:1)]: 145; ref count: 1; taken for queues: {Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=true} by Subs:{Queue(queue-596fb10e-2968-4e51-a751-1e6643bf9dd6)@16017326=[channel=Channel: id 1, transaction mode: true, prefetch marks: 2500/5000, consumerTag=33, session=anonymous(26960027), resendQueue=false]}
+ // Note: In the last request to take the message from thread 4,5 the message has been
+ // taken by the previous call done by thread 2,5
+
+
+ return;
+ }
+ //Deliver the message
+ s.send(entry, _queue);
+ }
+ else
+ {
+ if (debugEnabled)
+ {
+ _log.debug(debugIdentity() + " Subscription(" + System.identityHashCode(s) + ") became " +
+ "suspended between nextSubscriber and send for message:" + entry.getMessage().debugIdentity());
+ }
+ }
+ }
+
+ //
+ // Why do we do this? What was the reasoning? We should have a better approach
+ // than recursion and rejecting if someone else sends it before we do.
+ //
+ if (!entry.isTaken())
+ {
+ if (debugEnabled)
+ {
+ _log.debug(debugIdentity() + " Message(" + entry.getMessage().debugIdentity() + ") has not been taken so recursing!:" +
+ " Subscriber:" + System.identityHashCode(s));
+ }
+
+ deliver(context, name, entry, deliverFirst);
+ }
+ else
+ {
+ if (debugEnabled)
+ {
+ _log.debug(debugIdentity() + " Message(" + entry.toString() +
+ ") has been taken so disregarding deliver request to Subscriber:" +
+ System.identityHashCode(s));
+ }
+ }
+ }
+
+ }
+ finally
+ {
+ //ensure lock is released
+ if (_lock.isHeldByCurrentThread())
+ {
+ _lock.unlock();
+ }
+ }
+ }
+
+ private final String id = "(" + String.valueOf(System.identityHashCode(this)) + ")";
+
+ private String debugIdentity()
+ {
+ return id;
+ }
+
+ final Runner _asyncDelivery = new Runner();
+
+ private class Runner implements Runnable
+ {
+ public void run()
+ {
+ String startName = Thread.currentThread().getName();
+ Thread.currentThread().setName("CSDM-AsyncDelivery:" + startName);
+ boolean running = true;
+ while (running && !_movingMessages.get())
+ {
+ processQueue();
+
+ //Check that messages have not been added since we did our last peek();
+ // Synchronize with the thread that adds to the queue.
+ // If the queue is still empty then we can exit
+ synchronized (_asyncDelivery)
+ {
+ if (!(hasQueuedMessages() && _subscriptions.hasActiveSubscribers()))
+ {
+ running = false;
+ _processing.set(false);
+ }
+ }
+ }
+ Thread.currentThread().setName(startName);
+ }
+ }
+
+ public void processAsync(Executor executor)
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug(debugIdentity() + "Processing Async." + currentStatus());
+ }
+
+ synchronized (_asyncDelivery)
+ {
+ if (hasQueuedMessages() && _subscriptions.hasActiveSubscribers())
+ {
+ //are we already running? if so, don't re-run
+ if (_processing.compareAndSet(false, true))
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug(debugIdentity() + "Executing Async process.");
+ }
+ executor.execute(_asyncDelivery);
+ }
+ }
+ }
+ }
+
+ private String currentStatus()
+ {
+ return " Queued:" + (_messages.isEmpty() ? "Empty " : "Contains(H:M)") +
+ "(" + ((ConcurrentLinkedMessageQueueAtomicSize) _messages).headSize() +
+ ":" + (_messages.size() - ((ConcurrentLinkedMessageQueueAtomicSize) _messages).headSize()) + ") " +
+ " Extra: " + (_hasContent.isEmpty() ? "Empty " : "Contains") +
+ "(" + _hasContent.size() + ":" + _extraMessages.get() + ") " +
+ " Active:" + _subscriptions.hasActiveSubscribers() +
+ " Processing:" + (_processing.get() ? " true : Processing Thread: " + _processingThreadName : " false");
+ }
+
+}
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/DeliveryManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java
new file mode 100644
index 0000000000..1568f58e2e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.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.queue;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.store.StoreContext;
+
+interface DeliveryManager
+{
+ /**
+ * Determines whether there are queued messages. Sets _queueing to false if there are no queued messages. This needs
+ * to be atomic.
+ *
+ * @return true if there are queued messages
+ */
+ boolean hasQueuedMessages();
+
+ /**
+ * This method should not be used to determin if there are messages in the queue.
+ *
+ * @return int The number of messages in the queue
+ *
+ * @use hasQueuedMessages() for all controls relating to having messages on the queue.
+ */
+ int getQueueMessageCount();
+
+ /**
+ * Requests that the delivery manager start processing the queue asynchronously if there is work that can be done
+ * (i.e. there are messages queued up and subscribers that can receive them. <p/> This should be called when
+ * subscribers are added, but only after the consume-ok message has been returned as message delivery may start
+ * immediately. It should also be called after unsuspending a client. <p/>
+ *
+ * @param executor the executor on which the delivery should take place
+ */
+ void processAsync(Executor executor);
+
+ /**
+ * Handles message delivery. The delivery manager is always in one of two modes; it is either queueing messages for
+ * asynchronous delivery or delivering directly.
+ *
+ * @param storeContext
+ * @param name the name of the entity on whose behalf we are delivering the message
+ * @param entry the message to deliver
+ * @param deliverFirst
+ *
+ * @throws org.apache.qpid.server.queue.FailedDequeueException
+ * if the message could not be dequeued
+ */
+ void deliver(StoreContext storeContext, AMQShortString name, QueueEntry entry, boolean deliverFirst) throws FailedDequeueException, AMQException;
+
+ void removeAMessageFromTop(StoreContext storeContext, AMQQueue queue) throws AMQException;
+
+ long clearAllMessages(StoreContext storeContext) throws AMQException;
+
+ void startMovingMessages();
+
+ void enqueueMovedMessages(StoreContext context, List<QueueEntry> messageList);
+
+ void stopMovingMessages();
+
+ void removeMovedMessages(List<QueueEntry> messageListToRemove);
+
+ List<QueueEntry> getMessages();
+
+ List<QueueEntry> getMessages(long fromMessageId, long toMessageId);
+
+ void populatePreDeliveryQueue(Subscription subscription);
+
+ boolean performGet(AMQProtocolSession session, AMQChannel channel, boolean acks) throws AMQException;
+
+ long getTotalMessageSize();
+
+ long getOldestMessageArrival();
+
+ void subscriberHasPendingResend(boolean hasContent, Subscription subscription, QueueEntry msg);
+
+ void removeExpired() throws AMQException;
+}
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..e6377b33da
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java
@@ -0,0 +1,135 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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 static final FieldTable EMPTY_ARGUMENTS = new FieldTable();
+
+ static class ExchangeBinding
+ {
+ private final Exchange _exchange;
+ private final AMQShortString _routingKey;
+ private final FieldTable _arguments;
+
+ 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 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);
+ }
+ }
+
+ 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 void remove(AMQShortString routingKey, FieldTable arguments, Exchange exchange)
+ {
+ _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/InMemoryMessageHandle.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java
new file mode 100644
index 0000000000..630186991b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.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.queue;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentBody;
+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 = new LinkedList<ContentChunk>();
+
+ private boolean _redelivered;
+
+ private long _arrivalTime;
+
+ public InMemoryMessageHandle()
+ {
+ }
+
+ public ContentHeaderBody getContentHeaderBody(StoreContext context, Long messageId) throws AMQException
+ {
+ return _contentHeaderBody;
+ }
+
+ public int getBodyCount(StoreContext context, Long messageId)
+ {
+ return _contentBodies.size();
+ }
+
+ public long getBodySize(StoreContext context, Long messageId) throws AMQException
+ {
+ return getContentHeaderBody(context, messageId).bodySize;
+ }
+
+ public ContentChunk getContentChunk(StoreContext context, Long messageId, int index) throws AMQException, IllegalArgumentException
+ {
+ if (index > _contentBodies.size() - 1)
+ {
+ throw new IllegalArgumentException("Index " + index + " out of valid range 0 to " +
+ (_contentBodies.size() - 1));
+ }
+ return _contentBodies.get(index);
+ }
+
+ public void addContentBodyFrame(StoreContext storeContext, Long messageId, ContentChunk contentBody, boolean isLastContentBody)
+ throws AMQException
+ {
+ _contentBodies.add(contentBody);
+ }
+
+ public MessagePublishInfo getMessagePublishInfo(StoreContext context, Long messageId) throws AMQException
+ {
+ return _messagePublishInfo;
+ }
+
+ public boolean isRedelivered()
+ {
+ return _redelivered;
+ }
+
+
+ public void setRedelivered(boolean redelivered)
+ {
+ _redelivered = redelivered;
+ }
+
+ public boolean isPersistent(StoreContext context, Long messageId) throws AMQException
+ {
+ //todo remove literal values to a constant file such as AMQConstants in common
+ ContentHeaderBody chb = getContentHeaderBody(context, messageId);
+ return chb.properties instanceof BasicContentHeaderProperties &&
+ ((BasicContentHeaderProperties) chb.properties).getDeliveryMode() == 2;
+ }
+
+ /**
+ * This is called when all the content has been received.
+ * @param messagePublishInfo
+ * @param contentHeaderBody
+ * @throws AMQException
+ */
+ public void setPublishAndContentHeaderBody(StoreContext storeContext, Long messageId, MessagePublishInfo messagePublishInfo,
+ ContentHeaderBody contentHeaderBody)
+ throws AMQException
+ {
+ _messagePublishInfo = messagePublishInfo;
+ _contentHeaderBody = contentHeaderBody;
+ _arrivalTime = System.currentTimeMillis();
+ }
+
+ public void removeMessage(StoreContext storeContext, Long messageId) throws AMQException
+ {
+ // NO OP
+ }
+
+ public void enqueue(StoreContext storeContext, Long messageId, AMQQueue queue) throws AMQException
+ {
+ // NO OP
+ }
+
+ public void dequeue(StoreContext storeContext, Long messageId, AMQQueue queue) throws AMQException
+ {
+ // NO OP
+ }
+
+ public long getArrivalTime()
+ {
+ return _arrivalTime;
+ }
+
+}
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..061ab56024
--- /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";
+
+ /**
+ * 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="Size of messages(KB) 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)
+ * @return the maximum age
+ * @throws IOException
+ */
+ Long getMaximumMessageAge() throws IOException;
+
+ /**
+ * Sets the maximum age of a message
+ * @param age maximum age of message.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="MaximumMessageAge", description="Threshold high value for message age on thr broker")
+ void setMaximumMessageAge(Long age) throws IOException;
+
+ /**
+ * Returns the maximum size of a message (in kbytes) allowed to be accepted by the
+ * ManagedQueue. This is useful in setting notifications or taking
+ * appropriate action, if the size of the message received is more than
+ * the allowed size.
+ * @return the maximum size of a message allowed to be aceepted by the
+ * ManagedQueue.
+ * @throws IOException
+ */
+ Long getMaximumMessageSize() throws IOException;
+
+ /**
+ * Sets the maximum size of the message (in kbytes) that is allowed to be
+ * accepted by the Queue.
+ * @param size maximum size of message.
+ * @throws IOException
+ */
+ @MBeanAttribute(name="MaximumMessageSize", description="Threshold high value(KB) 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(KB) 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..94ab935115
--- /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(store);
+ }
+ else
+ {
+ return new InMemoryMessageHandle();
+ }
+ }
+}
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/QueueEntry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.java
new file mode 100644
index 0000000000..8553db3e09
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.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.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.log4j.Logger;
+
+import java.util.Set;
+import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicReference;
+
+
+public class QueueEntry
+{
+
+ /**
+ * Used for debugging purposes.
+ */
+ private static final Logger _log = Logger.getLogger(QueueEntry.class);
+
+ private final AMQQueue _queue;
+ private final AMQMessage _message;
+
+ private Set<Subscription> _rejectedBy = null;
+
+ private AtomicReference<Object> _owner = new AtomicReference<Object>();
+
+
+ public QueueEntry(AMQQueue queue, AMQMessage message)
+ {
+ _queue = queue;
+ _message = message;
+ }
+
+
+ public AMQQueue getQueue()
+ {
+ return _queue;
+ }
+
+ 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(_queue);
+ }
+
+ public boolean isTaken()
+ {
+ return _owner.get() != null;
+ }
+
+ public boolean taken(Subscription sub)
+ {
+ return !(_owner.compareAndSet(null, sub == null ? this : sub));
+ }
+
+ public void setDeliveredToConsumer()
+ {
+ getMessage().setDeliveredToConsumer();
+ }
+
+ public void release()
+ {
+ _owner.set(null);
+ }
+
+ public String debugIdentity()
+ {
+ return getMessage().debugIdentity();
+ }
+
+ public void process(StoreContext storeContext, boolean deliverFirst) throws AMQException
+ {
+ _queue.process(storeContext, this, deliverFirst);
+ }
+
+ public void checkDeliveredToConsumer() throws NoConsumersException
+ {
+ _message.checkDeliveredToConsumer();
+ }
+
+ public void setRedelivered(boolean b)
+ {
+ getMessage().setRedelivered(b);
+ }
+
+ public Subscription getDeliveredSubscription()
+ {
+ synchronized (this)
+ {
+ Object owner = _owner.get();
+ if (owner instanceof Subscription)
+ {
+ return (Subscription) owner;
+ }
+ 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)
+ {
+ boolean rejected = _rejectedBy != null;
+
+ if (rejected) // We have subscriptions that rejected this message
+ {
+ return _rejectedBy.contains(subscription);
+ }
+ else // This messasge hasn't been rejected yet.
+ {
+ return rejected;
+ }
+ }
+
+
+}
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/Subscription.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Subscription.java
new file mode 100644
index 0000000000..a706098b71
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Subscription.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.queue;
+
+import java.util.Queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.AMQChannel;
+
+public interface Subscription
+{
+ void send(QueueEntry msg, AMQQueue queue) throws AMQException;
+
+ boolean isSuspended();
+
+ void queueDeleted(AMQQueue queue) throws AMQException;
+
+ boolean filtersMessages();
+
+ boolean hasInterest(QueueEntry msg);
+
+ Queue<QueueEntry> getPreDeliveryQueue();
+
+ Queue<QueueEntry> getResendQueue();
+
+ Queue<QueueEntry> getNextQueue(Queue<QueueEntry> messages);
+
+ void enqueueForPreDelivery(QueueEntry msg, boolean deliverFirst);
+
+ boolean isAutoClose();
+
+ void close();
+
+ boolean isClosed();
+
+ boolean isBrowser();
+
+ boolean wouldSuspend(QueueEntry msg);
+
+ void addToResendQueue(QueueEntry msg);
+
+ Object getSendLock();
+
+ AMQChannel getChannel();
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.java
new file mode 100644
index 0000000000..917f7c4e97
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionFactory.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.framing.FieldTable;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+
+/**
+ * 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, AMQQueue queue) throws AMQException;
+
+
+ Subscription createSubscription(int channel, AMQProtocolSession protocolSession, AMQShortString consumerTag)
+ throws AMQException;
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java
new file mode 100644
index 0000000000..6e68b5637e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java
@@ -0,0 +1,669 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.Queue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+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.output.ProtocolOutputConverter;
+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;
+import org.apache.qpid.util.ConcurrentLinkedQueueAtomicSize;
+import org.apache.qpid.util.MessageQueue;
+import org.apache.qpid.util.ConcurrentLinkedMessageQueueAtomicSize;
+
+/**
+ * Encapsulation of a supscription to a queue. <p/> Ties together the protocol session of a subscriber, the consumer tag
+ * that was given out by the broker and the channel id. <p/>
+ */
+public class SubscriptionImpl implements Subscription
+{
+
+ private static final Logger _suspensionlogger = Logger.getLogger("Suspension");
+ private static final Logger _logger = Logger.getLogger(SubscriptionImpl.class);
+
+ public final AMQChannel channel;
+
+ public final AMQProtocolSession protocolSession;
+
+ public final AMQShortString consumerTag;
+
+ private final Object _sessionKey;
+
+ private MessageQueue<QueueEntry> _messages;
+
+ private Queue<QueueEntry> _resendQueue;
+
+ private final boolean _noLocal;
+
+ /** True if messages need to be acknowledged */
+ private final boolean _acks;
+ private FilterManager _filters;
+ private final boolean _isBrowser;
+ private final Boolean _autoClose;
+ private boolean _sentClose = false;
+
+ private static final String CLIENT_PROPERTIES_INSTANCE = ClientProperties.instance.toString();
+
+ private AMQQueue _queue;
+ private final AtomicBoolean _sendLock = new AtomicBoolean(false);
+
+
+ public static class Factory implements SubscriptionFactory
+ {
+ public Subscription createSubscription(int channel, AMQProtocolSession protocolSession,
+ AMQShortString consumerTag, boolean acks, FieldTable filters,
+ boolean noLocal, AMQQueue queue) throws AMQException
+ {
+ return new SubscriptionImpl(channel, protocolSession, consumerTag, acks, filters, noLocal, queue);
+ }
+
+ public SubscriptionImpl createSubscription(int channel, AMQProtocolSession protocolSession, AMQShortString consumerTag)
+ throws AMQException
+ {
+ return new SubscriptionImpl(channel, protocolSession, consumerTag, false, null, false, null);
+ }
+ }
+
+ public SubscriptionImpl(int channelId, AMQProtocolSession protocolSession,
+ AMQShortString consumerTag, boolean acks)
+ throws AMQException
+ {
+ this(channelId, protocolSession, consumerTag, acks, null, false, null);
+ }
+
+ public SubscriptionImpl(int channelId, AMQProtocolSession protocolSession,
+ AMQShortString consumerTag, boolean acks, FieldTable filters,
+ boolean noLocal, AMQQueue queue)
+ throws AMQException
+ {
+ AMQChannel channel = protocolSession.getChannel(channelId);
+ if (channel == null)
+ {
+ throw new AMQException(AMQConstant.NOT_FOUND, "channel :" + channelId + " not found in protocol session");
+ }
+
+ this.channel = channel;
+ this.protocolSession = protocolSession;
+ this.consumerTag = consumerTag;
+ _sessionKey = protocolSession.getKey();
+ _acks = acks;
+ _noLocal = noLocal;
+ _queue = queue;
+
+ _filters = FilterManagerFactory.createManager(filters);
+
+
+ if (_filters != null)
+ {
+ Object isBrowser = filters.get(AMQPFilterTypes.NO_CONSUME.getValue());
+ if (isBrowser != null)
+ {
+ _isBrowser = (Boolean) isBrowser;
+ }
+ else
+ {
+ _isBrowser = false;
+ }
+ }
+ else
+ {
+ _isBrowser = false;
+ }
+
+
+ if (_filters != null)
+ {
+ Object autoClose = filters.get(AMQPFilterTypes.AUTO_CLOSE.getValue());
+ if (autoClose != null)
+ {
+ _autoClose = (Boolean) autoClose;
+ }
+ else
+ {
+ _autoClose = false;
+ }
+ }
+ else
+ {
+ _autoClose = false;
+ }
+
+
+ if (filtersMessages())
+ {
+ _messages = new ConcurrentLinkedMessageQueueAtomicSize<QueueEntry>();
+ }
+ else
+ {
+ // Reference the DeliveryManager
+ _messages = null;
+ }
+ }
+
+
+ public SubscriptionImpl(int channel, AMQProtocolSession protocolSession,
+ AMQShortString consumerTag)
+ throws AMQException
+ {
+ this(channel, protocolSession, consumerTag, false);
+ }
+
+ public boolean equals(Object o)
+ {
+ return (o instanceof SubscriptionImpl) && equals((SubscriptionImpl) o);
+ }
+
+ /**
+ * Equality holds if the session matches and the channel and consumer tag are the same.
+ *
+ * @param psc The subscriptionImpl to compare
+ *
+ * @return equality
+ */
+ private boolean equals(SubscriptionImpl psc)
+ {
+ return _sessionKey.equals(psc._sessionKey)
+ && psc.channel == channel
+ && psc.consumerTag.equals(consumerTag);
+ }
+
+ public int hashCode()
+ {
+ return _sessionKey.hashCode();
+ }
+
+ public String toString()
+ {
+ String subscriber = "[channel=" + channel +
+ ", consumerTag=" + consumerTag +
+ ", session=" + protocolSession.getKey() +
+ ", resendQueue=" + (_resendQueue != null);
+
+ if (_resendQueue != null)
+ {
+ subscriber += ", resendSize=" + _resendQueue.size();
+ }
+
+
+ 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
+ * @param queue the Queue it has been sent from
+ *
+ * @throws AMQException
+ */
+ public void send(QueueEntry msg, AMQQueue queue) throws AMQException
+ {
+ if (msg != null)
+ {
+ if (_isBrowser)
+ {
+ sendToBrowser(msg, queue);
+ }
+ else
+ {
+ sendToConsumer(channel.getStoreContext(), msg, queue);
+ }
+ }
+ else
+ {
+ _logger.error("Attempt to send Null message", new NullPointerException());
+ }
+ }
+
+ private void sendToBrowser(QueueEntry msg, AMQQueue queue) 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 (channel)
+ {
+ long deliveryTag = channel.getNextDeliveryTag();
+
+ // We don't need to add the message to the unacknowledgedMap as we don't need to know if the client
+ // received the message. If it is lost in transit that is not important.
+// if (_acks)
+// {
+// channel.addUnacknowledgedBrowsedMessage(msg, deliveryTag, consumerTag, queue);
+// }
+
+ if (_sendLock.get())
+ {
+ _logger.error("Sending " + msg + " when subscriber(" + this + ") is closed!");
+ }
+
+ protocolSession.getProtocolOutputConverter().writeDeliver(msg.getMessage(), channel.getChannelId(), deliveryTag, consumerTag);
+ }
+ }
+
+ private void sendToConsumer(StoreContext storeContext, QueueEntry entry, AMQQueue queue)
+ 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.
+ if (!_acks)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("No ack mode so dequeuing message immediately: " + entry.getMessage().getMessageId());
+ }
+ queue.dequeue(storeContext, entry);
+ }
+
+/*
+ if (_sendLock.get())
+ {
+ _logger.error("Sending " + entry + " when subscriber(" + this + ") is closed!");
+ }
+*/
+
+ synchronized (channel)
+ {
+ long deliveryTag = channel.getNextDeliveryTag();
+
+
+ if (_acks)
+ {
+ channel.addUnacknowledgedMessage(entry, deliveryTag, consumerTag);
+ }
+
+ protocolSession.getProtocolOutputConverter().writeDeliver(entry.getMessage(), channel.getChannelId(), deliveryTag, consumerTag);
+
+
+ }
+ if (!_acks)
+ {
+ entry.getMessage().decrementReference(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.setDeliveredToConsumer();
+ }
+ }
+
+ public boolean isSuspended()
+ {
+// if (_suspensionlogger.isInfoEnabled())
+// {
+// if (channel.isSuspended())
+// {
+// _suspensionlogger.debug("Subscription(" + debugIdentity() + ") channel's is susupended");
+// }
+// if (_sendLock.get())
+// {
+// _suspensionlogger.debug("Subscription(" + debugIdentity() + ") has sendLock set so closing.");
+// }
+// }
+ return channel.isSuspended() || _sendLock.get();
+ }
+
+ /**
+ * Callback indicating that a queue has been deleted.
+ *
+ * @param queue The queue to delete
+ */
+ public void queueDeleted(AMQQueue queue) throws AMQException
+ {
+ 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 AMQProtocolSession publisher = entry.getMessage().getPublisher();
+ if(publisher != null)
+
+ {
+ // We don't want local messages so check to see if message is one we sent
+ Object localInstance;
+ Object msgInstance;
+
+ if ((protocolSession.getClientProperties() != null) &&
+ (localInstance = protocolSession.getClientProperties().getObject(CLIENT_PROPERTIES_INSTANCE)) != null)
+ {
+
+ if ((publisher.getClientProperties() != null) &&
+ (msgInstance = publisher.getClientProperties().getObject(CLIENT_PROPERTIES_INSTANCE)) != null)
+ {
+ if (localInstance == msgInstance || localInstance.equals(msgInstance))
+ {
+ // if (_logger.isTraceEnabled())
+ // {
+ // _logger.trace("(" + debugIdentity() + ") has no interest as it is a local message(" +
+ // msg.debugIdentity() + ")");
+ // }
+ return false;
+ }
+ }
+ }
+ else
+ {
+
+ localInstance = protocolSession.getClientIdentifier();
+ //todo - client id should be recoreded and this test removed but handled here
+
+ msgInstance = publisher.getClientIdentifier();
+ if (localInstance == msgInstance || ((localInstance != null) && localInstance.equals(msgInstance)))
+ {
+ // if (_logger.isTraceEnabled())
+ // {
+ // _logger.trace("(" + debugIdentity() + ") has no interest as it is a local message(" +
+ // msg.debugIdentity() + ")");
+ // }
+ return false;
+ }
+ }
+
+ }
+ }
+
+
+ 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 Queue<QueueEntry> getPreDeliveryQueue()
+ {
+ return _messages;
+ }
+
+ public void enqueueForPreDelivery(QueueEntry msg, boolean deliverFirst)
+ {
+ if (_messages != null)
+ {
+ if (deliverFirst)
+ {
+ _messages.pushHead(msg);
+ }
+ else
+ {
+ _messages.offer(msg);
+ }
+ }
+ }
+
+ public boolean isAutoClose()
+ {
+ return _autoClose;
+ }
+
+ public void close()
+ {
+ boolean closed = false;
+ synchronized (_sendLock)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Setting SendLock true:" + debugIdentity());
+ }
+
+ closed = _sendLock.getAndSet(true);
+ }
+
+ if (closed)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Called close() on a closed subscription");
+ }
+
+ return;
+ }
+
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Closing subscription (" + debugIdentity() + "):" + this);
+ }
+
+ if (_resendQueue != null && !_resendQueue.isEmpty())
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Requeuing closing subscription (" + debugIdentity() + "):" + this);
+ }
+ requeue();
+ }
+
+ //remove references in PDQ
+ if (_messages != null)
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Clearing PDQ (" + debugIdentity() + "):" + this);
+ }
+
+ _messages.clear();
+ }
+ }
+
+ private void autoclose()
+ {
+ close();
+
+ if (_autoClose && !_sentClose)
+ {
+ _logger.info("Closing autoclose subscription (" + debugIdentity() + "):" + this);
+
+ ProtocolOutputConverter converter = protocolSession.getProtocolOutputConverter();
+ converter.confirmConsumerAutoClose(channel.getChannelId(), consumerTag);
+ _sentClose = true;
+
+ //fixme JIRA do this better
+ try
+ {
+ channel.unsubscribeConsumer(protocolSession, consumerTag);
+ }
+ catch (AMQException e)
+ {
+ // Occurs if we cannot find the subscriber in the channel with protocolSession and consumerTag.
+ }
+ }
+ }
+
+ private void requeue()
+ {
+ if (_queue != null)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Requeuing :" + _resendQueue.size() + " messages");
+ }
+
+ while (!_resendQueue.isEmpty())
+ {
+ QueueEntry resent = _resendQueue.poll();
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Removed for resending:" + resent.debugIdentity());
+ }
+
+ resent.release();
+ _queue.subscriberHasPendingResend(false, this, resent);
+
+ try
+ {
+ channel.getTransactionalContext().deliver(resent, true);
+ }
+ catch (AMQException e)
+ {
+ _logger.error("MESSAGE LOSS : Unable to re-deliver messages", e);
+ }
+ }
+
+ if (!_resendQueue.isEmpty())
+ {
+ _logger.error("[MESSAGES LOST]Unable to re-deliver messages as queue is null.");
+ }
+
+ _queue.subscriberHasPendingResend(false, this, null);
+ }
+ else
+ {
+ if (!_resendQueue.isEmpty())
+ {
+ _logger.error("Unable to re-deliver messages as queue is null.");
+ }
+ }
+
+ // Clear the messages
+ _resendQueue = null;
+ }
+
+
+ public boolean isClosed()
+ {
+ return _sendLock.get(); // This rather than _close is used to signify the subscriber is now closed.
+ }
+
+ public boolean isBrowser()
+ {
+ return _isBrowser;
+ }
+
+ public boolean wouldSuspend(QueueEntry msg)
+ {
+ return _acks && channel.wouldSuspend(msg.getMessage());
+ }
+
+ public Queue<QueueEntry> getResendQueue()
+ {
+ if (_resendQueue == null)
+ {
+ _resendQueue = new ConcurrentLinkedQueueAtomicSize<QueueEntry>();
+ }
+ return _resendQueue;
+ }
+
+
+ public Queue<QueueEntry> getNextQueue(Queue<QueueEntry> messages)
+ {
+ if (_resendQueue != null && !_resendQueue.isEmpty())
+ {
+ return _resendQueue;
+ }
+
+ if (filtersMessages())
+ {
+ if (isAutoClose())
+ {
+ if (_messages.isEmpty())
+ {
+ autoclose();
+ return null;
+ }
+ }
+ return _messages;
+ }
+ else // we want the DM queue
+ {
+ return messages;
+ }
+ }
+
+ public void addToResendQueue(QueueEntry msg)
+ {
+ // add to our resend queue
+ getResendQueue().add(msg);
+
+ // Mark Queue has having content.
+ if (_queue == null)
+ {
+ _logger.error("Queue is null won't be able to resend messages");
+ }
+ else
+ {
+ _queue.subscriberHasPendingResend(true, this, msg);
+ }
+ }
+
+ public Object getSendLock()
+ {
+ return _sendLock;
+ }
+
+ public AMQChannel getChannel()
+ {
+ return channel;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.java
new file mode 100644
index 0000000000..bc17bcca9c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionManager.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;
+
+import java.util.List;
+
+/**
+ * Abstraction of actor that will determine the subscriber to whom
+ * a message will be sent.
+ */
+public interface SubscriptionManager
+{
+ public List<Subscription> getSubscriptions();
+ public boolean hasActiveSubscribers();
+ public Subscription nextSubscriber(QueueEntry entry);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java
new file mode 100644
index 0000000000..882efd380d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java
@@ -0,0 +1,274 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.List;
+import java.util.ListIterator;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+
+/** Holds a set of subscriptions for a queue and manages the round robin-ing of deliver etc. */
+class SubscriptionSet implements WeightedSubscriptionManager
+{
+ private static final Logger _log = Logger.getLogger(SubscriptionSet.class);
+
+ /** List of registered subscribers */
+ private List<Subscription> _subscriptions = new CopyOnWriteArrayList<Subscription>();
+
+ /** Used to control the round robin delivery of content */
+ private int _currentSubscriber;
+
+ private final Object _changeLock = new Object();
+ private volatile boolean _exclusive;
+
+
+ /** Accessor for unit tests. */
+ int getCurrentSubscriber()
+ {
+ return _currentSubscriber;
+ }
+
+ public void addSubscriber(Subscription subscription)
+ {
+ synchronized (_changeLock)
+ {
+ _subscriptions.add(subscription);
+ }
+ }
+
+ /**
+ * Remove the subscription, returning it if it was found
+ *
+ * @param subscription
+ *
+ * @return null if no match was found
+ */
+ public Subscription removeSubscriber(Subscription subscription)
+ {
+ // TODO: possibly need O(1) operation here.
+
+ Subscription sub = null;
+ synchronized (_changeLock)
+ {
+ int subIndex = _subscriptions.indexOf(subscription);
+
+ if (subIndex != -1)
+ {
+ //we can't just return the passed in subscription as it is a new object
+ // and doesn't contain the stored state we need.
+ //NOTE while this may be removed now anyone with an iterator will still have it in the list!!
+ sub = _subscriptions.remove(subIndex);
+ }
+ else
+ {
+ _log.error("Unable to remove from index(" + subIndex + ")subscription:" + subscription);
+ }
+ }
+ if (sub != null)
+ {
+ return sub;
+ }
+ else
+ {
+ debugDumpSubscription(subscription);
+ return null;
+ }
+ }
+
+ private void debugDumpSubscription(Subscription subscription)
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Subscription " + subscription + " not found. Dumping subscriptions:");
+ for (Subscription s : _subscriptions)
+ {
+ _log.debug("Subscription: " + s);
+ }
+ _log.debug("Subscription dump complete");
+ }
+ }
+
+ /**
+ * Return the next unsuspended subscription or null if not found. <p/> Performance note: This method can scan all
+ * items twice when looking for a subscription that is not suspended. The worst case occcurs when all subscriptions
+ * are suspended. However, it is does this without synchronisation and subscriptions may be added and removed
+ * concurrently. Also note that because of race conditions and when subscriptions are removed between calls to
+ * nextSubscriber, the IndexOutOfBoundsException also causes the scan to start at the beginning.
+ */
+ public Subscription nextSubscriber(QueueEntry msg)
+ {
+
+
+ try
+ {
+ final Subscription result = nextSubscriberImpl(msg);
+ if (result == null)
+ {
+ _currentSubscriber = 0;
+ return nextSubscriberImpl(msg);
+ }
+ else
+ {
+ return result;
+ }
+ }
+ catch (IndexOutOfBoundsException e)
+ {
+ _currentSubscriber = 0;
+ return nextSubscriber(msg);
+ }
+ }
+
+ private Subscription nextSubscriberImpl(QueueEntry msg)
+ {
+ if(_exclusive)
+ {
+ try
+ {
+ Subscription subscription = _subscriptions.get(0);
+ subscriberScanned();
+
+ if (!(subscription.isSuspended() || subscription.wouldSuspend(msg)))
+ {
+ if (subscription.hasInterest(msg))
+ {
+ // if the queue is not empty then this client is ready to receive a message.
+ //FIXME the queue could be full of sent messages.
+ // Either need to clean all PDQs after sending a message
+ // OR have a clean up thread that runs the PDQs expunging the messages.
+ if (!subscription.filtersMessages() || subscription.getPreDeliveryQueue().isEmpty())
+ {
+ return subscription;
+ }
+ }
+ }
+ }
+ catch(IndexOutOfBoundsException e)
+ {
+ }
+ return null;
+ }
+ else
+ {
+ if (_subscriptions.isEmpty())
+ {
+ return null;
+ }
+ final ListIterator<Subscription> iterator = _subscriptions.listIterator(_currentSubscriber);
+ while (iterator.hasNext())
+ {
+ Subscription subscription = iterator.next();
+ ++_currentSubscriber;
+ subscriberScanned();
+
+ if (!(subscription.isSuspended() || subscription.wouldSuspend(msg)))
+ {
+ if (subscription.hasInterest(msg))
+ {
+ // if the queue is not empty then this client is ready to receive a message.
+ //FIXME the queue could be full of sent messages.
+ // Either need to clean all PDQs after sending a message
+ // OR have a clean up thread that runs the PDQs expunging the messages.
+ if (!subscription.filtersMessages() || subscription.getPreDeliveryQueue().isEmpty())
+ {
+ return subscription;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+
+ /** Overridden in test classes. */
+ protected void subscriberScanned()
+ {
+ }
+
+ public boolean isEmpty()
+ {
+ return _subscriptions.isEmpty();
+ }
+
+ public List<Subscription> getSubscriptions()
+ {
+ return _subscriptions;
+ }
+
+ public boolean hasActiveSubscribers()
+ {
+ for (Subscription s : _subscriptions)
+ {
+ if (!s.isSuspended())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int getWeight()
+ {
+ int count = 0;
+ for (Subscription s : _subscriptions)
+ {
+ if (!s.isSuspended())
+ {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Notification that a queue has been deleted. This is called so that the subscription can inform the channel, which
+ * in turn can update its list of unacknowledged messages.
+ *
+ * @param queue
+ */
+ public void queueDeleted(AMQQueue queue) throws AMQException
+ {
+ for (Subscription s : _subscriptions)
+ {
+ s.queueDeleted(queue);
+ }
+ }
+
+ int size()
+ {
+ return _subscriptions.size();
+ }
+
+
+ public Object getChangeLock()
+ {
+ return _changeLock;
+ }
+
+ public void setExclusive(final boolean exclusive)
+ {
+ _exclusive = exclusive;
+ }
+
+}
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/WeakReferenceMessageHandle.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java
new file mode 100644
index 0000000000..373a64e2eb
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java
@@ -0,0 +1,227 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.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.BasicContentHeaderProperties;
+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 long _arrivalTime;
+
+
+ public WeakReferenceMessageHandle(MessageStore messageStore)
+ {
+ _messageStore = messageStore;
+ }
+
+ public ContentHeaderBody getContentHeaderBody(StoreContext context, Long messageId) throws AMQException
+ {
+ ContentHeaderBody chb = (_contentHeaderBody != null ? _contentHeaderBody.get() : null);
+ if (chb == null)
+ {
+ MessageMetaData mmd = loadMessageMetaData(context, messageId);
+ chb = mmd.getContentHeaderBody();
+ }
+ return chb;
+ }
+
+ private MessageMetaData loadMessageMetaData(StoreContext context, Long messageId)
+ 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, Long messageId) 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, Long messageId) throws AMQException
+ {
+ return getContentHeaderBody(context, messageId).bodySize;
+ }
+
+ public ContentChunk getContentChunk(StoreContext context, Long messageId, int index) throws AMQException, IllegalArgumentException
+ {
+ if (index > _contentBodies.size() - 1)
+ {
+ throw new IllegalArgumentException("Index " + index + " out of valid range 0 to " +
+ (_contentBodies.size() - 1));
+ }
+ WeakReference<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 messageId
+ * @param contentChunk
+ * @param isLastContentBody
+ * @throws AMQException
+ */
+ public void addContentBodyFrame(StoreContext storeContext, Long messageId, 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, Long messageId) throws AMQException
+ {
+ MessagePublishInfo bpb = (_messagePublishInfo != null ? _messagePublishInfo.get() : null);
+ if (bpb == null)
+ {
+ MessageMetaData mmd = loadMessageMetaData(context, messageId);
+
+ bpb = mmd.getMessagePublishInfo();
+ }
+ return bpb;
+ }
+
+ public boolean isRedelivered()
+ {
+ return _redelivered;
+ }
+
+ public void setRedelivered(boolean redelivered)
+ {
+ _redelivered = redelivered;
+ }
+
+ public boolean isPersistent(StoreContext context, Long messageId) throws AMQException
+ {
+ //todo remove literal values to a constant file such as AMQConstants in common
+ ContentHeaderBody chb = getContentHeaderBody(context, messageId);
+ return chb.properties instanceof BasicContentHeaderProperties &&
+ ((BasicContentHeaderProperties) chb.properties).getDeliveryMode() == 2;
+ }
+
+ /**
+ * This is called when all the content has been received.
+ *
+ * @param publishBody
+ * @param contentHeaderBody
+ * @throws AMQException
+ */
+ public void setPublishAndContentHeaderBody(StoreContext storeContext, Long messageId, 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, Long messageId) throws AMQException
+ {
+ _messageStore.removeMessage(storeContext, messageId);
+ }
+
+ public void enqueue(StoreContext storeContext, Long messageId, AMQQueue queue) throws AMQException
+ {
+ _messageStore.enqueueMessage(storeContext, queue.getName(), messageId);
+ }
+
+ public void dequeue(StoreContext storeContext, Long messageId, AMQQueue queue) throws AMQException
+ {
+ _messageStore.dequeueMessage(storeContext, queue.getName(), messageId);
+ }
+
+ public long getArrivalTime()
+ {
+ return _arrivalTime;
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java
new file mode 100644
index 0000000000..6c71571807
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeightedSubscriptionManager.java
@@ -0,0 +1,26 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+public interface WeightedSubscriptionManager extends SubscriptionManager
+{
+ public int getWeight();
+}
diff --git a/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..455983c6d8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.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.registry;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.log4j.Logger;
+import org.apache.qpid.server.configuration.Configurator;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An abstract application registry that provides access to configuration information and handles the
+ * construction and caching of configurable objects.
+ * <p/>
+ * Subclasses should handle the construction of the "registered objects" such as the exchange registry.
+ */
+public abstract class ApplicationRegistry implements IApplicationRegistry
+{
+ private static final Logger _logger = Logger.getLogger(ApplicationRegistry.class);
+
+ private static Map<Integer, IApplicationRegistry> _instanceMap = new HashMap<Integer, IApplicationRegistry>();
+
+ private final Map<Class<?>, Object> _configuredObjects = new HashMap<Class<?>, Object>();
+
+ protected final Configuration _configuration;
+
+ public static final int DEFAULT_INSTANCE = 1;
+ public static final String DEFAULT_APPLICATION_REGISTRY = "org.apache.qpid.server.util.NullApplicationRegistry";
+ public static String _APPLICATION_REGISTRY = DEFAULT_APPLICATION_REGISTRY;
+
+ static
+ {
+ Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownService()));
+ }
+
+ private static class ShutdownService implements Runnable
+ {
+ public void run()
+ {
+ _logger.info("Shutting down application registries...");
+ 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);
+ }
+ }
+
+ public static void remove(int instanceID)
+ {
+ try
+ {
+ _instanceMap.get(instanceID).close();
+ }
+ catch (Exception e)
+ {
+ _logger.error("Error shutting down message store: " + e, e);
+
+ }
+ finally
+ {
+ _instanceMap.remove(instanceID);
+ }
+ }
+
+ public static void removeAll()
+ {
+ Object[] keys = _instanceMap.keySet().toArray();
+ for (Object k : keys)
+ {
+ remove((Integer) k);
+ }
+ }
+
+ protected ApplicationRegistry(Configuration 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
+ {
+ for (VirtualHost virtualHost : getVirtualHostRegistry().getVirtualHosts())
+ {
+ virtualHost.close();
+ }
+
+ // close the rmi registry(if any) started for management
+ if (getInstance().getManagedObjectRegistry() != null)
+ {
+ getInstance().getManagedObjectRegistry().close();
+ }
+ }
+
+ public Configuration getConfiguration()
+ {
+ return _configuration;
+ }
+
+ public <T> T getConfiguredObject(Class<T> instanceType)
+ {
+ T instance = (T) _configuredObjects.get(instanceType);
+ if (instance == null)
+ {
+ try
+ {
+ instance = instanceType.newInstance();
+ }
+ catch (Exception e)
+ {
+ _logger.error("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor");
+ throw new IllegalArgumentException("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor", e);
+ }
+ Configurator.configure(instance);
+ _configuredObjects.put(instanceType, instance);
+ }
+ return instance;
+ }
+
+
+ public static void setDefaultApplicationRegistry(String clazz)
+ {
+ _APPLICATION_REGISTRY = clazz;
+ }
+}
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..e0fcaa208d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java
@@ -0,0 +1,187 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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 java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.SystemConfiguration;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.qpid.server.management.JMXManagedObjectRegistry;
+import org.apache.qpid.server.management.ManagedObjectRegistry;
+import org.apache.qpid.server.management.ManagementConfiguration;
+import org.apache.qpid.server.management.NoopManagedObjectRegistry;
+import org.apache.qpid.server.plugins.PluginManager;
+import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
+import org.apache.qpid.server.security.auth.database.ConfigurationFilePrincipalDatabaseManager;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager;
+import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.ACLManager;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+import org.apache.qpid.AMQException;
+
+public class ConfigurationFileApplicationRegistry extends ApplicationRegistry
+{
+
+ private ManagedObjectRegistry _managedObjectRegistry;
+
+ private AuthenticationManager _authenticationManager;
+
+ private ACLPlugin _accessManager;
+
+ private PrincipalDatabaseManager _databaseManager;
+
+ private VirtualHostRegistry _virtualHostRegistry;
+
+
+ //fixme Why is this not used.
+ private final Map<String, VirtualHost> _virtualHosts = new ConcurrentHashMap<String, VirtualHost>();
+
+ private PluginManager _pluginManager;
+
+
+ public ConfigurationFileApplicationRegistry(File configurationURL) throws ConfigurationException
+ {
+ super(config(configurationURL));
+ }
+
+ // Our configuration class needs to make the interpolate method
+ // public so it can be called below from the config method.
+ private static class MyConfiguration extends CompositeConfiguration
+ {
+ public String interpolate(String obj)
+ {
+ return super.interpolate(obj);
+ }
+ }
+
+ private static final Configuration config(File url) throws ConfigurationException
+ {
+ // We have to override the interpolate methods so that
+ // interpolation takes place accross the entirety of the
+ // composite configuration. Without doing this each
+ // configuration object only interpolates variables defined
+ // inside itself.
+ final MyConfiguration conf = new MyConfiguration();
+ conf.addConfiguration(new SystemConfiguration()
+ {
+ protected String interpolate(String o)
+ {
+ return conf.interpolate(o);
+ }
+ });
+ conf.addConfiguration(new XMLConfiguration(url)
+ {
+ protected String interpolate(String o)
+ {
+ return conf.interpolate(o);
+ }
+ });
+ return conf;
+ }
+
+ public void initialise() throws Exception
+ {
+ initialiseManagedObjectRegistry();
+
+ _virtualHostRegistry = new VirtualHostRegistry();
+
+ _accessManager = ACLManager.loadACLManager("default", _configuration);
+
+ _databaseManager = new ConfigurationFilePrincipalDatabaseManager();
+
+ _authenticationManager = new PrincipalDatabaseAuthenticationManager(null, null);
+
+ _databaseManager.initialiseManagement(_configuration);
+
+ _managedObjectRegistry.start();
+
+ _pluginManager = new PluginManager(_configuration.getString("plugin-directory"));
+
+ initialiseVirtualHosts();
+
+ }
+
+ private void initialiseVirtualHosts() throws Exception
+ {
+ for (String name : getVirtualHostNames())
+ {
+
+ _virtualHostRegistry.registerVirtualHost(new VirtualHost(name, getConfiguration().subset("virtualhosts.virtualhost." + name)));
+ }
+ }
+
+ private void initialiseManagedObjectRegistry() throws AMQException
+ {
+ ManagementConfiguration config = getConfiguredObject(ManagementConfiguration.class);
+ if (config.enabled)
+ {
+ _managedObjectRegistry = new JMXManagedObjectRegistry();
+ }
+ else
+ {
+ _managedObjectRegistry = new NoopManagedObjectRegistry();
+ }
+ }
+
+
+ public VirtualHostRegistry getVirtualHostRegistry()
+ {
+ return _virtualHostRegistry;
+ }
+
+ public ACLPlugin getAccessManager()
+ {
+ return _accessManager;
+ }
+
+ public ManagedObjectRegistry getManagedObjectRegistry()
+ {
+ return _managedObjectRegistry;
+ }
+
+ public PrincipalDatabaseManager getDatabaseManager()
+ {
+ return _databaseManager;
+ }
+
+ public AuthenticationManager getAuthenticationManager()
+ {
+ return _authenticationManager;
+ }
+
+ public Collection<String> getVirtualHostNames()
+ {
+ return getConfiguration().getList("virtualhosts.virtualhost.name");
+ }
+
+ public PluginManager getPluginManager()
+ {
+ return _pluginManager;
+ }
+}
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..ca10fbdba2
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.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.registry;
+
+import java.util.Collection;
+
+import org.apache.commons.configuration.Configuration;
+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.ACLPlugin;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+
+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;
+
+ void close() throws Exception;
+
+ /**
+ * This gets access to a "configured object". A configured object has fields populated from a the configuration
+ * object (Commons Configuration) automatically, where it has the appropriate attributes defined on fields.
+ * Application registry implementations can choose the refresh strategy or caching approach.
+ * @param instanceType the type of object you want initialised. This must be unique - i.e. you can only
+ * have a single object of this type in the system.
+ * @return the configured object
+ */
+ <T> T getConfiguredObject(Class<T> instanceType);
+
+ /**
+ * Get the low level configuration. For use cases where the configured object approach is not required
+ * you can get the complete configuration information.
+ * @return a Commons Configuration instance
+ */
+ Configuration getConfiguration();
+
+ ManagedObjectRegistry getManagedObjectRegistry();
+
+ PrincipalDatabaseManager getDatabaseManager();
+
+ AuthenticationManager getAuthenticationManager();
+
+ Collection<String> getVirtualHostNames();
+
+ VirtualHostRegistry getVirtualHostRegistry();
+
+ ACLPlugin getAccessManager();
+
+ PluginManager getPluginManager();
+
+}
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..539f32a732
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLManager.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.security.access.plugins.DenyAll;
+import org.apache.qpid.configuration.PropertyUtils;
+import org.apache.log4j.Logger;
+
+import java.util.List;
+import java.lang.reflect.Method;
+
+public class ACLManager
+{
+ private static final Logger _logger = Logger.getLogger(ACLManager.class);
+
+ public static ACLPlugin loadACLManager(String name, Configuration hostConfig) throws ConfigurationException
+ {
+ ACLPlugin aclPlugin = ApplicationRegistry.getInstance().getAccessManager();
+
+ if (hostConfig == null)
+ {
+ _logger.warn("No Configuration specified. Using default ACLPlugin '" + aclPlugin.getPluginName()
+ + "' for VirtualHost:'" + name + "'");
+ return aclPlugin;
+ }
+
+ String accessClass = hostConfig.getString("security.access.class");
+ if (accessClass == null)
+ {
+
+ _logger.warn("No ACL Plugin specified. Using default ACL Plugin '" + aclPlugin.getPluginName() +
+ "' for VirtualHost:'" + name + "'");
+ return aclPlugin;
+ }
+
+ Object o;
+ try
+ {
+ o = Class.forName(accessClass).newInstance();
+ }
+ catch (Exception e)
+ {
+ throw new ConfigurationException("Error initialising ACL: " + e, e);
+ }
+
+ if (!(o instanceof ACLPlugin))
+ {
+ throw new ConfigurationException("ACL Plugins must implement the ACLPlugin interface");
+ }
+
+ initialiseAccessControl((ACLPlugin) o, hostConfig);
+
+ aclPlugin = getManager((ACLPlugin) o);
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Initialised ACL Plugin '" + aclPlugin.getPluginName()
+ + "' for virtualhost '" + name + "' successfully");
+ }
+
+ return aclPlugin;
+ }
+
+
+ private static void initialiseAccessControl(ACLPlugin accessManager, Configuration config)
+ throws ConfigurationException
+ {
+ //First provide the ACLPlugin with the host configuration
+
+ accessManager.setConfiguaration(config);
+
+ //Provide additional attribute customisation.
+ String baseName = "security.access.attributes.attribute.";
+ List<String> argumentNames = config.getList(baseName + "name");
+ List<String> argumentValues = config.getList(baseName + "value");
+ for (int i = 0; i < argumentNames.size(); i++)
+ {
+ String argName = argumentNames.get(i);
+ if (argName == null || argName.length() == 0)
+ {
+ throw new ConfigurationException("Access Control 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 = accessManager.getClass().getMethod(methodName, String.class);
+ }
+ catch (NoSuchMethodException e)
+ {
+ //do nothing as method will be null
+ }
+
+ if (method == null)
+ {
+ throw new ConfigurationException("No method " + methodName + " found in class " + accessManager.getClass() +
+ " hence unable to configure access control. The method must be public and " +
+ "have a single String argument with a void return type");
+ }
+ try
+ {
+ method.invoke(accessManager, PropertyUtils.replaceProperties(argumentValues.get(i)));
+ }
+ catch (Exception e)
+ {
+ ConfigurationException ce = new ConfigurationException(e.getMessage(), e.getCause());
+ ce.initCause(e);
+ throw ce;
+ }
+ }
+ }
+
+
+ private static ACLPlugin getManager(ACLPlugin manager)
+ {
+ if (manager == null)
+ {
+ if (ApplicationRegistry.getInstance().getAccessManager() == null)
+ {
+ return new DenyAll();
+ }
+ else
+ {
+ return ApplicationRegistry.getInstance().getAccessManager();
+ }
+ }
+ else
+ {
+ return manager;
+ }
+ }
+
+ public static Logger getLogger()
+ {
+ return _logger;
+ }
+}
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..7855f147b4
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPlugin.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.security.access;
+
+import org.apache.qpid.framing.AMQMethodBody;
+
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.commons.configuration.Configuration;
+
+
+public interface ACLPlugin
+{
+ /**
+ * Pseudo-Code:
+ * Identify requested RighConnectiont
+ * Lookup users ability for that right.
+ * if rightsExists
+ * Validate right on object
+ * Return result
+ * e.g
+ * User, CONSUME , Queue
+ * User, CONSUME , Exchange + RoutingKey
+ * User, PUBLISH , Exchange + RoutingKey
+ * User, CREATE , Exchange || Queue
+ * User, BIND , Exchange + RoutingKey + Queue
+ *
+ * @param session - The session requesting access
+ * @param permission - The permission requested
+ * @param parameters - The above objects that are used to authorise the request.
+ * @return The AccessResult decision
+ */
+ //todo potential refactor this ConnectionException Out of here
+ AccessResult authorise(AMQProtocolSession session, Permission permission, AMQMethodBody body, Object... parameters) throws AMQConnectionException;
+
+ String getPluginName();
+
+ void setConfiguaration(Configuration config);
+
+}
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..89cead69b3
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessResult.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.access;
+
+public class AccessResult
+{
+ public enum AccessStatus
+ {
+ GRANTED, REFUSED
+ }
+
+ StringBuilder _authorizer;
+ AccessStatus _status;
+
+ public AccessResult(ACLPlugin authorizer, AccessStatus status)
+ {
+ _status = status;
+ _authorizer = new StringBuilder(authorizer.getPluginName());
+ }
+
+ public void setAuthorizer(ACLPlugin authorizer)
+ {
+ _authorizer.append(authorizer.getPluginName());
+ }
+
+ public String getAuthorizer()
+ {
+ return _authorizer.toString();
+ }
+
+ public void setStatus(AccessStatus status)
+ {
+ _status = status;
+ }
+
+ public AccessStatus getStatus()
+ {
+ return _status;
+ }
+
+ public void addAuthorizer(ACLPlugin accessManager)
+ {
+ _authorizer.insert(0, "->");
+ _authorizer.insert(0, accessManager.getPluginName());
+ }
+
+
+}
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/Permission.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.java
new file mode 100644
index 0000000000..5d439a99eb
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.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.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,
+ CREATE,
+ 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..22f1cf25a8
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/PrincipalPermissions.java
@@ -0,0 +1,587 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.exchange.Exchange;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PrincipalPermissions
+{
+
+ private static final int CONSUME_QUEUES_KEY = 0;
+ private static final int CONSUME_TEMPORARY_KEY = 1;
+ private static final int CONSUME_OWN_QUEUES_ONLY_KEY = 2;
+
+ private static final int CREATE_QUEUES_KEY = 0;
+ private static final int CREATE_EXCHANGES_KEY = 1;
+
+ private static final int CREATE_QUEUE_TEMPORARY_KEY = 2;
+ private static final int CREATE_QUEUE_QUEUES_KEY = 1;
+ private static final int CREATE_QUEUE_EXCHANGES_KEY = 0;
+
+ private static final int CREATE_QUEUE_EXCHANGES_TEMPORARY_KEY = 0;
+ private static final int CREATE_QUEUE_EXCHANGES_ROUTINGKEYS_KEY = 1;
+
+ private static final int PUBLISH_EXCHANGES_KEY = 0;
+
+ private Map _permissions;
+
+ private String _user;
+
+
+ public PrincipalPermissions(String user)
+ {
+ _user = user;
+ _permissions = new ConcurrentHashMap();
+ }
+
+ 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 CREATE: // Parameters : Boolean temporary, AMQShortString queueName
+ // , AMQShortString exchangeName , AMQShortString routingKey
+ // || AMQShortString exchangeName , AMQShortString Class
+
+ 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;
+ }
+
+
+ if (parameters[0] instanceof Boolean) //Create Queue :
+ // Boolean temporary, [AMQShortString queueName, AMQShortString exchangeName , AMQShortString routingKey]
+ {
+ 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 ? (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);
+ }
+ }
+ }
+ else // Create Exchange : AMQShortString exchangeName , AMQShortString Class
+ {
+ Map create_exchanges = (Map) createRights.get(CREATE_EXCHANGES_KEY);
+
+ if (create_exchanges == null)
+ {
+ create_exchanges = new ConcurrentHashMap();
+ createRights.put(CREATE_EXCHANGES_KEY, create_exchanges);
+ }
+
+ //Should perhaps error if parameters[0] is null;
+ AMQShortString exchangeName = parameters.length > 0 ? (AMQShortString) parameters[0] : null;
+ AMQShortString className = parameters.length > 1 ? (AMQShortString) parameters[1] : null;
+
+ //Store the exchangeName / class mapping if the mapping is null
+ createRights.put(exchangeName, 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;
+ }
+
+ }
+
+ public boolean authorise(Permission permission, Object... parameters)
+ {
+
+ switch (permission)
+ {
+ case ACCESS:
+ return true; // 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
+
+// QueueDeclareBody body = (QueueDeclareBody) parameters[0];
+
+ Exchange exchange = (Exchange) parameters[1];
+
+ if (exchange.getName().equals("<<default>>"))
+ {
+ // Binding to <<default>> can not be programmed via ACLs due to '<','>' unable to be used in the XML
+ System.err.println("Binding on exchange <<default>> not alowed via ACLs");
+ }
+
+ AMQQueue bind_queueName = (AMQQueue) parameters[2];
+ AMQShortString routingKey = (AMQShortString) parameters[3];
+
+ //Get all Create Rights for this user
+ Map bindCreateRights = (Map) _permissions.get(Permission.CREATE);
+
+ //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 true;
+ }
+
+ // 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 true;
+ }
+ 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;
+ }
+
+
+ }
+ 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);
+ }
+
+ //no white list so all allowed, drop through to return true below.
+ }
+
+ // not a temporary queue and no white list so all allowed.
+ return true;
+ }
+
+ case CREATE:// Paramters : QueueDeclareBody || ExchangeDeclareBody
+
+ Map createRights = (Map) _permissions.get(permission);
+
+ // If there are no create rights then deny request
+ if (createRights == null)
+ {
+ return false;
+ }
+
+ if (parameters.length == 1)
+ {
+ if (parameters[0] instanceof QueueDeclareBody)
+ {
+ QueueDeclareBody body = (QueueDeclareBody) parameters[0];
+
+ //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 = body.getQueue();
+
+
+ if (body.getAutoDelete())// we have a temporary queue
+ {
+ return (Boolean) create_queues.get(CREATE_QUEUE_TEMPORARY_KEY);
+ }
+ else
+ {
+ // If there is a white list then check
+ return create_queues_queues == null || create_queues_queues.containsKey(queueName);
+ }
+
+ }
+ else if (parameters[0] instanceof ExchangeDeclareBody)
+ {
+ ExchangeDeclareBody body = (ExchangeDeclareBody) parameters[0];
+
+ AMQShortString exchangeName = body.getExchange();
+
+ Map create_exchanges = (Map) createRights.get(CREATE_EXCHANGES_KEY);
+
+ // If the exchange list is doesn't exist then all is allowed else check the valid exchanges
+ return create_exchanges == null || create_exchanges.containsKey(exchangeName);
+ }
+ }
+ break;
+ 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);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ // 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());
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ // If we are
+ return queues.size() == 0 || queues.contains(queue.getName());
+ }
+ }
+
+ // Can't authenticate without the right parameters
+ return false;
+ case DELETE:
+ break;
+
+ case PUBLISH: // Parameters : Exchange exchange, AMQShortString routingKey
+ Map publishRights = (Map) _permissions.get(permission);
+
+ if (publishRights == null)
+ {
+ return false;
+ }
+
+ Map exchanges = (Map) publishRights.get(PUBLISH_EXCHANGES_KEY);
+
+ // Having no exchanges listed gives full publish rights to all exchanges
+ if (exchanges == null)
+ {
+ return true;
+ }
+ // 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(parameters[0]))
+ {
+ return false;
+ }
+ else
+ {
+
+ // Get valid routing keys
+ HashSet routingKeys = (HashSet) exchanges.get(parameters[0]);
+
+ // Having no routingKeys in the map then all are allowed.
+ if (routingKeys == null)
+ {
+ return true;
+ }
+ 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;
+ }
+ }
+ case PURGE:
+ break;
+ case UNBIND:
+ break;
+
+ }
+
+ return false;
+ }
+}
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..a8ae03cc5d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBean.java
@@ -0,0 +1,468 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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 String _accessFileName;
+ 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);
+ }
+
+ 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)
+ {
+
+ if (_accessRights.get(username) == null)
+ {
+ // If the user doesn't exist in the user 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);
+ }
+ }
+
+ saveAccessFile();
+ }
+ 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))
+ {
+ _accessRights.put(username, "");
+
+ return setRights(username, read, write, admin);
+ }
+
+ return false;
+ }
+
+ public boolean deleteUser(String username)
+ {
+
+ try
+ {
+ if (_principalDatabase.deletePrincipal(new UsernamePrincipal(username)))
+ {
+ try
+ {
+ _accessRightsUpdate.lock();
+
+ _accessRights.remove(username);
+ saveAccessFile();
+ }
+ finally
+ {
+ if (_accessRightsUpdate.isHeldByCurrentThread())
+ {
+ _accessRightsUpdate.unlock();
+ }
+ }
+ return true;
+ }
+ }
+ catch (AccountNotFoundException e)
+ {
+ _logger.warn("Attempt to delete user (" + username + ") that doesn't exist");
+ }
+
+ return false;
+ }
+
+ public boolean reloadData()
+ {
+ try
+ {
+ try
+ {
+ loadAccessFile();
+ }
+ catch (ConfigurationException e)
+ {
+ _logger.info("Reload failed due to:" + e);
+ return false;
+ }
+
+ // Reload successful
+ return true;
+ }
+ catch (IOException e)
+ {
+ _logger.info("Reload failed due to:" + e);
+ // Reload unsuccessful
+ return false;
+ }
+ }
+
+
+ @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
+ {
+ _accessFileName = accessFile;
+
+ if (_accessFileName != null)
+ {
+ loadAccessFile();
+ }
+ else
+ {
+ _logger.warn("Access rights file specified is null. Access rights not changed.");
+ }
+ }
+
+ private void loadAccessFile() throws IOException, ConfigurationException
+ {
+ try
+ {
+ _accessRightsUpdate.lock();
+
+ Properties accessRights = new Properties();
+
+ File accessFile = new File(_accessFileName);
+
+ if (!accessFile.exists())
+ {
+ throw new ConfigurationException("'" + _accessFileName + "' does not exist");
+ }
+
+ if (!accessFile.canRead())
+ {
+ throw new ConfigurationException("Cannot read '" + _accessFileName + "'.");
+ }
+
+ if (!accessFile.canWrite())
+ {
+ _logger.warn("Unable to write to access file '" + _accessFileName + "' changes will not be preserved.");
+ }
+
+ accessRights.load(new FileInputStream(accessFile));
+ checkAccessRights(accessRights);
+ setAccessRights(accessRights);
+ }
+ finally
+ {
+ if (_accessRightsUpdate.isHeldByCurrentThread())
+ {
+ _accessRightsUpdate.unlock();
+ }
+ }
+ }
+
+ 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()
+ {
+ try
+ {
+ _accessRightsUpdate.lock();
+ try
+ {
+ // remove old temporary file
+ File tmp = new File(_accessFileName + ".tmp");
+ if (tmp.exists())
+ {
+ tmp.delete();
+ }
+
+ //remove old backup
+ File old = new File(_accessFileName + ".old");
+ if (old.exists())
+ {
+ old.delete();
+ }
+
+ // Rename current file
+ File rights = new File(_accessFileName);
+ rights.renameTo(old);
+
+ 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(rights);
+
+ // delete tmp
+ tmp.delete();
+ }
+ catch (IOException e)
+ {
+ _logger.warn("Problem occured saving '" + _accessFileName + "' changes may not be preserved. :" + e);
+ }
+ }
+ finally
+ {
+ if (_accessRightsUpdate.isHeldByCurrentThread())
+ {
+ _accessRightsUpdate.unlock();
+ }
+ }
+ }
+
+ private String getCurrentJMXUser()
+ {
+ AccessControlContext acc = AccessController.getContext();
+ Subject subject = Subject.getSubject(acc);
+
+ // 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..658d7ebbd3
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/UserManagement.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.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";
+
+ //********** 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/AllowAll.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java
new file mode 100644
index 0000000000..a51061aa0d
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.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.plugins;
+
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.ACLManager;
+import org.apache.qpid.server.security.access.AccessResult;
+import org.apache.qpid.server.security.access.Accessable;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.commons.configuration.Configuration;
+
+public class AllowAll implements ACLPlugin
+{
+ public AccessResult authorise(AMQProtocolSession session, Permission permission, AMQMethodBody body, Object... parameters)
+ {
+ if (ACLManager.getLogger().isInfoEnabled())
+ {
+ ACLManager.getLogger().info("Allowing user:" + session.getAuthorizedID() + " for :" + permission.toString()
+ + " on " + body.getClass().getSimpleName()
+ + (parameters == null || parameters.length == 0 ? "" : "-" + accessablesToString(parameters)));
+ }
+
+ return new AccessResult(this, AccessResult.AccessStatus.GRANTED);
+ }
+
+ public static String accessablesToString(Object[] accessObject)
+ {
+ StringBuilder sb = new StringBuilder();
+
+ for (Object access : accessObject)
+ {
+ sb.append(access.getClass().getSimpleName() + ":" + access.toString() + ", ");
+ }
+
+ return sb.delete(sb.length() - 2, sb.length()).toString();
+ }
+
+ public String getPluginName()
+ {
+ return "AllowAll";
+ }
+
+ public void setConfiguaration(Configuration config)
+ {
+ //no-op
+ }
+
+}
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..80c125e737
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/DenyAll.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access.plugins;
+
+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.AccessResult;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.commons.configuration.Configuration;
+
+public class DenyAll implements ACLPlugin
+{
+ public AccessResult authorise(AMQProtocolSession session, Permission permission, AMQMethodBody body, Object... parameters) throws AMQConnectionException
+ {
+
+ if (ACLManager.getLogger().isInfoEnabled())
+ {
+ }
+ ACLManager.getLogger().info("Denying user:" + session.getAuthorizedID() + " for :" + permission.toString()
+ + " on " + body.getClass().getSimpleName()
+ + (parameters == null || parameters.length == 0 ? "" : "-" + AllowAll.accessablesToString(parameters)));
+
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "DenyAll Plugin");
+ }
+
+ public String getPluginName()
+ {
+ return "DenyAll";
+ }
+
+ public void setConfiguaration(Configuration config)
+ {
+ //no-op
+ }
+}
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..c09cdc33f5
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java
@@ -0,0 +1,431 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.security.access.ACLManager;
+import org.apache.qpid.server.security.access.ACLPlugin;
+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 java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This uses the default
+ */
+public class SimpleXML implements ACLPlugin
+{
+ private static final Logger _logger = ACLManager.getLogger();
+
+ private Map<String, PrincipalPermissions> _users;
+
+ public SimpleXML()
+ {
+ _users = new ConcurrentHashMap<String, PrincipalPermissions>();
+ }
+
+ public void setConfiguaration(Configuration config)
+ {
+ _logger.info("SimpleXML Configuration");
+
+ 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("security.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);
+ _logger.info("PUBLISH:GRANTED:USER:" + user + " for all destinations");
+ }
+
+ // 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);
+ _logger.info("PUBLISH:GRANTED:USER:" + user + " on Exchange '" + exchangeName + "' for key '" + 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);
+ _logger.info("PUBLISH:GRANTED:USER:" + user + " on Exchange:" + 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("security.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);
+ if (temporary)
+ {
+ if (ownQueues)
+ {
+ _logger.info("CONSUME:GRANTED:USER:" + user + " on temporary queues owned by user.");
+ }
+ else
+ {
+ _logger.info("CONSUME:GRANTED:USER:" + user + " on all temporary queues.");
+ }
+ }
+ else
+ {
+ _logger.info("CONSUME:GRANTED:USER:" + user + " on queue '" + queueName + "'");
+ }
+ }
+
+ //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);
+ _logger.info("CONSUME:GRANTED:USER:" + user + " from all queues.");
+ }
+ }
+
+ private void processCreate(Configuration config)
+ {
+ Configuration createConfig = config.subset("security.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.CREATE, user, temporary,
+ (queueName.equals("") ? null : queueName),
+ (exchange.equals("") ? null : exchange),
+ (routingKey.equals("") ? null : routingKey));
+
+ _logger.info("CREATE :GRANTED:USER:" + user + " for "
+ + (queueName.equals("") ? "" : "queue '" + queueName + "' ")
+ + (exchange.equals("") ? "" : "exchange '" + exchange + "' ")
+ + (routingKey.equals("") ? "" : " rk '" + routingKey + "' ")
+ + (temporary ? " temporary:" + temporary : ""));
+ }
+
+ //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.CREATE, user, temporary, queueName);
+ if (temporary)
+ {
+ _logger.info("CREATE :GRANTED:USER:" + user + " from temporary queues on any exchange.");
+ }
+ else
+ {
+ _logger.info("CREATE :GRANTED:USER:" + user + " from queue '" + queueName + "' on any exchange.");
+ }
+ }
+
+ //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.CREATE, user, exchange, clazz);
+ _logger.info("CREATE:GRANTED:USER:" + user + " for exchange '" + exchange + ":class:'" + 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.CREATE, user);
+ _logger.info("CREATE:GRANTED:USER:" + user + " from all queues & exchanges.");
+ }
+
+
+ }
+
+ public String getPluginName()
+ {
+ return "Simple";
+ }
+
+ public AccessResult authorise(AMQProtocolSession session, Permission permission, AMQMethodBody body, Object... parameters) throws AMQConnectionException
+ {
+ String error = "";
+
+ if (ACLManager.getLogger().isInfoEnabled())
+ {
+ ACLManager.getLogger().info("Simple Authorisation processing user:" + session.getAuthorizedID() + " for :" + permission.toString()
+ + " on " + body.getClass().getSimpleName()
+ + (parameters == null || parameters.length == 0 ? "" : "-" + AllowAll.accessablesToString(parameters)));
+ }
+
+ String username = session.getAuthorizedID().getName();
+
+ //Get the Users Permissions
+ PrincipalPermissions permissions = _users.get(username);
+
+ _logger.warn("Processing :" + permission + " for:" + username + ":" + permissions+":"+parameters.length);
+
+ if (permissions != null)
+ {
+ switch (permission)
+ {
+ case ACCESS:
+ _logger.warn("GRANTED:"+permission);
+ return new AccessResult(this, AccessResult.AccessStatus.GRANTED);
+ case BIND: // Body QueueDeclareBody - Parameters : Exchange, Queue, QueueName
+ // Body QueueBindBody - Paramters : Exchange, Queue, QueueName
+ if (parameters.length == 3)
+ {
+ // Parameters : Exchange, Queue, RoutingKey
+ if (permissions.authorise(Permission.BIND, body, parameters[0], parameters[1], parameters[2]))
+ {
+ _logger.warn("GRANTED:"+permission);
+ return new AccessResult(this, AccessResult.AccessStatus.GRANTED);
+ }
+ }
+ break;
+ case CONSUME: // Parameters : none
+ if (parameters.length == 1 && permissions.authorise(Permission.CONSUME, parameters[0]))
+ {
+ _logger.warn("GRANTED:"+permission);
+ return new AccessResult(this, AccessResult.AccessStatus.GRANTED);
+ }
+ break;
+ case CREATE: // Body : QueueDeclareBody | ExchangeDeclareBody - Parameters : none
+ if (permissions.authorise(Permission.CREATE, body))
+ {
+ _logger.warn("GRANTED:"+permission);
+ return new AccessResult(this, AccessResult.AccessStatus.GRANTED);
+ }
+ break;
+ case PUBLISH: // Body : BasicPublishBody Parameters : exchange
+ if (parameters.length == 1 && parameters[0] instanceof Exchange)
+ {
+ if (permissions.authorise(Permission.PUBLISH, ((Exchange) parameters[0]).getName(),
+ ((BasicPublishBody) body).getRoutingKey()))
+ {
+ _logger.warn("GRANTED:"+permission);
+ return new AccessResult(this, AccessResult.AccessStatus.GRANTED);
+ }
+ }
+ break;
+ case PURGE:
+ break;
+ case DELETE:
+ break;
+ case UNBIND:
+ break;
+ }
+ }
+
+ _logger.warn("Access Denied for :" + permission + " for:" + username + ":" + permissions);
+ //todo potential refactor this ConnectionException Out of here
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, error);
+ }
+
+//todo use or lose
+// if (accessObject instanceof VirtualHost)
+// {
+// VirtualHostAccess[] hosts = lookupVirtualHost(user.getName());
+//
+// if (hosts != null)
+// {
+// for (VirtualHostAccess host : hosts)
+// {
+// if (accessObject.getAccessableName().equals(host.getVirtualHost()))
+// {
+// if (host.getAccessRights().allows(rights))
+// {
+// return new AccessResult(this, AccessResult.AccessStatus.GRANTED);
+// }
+// else
+// {
+// return new AccessResult(this, AccessResult.AccessStatus.REFUSED);
+// }
+// }
+// }
+// }
+// }
+// else if (accessObject instanceof AMQQueue)
+// {
+// String[] queues = lookupQueue(username, ((AMQQueue) accessObject).getVirtualHost());
+//
+// if (queues != null)
+// {
+// for (String queue : queues)
+// {
+// if (accessObject.getAccessableName().equals(queue))
+// {
+// return new AccessResult(this, AccessResult.AccessStatus.GRANTED);
+// }
+// }
+// }
+// }
+
+// return new AccessResult(this, AccessResult.AccessStatus.REFUSED);
+// }
+
+}
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..0e3aea4de0
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth;
+
+public class AuthenticationResult
+{
+ public enum AuthenticationStatus
+ {
+ SUCCESS, CONTINUE, ERROR
+ }
+
+ public AuthenticationStatus status;
+ public byte[] challenge;
+
+ public AuthenticationResult(byte[] challenge, AuthenticationStatus status)
+ {
+ this.status = status;
+ this.challenge = challenge;
+ }
+
+ public AuthenticationResult(AuthenticationStatus status)
+ {
+ this.status = status;
+ }
+}
diff --git a/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..348bccb4e9
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java
@@ -0,0 +1,598 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.crammd5.CRAMMD5HashedInitialiser;
+import org.apache.qpid.server.security.access.management.AMQUserManagementMBean;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.EncoderException;
+
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.AccountNotFoundException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.UnsupportedEncodingException;
+import java.io.PrintStream;
+import java.util.regex.Pattern;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.concurrent.locks.ReentrantLock;
+import java.security.Principal;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 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;
+ private static final String DEFAULT_ENCODING = "utf-8";
+ private Map<String, User> _users = new HashMap<String, User>();
+ 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
+ *
+ * @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);
+
+ int index = 0;
+ boolean verified = true;
+
+ while (verified & index < password.length)
+ {
+ verified = (pwd[index] == password[index]);
+ index++;
+ }
+ return verified;
+ }
+
+ public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException
+ {
+ User 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 + "' will revert at restart");
+ //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;
+ }
+
+ User user = new User(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());
+ return false;
+ }
+ }
+ finally
+ {
+ if (_userUpdate.isHeldByCurrentThread())
+ {
+ _userUpdate.unlock();
+ }
+ }
+ }
+
+ public boolean deletePrincipal(Principal principal) throws AccountNotFoundException
+ {
+ User 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)
+ {
+ User 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;
+ }
+
+ User user = new User(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 = new File(_passwordFile.getAbsolutePath() + ".tmp");
+ if (tmp.exists())
+ {
+ tmp.delete();
+ }
+ 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));
+ continue;
+ }
+
+ User 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.getEncodePassword();
+
+ 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 (User user : _users.values())
+ {
+ if (user.isModified())
+ {
+ byte[] encodedPassword;
+ try
+ {
+ encodedPassword = user.getEncodePassword();
+ 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();
+ }
+ }
+ }
+
+ private class User implements Principal
+ {
+ String _name;
+ char[] _password;
+ byte[] _encodedPassword = null;
+ private boolean _modified = false;
+ private boolean _deleted = false;
+
+ User(String[] data) throws UnsupportedEncodingException
+ {
+ if (data.length != 2)
+ {
+ throw new IllegalArgumentException("User Data should be lenght 2, username, password");
+ }
+
+ _name = data[0];
+
+ byte[] encoded_password = data[1].getBytes(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 User(String name, char[] password)
+ {
+ _name = name;
+ setPassword(password);
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public String toString()
+ {
+ if (_logger.isDebugEnabled())
+ {
+ return getName() + ((_encodedPassword == null) ? "" : ":" + new String(_encodedPassword));
+ }
+ else
+ {
+ return _name;
+ }
+ }
+
+ char[] getPassword()
+ {
+ return _password;
+ }
+
+ void setPassword(char[] password)
+ {
+ _password = password;
+ _modified = true;
+ _encodedPassword = null;
+ }
+
+
+ byte[] getEncodePassword() 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/ConfigurationFilePrincipalDatabaseManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java
new file mode 100644
index 0000000000..c417d7e244
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java
@@ -0,0 +1,236 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.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);
+
+ private static final String _base = "security.principal-databases.principal-database";
+
+ Map<String, PrincipalDatabase> _databases;
+
+ public ConfigurationFilePrincipalDatabaseManager() throws Exception
+ {
+ _logger.info("Initialising PrincipleDatabase authentication manager");
+ _databases = initialisePrincipalDatabases();
+ }
+
+ private Map<String, PrincipalDatabase> initialisePrincipalDatabases() throws Exception
+ {
+ Configuration config = ApplicationRegistry.getInstance().getConfiguration();
+ List<String> databaseNames = config.getList(_base + ".name");
+ List<String> databaseClasses = config.getList(_base + ".class");
+ 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, config, i);
+
+ String name = databaseNames.get(i);
+ if ((name == null) || (name.length() == 0))
+ {
+ throw new Exception("Principal database names must have length greater than or equal to one character");
+ }
+
+ PrincipalDatabase pd = databases.get(name);
+ if (pd != null)
+ {
+ throw new Exception("Duplicate principal database name not provided");
+ }
+
+ _logger.info("Initialised principal database '" + name + "' successfully");
+ databases.put(name, (PrincipalDatabase) o);
+ }
+
+ return databases;
+ }
+
+ private void initialisePrincipalDatabase(PrincipalDatabase principalDatabase, Configuration config, int index)
+ throws FileNotFoundException, ConfigurationException
+ {
+ String baseName = _base + "(" + index + ").attributes.attribute.";
+ List<String> argumentNames = config.getList(baseName + "name");
+ List<String> argumentValues = config.getList(baseName + "value");
+ for (int i = 0; i < argumentNames.size(); i++)
+ {
+ String argName = argumentNames.get(i);
+ if ((argName == null) || (argName.length() == 0))
+ {
+ throw new 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(Configuration config) throws ConfigurationException
+ {
+ try
+ {
+ AMQUserManagementMBean _mbean = new AMQUserManagementMBean();
+
+ String baseSecurity = "security.jmx";
+ List<String> principalDBs = config.getList(baseSecurity + ".principal-database");
+
+ if (principalDBs.size() == 0)
+ {
+ throw new ConfigurationException("No principal-database specified for jmx security(" + baseSecurity + ".principal-database)");
+ }
+
+ 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.getList(baseSecurity + ".access");
+
+ if (jmxaccesslist.size() == 0)
+ {
+ throw new ConfigurationException("No access control files specified for jmx security(" + baseSecurity + ".access)");
+ }
+
+ 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/PlainPasswordFilePrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java
new file mode 100644
index 0000000000..352d41a0ba
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.security.Principal;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+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
+{
+ private static final Logger _logger = Logger.getLogger(PlainPasswordFilePrincipalDatabase.class);
+
+ protected File _passwordFile;
+
+ protected Pattern _regexp = Pattern.compile(":");
+
+ protected Map<String, AuthenticationProviderInitialiser> _saslServers;
+
+ 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 FileNotFoundException
+ {
+ 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.");
+ }
+ }
+
+ public void setPassword(Principal principal, PasswordCallback callback) throws IOException,
+ AccountNotFoundException
+ {
+ if (_passwordFile == null)
+ {
+ throw new AccountNotFoundException("Unable to locate principal since no password file was specified during initialisation");
+ }
+ if (principal == null)
+ {
+ throw new IllegalArgumentException("principal must not be null");
+ }
+ char[] pwd = lookupPassword(principal.getName());
+ if (pwd != null)
+ {
+ callback.setPassword(pwd);
+ }
+ else
+ {
+ throw new AccountNotFoundException("No account found for principal " + principal);
+ }
+ }
+
+ public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException
+ {
+ try
+ {
+ char[] pwd = lookupPassword(principal);
+
+ return compareCharArray(pwd, password);
+ }
+ catch (IOException e)
+ {
+ return false;
+ }
+ }
+
+ 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
+ }
+
+ public Map<String, AuthenticationProviderInitialiser> getMechanisms()
+ {
+ return _saslServers;
+ }
+
+ public List<Principal> getUsers()
+ {
+ return new LinkedList<Principal>(); //todo
+ }
+
+ public Principal getUser(String username)
+ {
+ try
+ {
+ if (lookupPassword(username) != null)
+ {
+ return new UsernamePrincipal(username);
+ }
+ }
+ catch (IOException e)
+ {
+ //fall through to null return
+ }
+ 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 name of the principal to lookup
+ *
+ * @return char[] of the password
+ *
+ * @throws java.io.IOException whilst accessing the file
+ */
+ private char[] lookupPassword(String name) throws IOException
+ {
+ BufferedReader reader = null;
+ try
+ {
+ reader = new BufferedReader(new FileReader(_passwordFile));
+ String line;
+
+ while ((line = reader.readLine()) != null)
+ {
+ if (!line.startsWith("#"))
+ {
+ String[] result = _regexp.split(line);
+ if (result == null || result.length < 2)
+ {
+ continue;
+ }
+
+ if (name.equals(result[0]))
+ {
+ return result[1].toCharArray();
+ }
+ }
+ }
+ return null;
+ }
+ finally
+ {
+ if (reader != null)
+ {
+ reader.close();
+ }
+ }
+ }
+}
diff --git a/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..a82f9ed40b
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java
@@ -0,0 +1,100 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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);
+
+
+ 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..2c553ae76a
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.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.security.auth.database;
+
+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(Configuration config) 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..73d58ca489
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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");
+ }
+ char[] pwd = _users.getProperty(principal.getName()).toCharArray();
+ if (pwd != null)
+ {
+ callback.setPassword(pwd);
+ }
+ 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;
+ }
+ }
+}
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..6b86a46bd2
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.auth.database;
+
+import org.apache.commons.configuration.Configuration;
+
+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;
+ }
+
+ public void initialiseManagement(Configuration config)
+ {
+ //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..bb94e0b7bf
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.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.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);
+
+}
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..f589140e8e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.commons.configuration.SubsetConfiguration;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+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;
+
+ public PrincipalDatabaseAuthenticationManager(String name, Configuration 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.getString("security.authentication.name");
+
+ 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(providerMap), 1) == -1)
+ {
+ _logger.warn("Unable to set order of providers.");
+ }
+ }
+ else
+ {
+ _logger.warn("No additional SASL providers registered.");
+ }
+
+ }
+
+
+ private void initialiseAuthenticationMechanisms(Map<String, Class<? extends SaslServerFactory>> providerMap, Map<String, PrincipalDatabase> databases) throws Exception
+ {
+// Configuration config = ApplicationRegistry.getInstance().getConfiguration();
+// List<String> mechanisms = config.getList("security.sasl.mechanisms.mechanism.initialiser.class");
+//
+// // Maps from the mechanism to the properties used to initialise the server. See the method
+// // Sasl.createSaslServer for details of the use of these properties. This map is populated during initialisation
+// // of each provider.
+
+
+ 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);
+ }
+ }
+
+ public AuthenticationResult isAuthorize(VirtualHost vhost, String username)
+ {
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR);
+ }
+}
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..fd4ad86055
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/JCAProvider.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.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(Map<String, Class<? extends SaslServerFactory>> providerMap)
+ {
+ super("AMQSASLProvider", 1.0, "A JCA provider that registers all " +
+ "AMQ SASL providers that want to be registered");
+ register(providerMap);
+ //Security.addProvider(this);
+ }
+
+ private void register(Map<String, Class<? extends SaslServerFactory>> providerMap)
+ {
+ for (Map.Entry<String, Class<? extends SaslServerFactory>> me :
+ providerMap.entrySet())
+ {
+ put("SaslServerFactory." + me.getKey(), me.getValue().getName());
+ }
+ }
+}
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..7842f376fb
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.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.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");
+ passwordCb.setPassword(pwd.toCharArray());
+ AuthorizeCallback authzCb = new AuthorizeCallback(username, username);
+ Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb};
+ _cbh.handle(callbacks);
+ _complete = true;
+ if (authzCb.isAuthorized())
+ {
+ _authorizationId = authzCb.getAuthenticationID();
+ return null;
+ }
+ else
+ {
+ throw new SaslException("Authentication failed");
+ }
+ }
+ catch (AMQFrameDecodingException e)
+ {
+ throw new SaslException("Unable to decode response: " + e, e);
+ }
+ catch (IOException e)
+ {
+ throw new SaslException("Error processing data: " + e, e);
+ }
+ catch (UnsupportedCallbackException e)
+ {
+ throw new SaslException("Unable to obtain data from callback handler: " + e, e);
+ }
+ }
+
+ public boolean isComplete()
+ {
+ return _complete;
+ }
+
+ public String getAuthorizationID()
+ {
+ return _authorizationId;
+ }
+
+ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+ {
+ throw new SaslException("Unsupported operation");
+ }
+
+ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+ {
+ throw new SaslException("Unsupported operation");
+ }
+
+ public Object getNegotiatedProperty(String propName)
+ {
+ return null;
+ }
+
+ public void dispose() throws SaslException
+ {
+ _cbh = null;
+ }
+}
diff --git a/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..36aeb77fe1
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java
@@ -0,0 +1,149 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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);
+ // we do not care about the prompt but it throws if null
+ PasswordCallback passwordCb = new PasswordCallback("prompt", false);
+ // TODO: should not get pwd as a String but as a char array...
+ int passwordLen = response.length - authcidNullPosition - 1;
+ String pwd = new String(response, authcidNullPosition + 1, passwordLen, "utf8");
+ passwordCb.setPassword(pwd.toCharArray());
+ AuthorizeCallback authzCb = new AuthorizeCallback(authzid, authzid);
+ Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb};
+ _cbh.handle(callbacks);
+ _complete = true;
+ if (authzCb.isAuthorized())
+ {
+ _authorizationId = authzCb.getAuthenticationID();
+ return null;
+ }
+ else
+ {
+ throw new SaslException("Authentication failed");
+ }
+ }
+ catch (IOException e)
+ {
+ throw new SaslException("Error processing data: " + e, e);
+ }
+ catch (UnsupportedCallbackException e)
+ {
+ throw new SaslException("Unable to obtain data from callback handler: " + e, e);
+ }
+ }
+
+ private int findNullPosition(byte[] response, int startPosition)
+ {
+ int position = startPosition;
+ while (position < response.length)
+ {
+ if (response[position] == (byte) 0)
+ {
+ return position;
+ }
+ position++;
+ }
+ return -1;
+ }
+
+ public boolean isComplete()
+ {
+ return _complete;
+ }
+
+ public String getAuthorizationID()
+ {
+ return _authorizationId;
+ }
+
+ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+ {
+ throw new SaslException("Unsupported operation");
+ }
+
+ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+ {
+ throw new SaslException("Unsupported operation");
+ }
+
+ public Object getNegotiatedProperty(String propName)
+ {
+ return null;
+ }
+
+ public void dispose() throws SaslException
+ {
+ _cbh = null;
+ }
+
+}
diff --git a/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/MemoryMessageStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java
new file mode 100644
index 0000000000..7a6e0b011f
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java
@@ -0,0 +1,223 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.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.exchange.Exchange;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.MessageMetaData;
+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, Configuration config)
+ {
+ int hashtableCapacity = config.getInt(base + "." + HASHTABLE_CAPACITY_CONFIG, DEFAULT_HASHTABLE_CAPACITY);
+ _log.info("Using capacity " + hashtableCapacity + " for hash tables");
+ _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>(hashtableCapacity);
+ _contentBodyMap = new ConcurrentHashMap<Long, List<ContentChunk>>(hashtableCapacity);
+ }
+
+ public void configure(VirtualHost virtualHost, String base, Configuration 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 required to do anything
+ }
+
+ public void removeQueue(AMQShortString name) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void enqueueMessage(StoreContext context, AMQShortString name, Long messageId) throws AMQException
+ {
+ // Not required to do anything
+ }
+
+ public void dequeueMessage(StoreContext context, AMQShortString name, 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);
+ }
+
+ 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..2a83d9b649
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java
@@ -0,0 +1,261 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.exchange.Exchange;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.MessageMetaData;
+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 config 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, Configuration config) 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;
+
+ /**
+ * Removes the specified queue from the persistent store.
+ *
+ * @param name The queue to remove.
+ *
+ * @throws AMQException If the operation fails for any reason.
+ */
+ void removeQueue(AMQShortString name) throws AMQException;
+
+ /**
+ * Places a message onto a specified queue, in a given transactional context.
+ *
+ * @param context The transactional context for the operation.
+ * @param name The name of 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, AMQShortString name, 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 name The name of the queue to take the message from.
+ * @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, AMQShortString name, 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;
+}
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..3ee49d58cf
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreContext.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.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 = super.toString();
+ }
+
+ public StoreContext(String name)
+ {
+ _name = name;
+ }
+
+ public Object getPayload()
+ {
+ return _payload;
+ }
+
+ public void setPayload(Object payload)
+ {
+ _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/transport/ConnectorConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ConnectorConfiguration.java
new file mode 100644
index 0000000000..23aaf56876
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ConnectorConfiguration.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.transport;
+
+import org.apache.mina.common.IoAcceptor;
+import org.apache.mina.util.NewThreadExecutor;
+import org.apache.qpid.configuration.Configured;
+import org.apache.log4j.Logger;
+
+public class ConnectorConfiguration
+{
+ private static final Logger _logger = Logger.getLogger(ConnectorConfiguration.class);
+
+ public static final String DEFAULT_PORT = "5672";
+
+ public static final String SSL_PORT = "8672";
+
+ @Configured(path = "connector.processors",
+ defaultValue = "4")
+ public int processors;
+
+ @Configured(path = "connector.port",
+ defaultValue = DEFAULT_PORT)
+ public int port;
+
+ @Configured(path = "connector.bind",
+ defaultValue = "wildcard")
+ public String bindAddress;
+
+ @Configured(path = "connector.socketReceiveBuffer",
+ defaultValue = "32767")
+ public int socketReceiveBufferSize;
+
+ @Configured(path = "connector.socketWriteBuffer",
+ defaultValue = "32767")
+ public int socketWriteBuferSize;
+
+ @Configured(path = "connector.tcpNoDelay",
+ defaultValue = "true")
+ public boolean tcpNoDelay;
+
+ @Configured(path = "advanced.filterchain[@enableExecutorPool]",
+ defaultValue = "false")
+ public boolean enableExecutorPool;
+
+ @Configured(path = "advanced.enablePooledAllocator",
+ defaultValue = "false")
+ public boolean enablePooledAllocator;
+
+ @Configured(path = "advanced.enableDirectBuffers",
+ defaultValue = "false")
+ public boolean enableDirectBuffers;
+
+ @Configured(path = "connector.ssl.enabled",
+ defaultValue = "false")
+ public boolean enableSSL;
+
+ @Configured(path = "connector.ssl.sslOnly",
+ defaultValue = "true")
+ public boolean sslOnly;
+
+ @Configured(path = "connector.ssl.port",
+ defaultValue = SSL_PORT)
+ public int sslPort;
+
+ @Configured(path = "connector.ssl.keystorePath",
+ defaultValue = "none")
+ public String keystorePath;
+
+ @Configured(path = "connector.ssl.keystorePassword",
+ defaultValue = "none")
+ public String keystorePassword;
+
+ @Configured(path = "connector.ssl.certType",
+ defaultValue = "SunX509")
+ public String certType;
+
+ @Configured(path = "connector.qpidnio",
+ defaultValue = "false")
+ public boolean _multiThreadNIO;
+
+
+ public IoAcceptor createAcceptor()
+ {
+ if (_multiThreadNIO)
+ {
+ _logger.warn("Using Qpid Multithreaded IO Processing");
+ return new org.apache.mina.transport.socket.nio.MultiThreadSocketAcceptor(processors, new NewThreadExecutor());
+ }
+ else
+ {
+ _logger.warn("Using Mina IO Processing");
+ return new org.apache.mina.transport.socket.nio.SocketAcceptor(processors, new NewThreadExecutor());
+ }
+ }
+}
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/CleanupMessageOperation.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/CleanupMessageOperation.java
new file mode 100644
index 0000000000..988f589339
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/CleanupMessageOperation.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.txn;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.NoConsumersException;
+import org.apache.qpid.server.store.StoreContext;
+
+import java.util.List;
+
+/**
+ * @author Apache Software Foundation
+ */
+public class CleanupMessageOperation implements TxnOp
+{
+ private static final Logger _log = Logger.getLogger(CleanupMessageOperation.class);
+
+ private final AMQMessage _msg;
+
+ private final List<RequiredDeliveryException> _returns;
+
+ public CleanupMessageOperation(AMQMessage msg, List<RequiredDeliveryException> returns)
+ {
+ _msg = msg;
+ _returns = returns;
+ }
+
+ public void prepare(StoreContext context) throws AMQException
+ { }
+
+ public void undoPrepare()
+ {
+ // don't need to do anything here, if the store's txn failed
+ // when processing prepare then the message was not stored
+ // or enqueued on any queues and can be discarded
+ }
+
+ public void commit(StoreContext context)
+ {
+ // No-op can't be done here has this is before the message has been attempted to be delivered.
+ /*try
+ {
+ _msg.checkDeliveredToConsumer();
+ }
+ catch (NoConsumersException e)
+ {
+ _returns.add(e);
+ }*/
+ }
+
+ public void rollback(StoreContext context)
+ {
+ // NO OP
+ }
+}
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..b12afd9a41
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.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.txn;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.RequiredDeliveryException;
+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.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.NoConsumersException;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.server.store.StoreContext;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/** 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<DeliveryDetails> _postCommitDeliveryList = new LinkedList<DeliveryDetails>();
+
+ /**
+ * We keep hold of the ack operation so that we can consolidate acks, i.e. multiple acks within a txn are
+ * consolidated into a single operation
+ */
+ private TxAck _ackOp;
+
+ private List<RequiredDeliveryException> _returnMessages;
+
+ private final MessageStore _messageStore;
+
+ private final StoreContext _storeContext;
+
+ private boolean _inTran = false;
+
+ /** Are there messages to deliver. NOT Has the message been delivered */
+ private boolean _messageDelivered = false;
+
+ private static class DeliveryDetails
+ {
+ public QueueEntry entry;
+
+ private boolean deliverFirst;
+
+ public DeliveryDetails(QueueEntry entry, boolean deliverFirst)
+ {
+ this.entry = entry;
+ this.deliverFirst = deliverFirst;
+ }
+ }
+
+ public LocalTransactionalContext(MessageStore messageStore, StoreContext storeContext,
+ List<RequiredDeliveryException> returnMessages)
+ {
+ _messageStore = messageStore;
+ _storeContext = storeContext;
+ _returnMessages = returnMessages;
+ // _txnBuffer.enlist(new StoreMessageOperation(messageStore));
+ }
+
+ public StoreContext getStoreContext()
+ {
+ return _storeContext;
+ }
+
+ public void rollback() throws AMQException
+ {
+ _txnBuffer.rollback(_storeContext);
+ // Hack to deal with uncommitted non-transactional writes
+ if (_messageStore.inTran(_storeContext))
+ {
+ _messageStore.abortTran(_storeContext);
+ _inTran = false;
+ }
+
+ _postCommitDeliveryList.clear();
+ }
+
+ public void deliver(QueueEntry entry, boolean deliverFirst) 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. Finally a cleanup op will be added to decrement
+ // the reference associated with the routing.
+ // message.incrementReference();
+ _postCommitDeliveryList.add(new DeliveryDetails(entry, deliverFirst));
+ _messageDelivered = true;
+ _txnBuffer.enlist(new CleanupMessageOperation(entry.getMessage(), _returnMessages));
+ /*_txnBuffer.enlist(new DeliverMessageOperation(message, queue));
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Incrementing ref count on message and enlisting cleanup operation - id " +
+ message.getMessageId());
+ }
+ message.incrementReference();
+ _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)
+ {
+ beginTranIfNecessary();
+ _ackOp = new TxAck(unacknowledgedMessageMap);
+ _txnBuffer.enlist(_ackOp);
+ }
+ // update the op to include this ack request
+ if (multiple && (deliveryTag == 0))
+ {
+ // if have signalled to ack all, that refers only
+ // to all at this time
+ _ackOp.update(lastDeliveryTag, multiple);
+ }
+ else
+ {
+ _ackOp.update(deliveryTag, multiple);
+ }
+ }
+
+ public void messageFullyReceived(boolean persistent) throws AMQException
+ {
+ // Not required in this transactional context
+ }
+
+ public void 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);
+ }
+
+ _messageStore.beginTran(_storeContext);
+ _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(_messageStore));
+ }
+ // fixme fail commit here ... QPID-440
+ try
+ {
+ _txnBuffer.commit(_storeContext);
+ }
+ finally
+ {
+ _messageDelivered = false;
+ _inTran = _messageStore.inTran(_storeContext);
+ }
+
+ try
+ {
+ postCommitDelivery(_returnMessages);
+ }
+ catch (AMQException e)
+ {
+ // OK so what do we do now...?
+ _log.error("Failed to deliver messages following txn commit: " + e, e);
+ }
+ }
+
+ private void postCommitDelivery(List<RequiredDeliveryException> returnMessages) throws AMQException
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Performing post commit delivery");
+ }
+
+ try
+ {
+ for (DeliveryDetails dd : _postCommitDeliveryList)
+ {
+ dd.entry.process(_storeContext, dd.deliverFirst);
+
+ try
+ {
+ dd.entry.checkDeliveredToConsumer();
+ }
+ catch (NoConsumersException nce)
+ {
+ returnMessages.add(nce);
+ }
+ }
+ }
+ 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..1e4b69c935
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java
@@ -0,0 +1,228 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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 java.util.Set;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.ack.UnacknowledgedMessage;
+import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.NoConsumersException;
+import org.apache.qpid.server.queue.QueueEntry;
+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 Set<Long> _browsedAcks;
+
+ 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, Set<Long> browsedAcks)
+ {
+ _channel = channel;
+ _storeContext = storeContext;
+ _returnMessages = returnMessages;
+ _messageStore = messageStore;
+ _browsedAcks = browsedAcks;
+ }
+
+
+ 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(QueueEntry entry, boolean deliverFirst) throws AMQException
+ {
+ try
+ {
+ entry.process(_storeContext, deliverFirst);
+ //following check implements the functionality
+ //required by the 'immediate' flag:
+ entry.checkDeliveredToConsumer();
+ }
+ catch (NoConsumersException e)
+ {
+ _returnMessages.add(e);
+ }
+ }
+
+ public void acknowledgeMessage(final long deliveryTag, long lastDeliveryTag,
+ boolean multiple, final UnacknowledgedMessageMap unacknowledgedMessageMap)
+ throws AMQException
+ {
+ if (multiple)
+ {
+ if (deliveryTag == 0)
+ {
+
+ //Spec 2.1.6.11 ... If the multiple field is 1, and the delivery tag is zero,
+ // tells the server to acknowledge all outstanding mesages.
+ _log.info("Multiple ack on delivery tag 0. ACKing all messages. Current count:" +
+ unacknowledgedMessageMap.size());
+ unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor()
+ {
+ public boolean callback(UnacknowledgedMessage message) throws AMQException
+ {
+ if (!_browsedAcks.contains(deliveryTag))
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Discarding message: " + message.getMessage().getMessageId());
+ }
+
+ //Message has been ack so discard it. This will dequeue and decrement the reference.
+ message.discard(_storeContext);
+ }
+ else
+ {
+ _browsedAcks.remove(deliveryTag);
+ }
+ return false;
+ }
+
+ public void visitComplete()
+ {
+ unacknowledgedMessageMap.clear();
+ }
+ });
+ }
+ else
+ {
+ if (!unacknowledgedMessageMap.contains(deliveryTag))
+ {
+ throw new AMQException("Multiple ack on delivery tag " + deliveryTag + " not known for channel");
+ }
+
+ LinkedList<UnacknowledgedMessage> acked = new LinkedList<UnacknowledgedMessage>();
+ unacknowledgedMessageMap.drainTo(acked, deliveryTag);
+ for (UnacknowledgedMessage msg : acked)
+ {
+ if (!_browsedAcks.contains(deliveryTag))
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Discarding message: " + msg.getMessage().getMessageId());
+ }
+
+ //Message has been ack so discard it. This will dequeue and decrement the reference.
+ msg.discard(_storeContext);
+ }
+ else
+ {
+ _browsedAcks.remove(deliveryTag);
+ }
+ }
+ }
+ }
+ else
+ {
+ UnacknowledgedMessage msg;
+ msg = unacknowledgedMessageMap.remove(deliveryTag);
+
+ if (msg == null)
+ {
+ _log.info("Single ack on delivery tag " + deliveryTag + " not known for channel:" +
+ _channel.getChannelId());
+ throw new AMQException("Single ack on delivery tag " + deliveryTag + " not known for channel:" +
+ _channel.getChannelId());
+ }
+
+ if (!_browsedAcks.contains(deliveryTag))
+ {
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Discarding message: " + msg.getMessage().getMessageId());
+ }
+
+ //Message has been ack so discard it. This will dequeue and decrement the reference.
+ msg.discard(_storeContext);
+ }
+ else
+ {
+ _browsedAcks.remove(deliveryTag);
+ }
+
+ if (_log.isDebugEnabled())
+ {
+ _log.debug("Received non-multiple ack for messaging with delivery tag " + deliveryTag + " msg id " +
+ msg.getMessage().getMessageId());
+ }
+ }
+ }
+
+ public void messageFullyReceived(boolean persistent) throws AMQException
+ {
+ if (persistent)
+ {
+ _messageStore.commitTran(_storeContext);
+ _inTran = false;
+ }
+ }
+
+ public void messageProcessed(AMQProtocolSession protocolSession) throws AMQException
+ {
+ _channel.processReturns(protocolSession);
+ }
+}
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..6016ecc1a5
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java
@@ -0,0 +1,170 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.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. A 'deliverFirst' flag may be set if the message is a
+ * redelivery, and should be placed on the front of the queue.
+ *
+ * <p/>This is an 'enqueue' operation.
+ *
+ * @param entry The message to deliver, and the queue to deliver to.
+ * @param deliverFirst <tt>true</tt> to place the message on the front of the queue for redelivery, <tt>false</tt>
+ * for normal FIFO message ordering.
+ *
+ * @throws AMQException If the message cannot be delivered for any reason.
+ */
+ void deliver(QueueEntry entry, boolean deliverFirst) 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..0acfa84f31
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.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.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Properties;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.MapConfiguration;
+import org.apache.qpid.server.management.ManagedObjectRegistry;
+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.auth.manager.AuthenticationManager;
+import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager;
+import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager;
+import org.apache.qpid.server.security.auth.database.PropertiesPrincipalDatabaseManager;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.plugins.AllowAll;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+
+public class NullApplicationRegistry extends ApplicationRegistry
+{
+ private ManagedObjectRegistry _managedObjectRegistry;
+
+ private AuthenticationManager _authenticationManager;
+
+ private VirtualHostRegistry _virtualHostRegistry;
+
+ private ACLPlugin _accessManager;
+
+ private PrincipalDatabaseManager _databaseManager;
+
+ private PluginManager _pluginManager;
+
+
+ public NullApplicationRegistry()
+ {
+ super(new MapConfiguration(new HashMap()));
+ }
+
+ public void initialise() throws Exception
+ {
+ _configuration.addProperty("store.class", "org.apache.qpid.server.store.MemoryMessageStore");
+
+ Properties users = new Properties();
+
+ users.put("guest", "guest");
+
+ _databaseManager = new PropertiesPrincipalDatabaseManager("default", users);
+
+ _accessManager = new AllowAll();
+
+ _authenticationManager = new PrincipalDatabaseAuthenticationManager(null, null);
+
+ _managedObjectRegistry = new NoopManagedObjectRegistry();
+ _virtualHostRegistry = new VirtualHostRegistry();
+ VirtualHost dummyHost = new VirtualHost("test", getConfiguration());
+ _virtualHostRegistry.registerVirtualHost(dummyHost);
+ _virtualHostRegistry.setDefaultVirtualHostName("test");
+ _pluginManager = new PluginManager("");
+ _configuration.addProperty("heartbeat.delay", 10 * 60); // 10 minutes
+
+ }
+
+ public Configuration getConfiguration()
+ {
+ return _configuration;
+ }
+
+
+ public ManagedObjectRegistry getManagedObjectRegistry()
+ {
+ return _managedObjectRegistry;
+ }
+
+ public PrincipalDatabaseManager getDatabaseManager()
+ {
+ return _databaseManager;
+ }
+
+ public AuthenticationManager getAuthenticationManager()
+ {
+ return _authenticationManager;
+ }
+
+ public Collection<String> getVirtualHostNames()
+ {
+ String[] hosts = {"test"};
+ return Arrays.asList(hosts);
+ }
+
+ public VirtualHostRegistry getVirtualHostRegistry()
+ {
+ return _virtualHostRegistry;
+ }
+
+ public ACLPlugin getAccessManager()
+ {
+ return _accessManager;
+ }
+
+ public PluginManager getPluginManager()
+ {
+ return _pluginManager;
+ }
+}
+
+
+
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..85d804457e
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.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.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";
+
+ /**
+ * 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..90004a028c
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.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.
+ *
+ */
+package org.apache.qpid.server.virtualhost;
+
+import javax.management.NotCompliantMBeanException;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.log4j.Logger;
+import org.apache.qpid.server.AMQBrokerManagerMBean;
+import org.apache.qpid.server.security.access.ACLPlugin;
+import org.apache.qpid.server.security.access.ACLManager;
+import org.apache.qpid.server.security.access.Accessable;
+import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager;
+import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
+import org.apache.qpid.server.configuration.Configurator;
+import org.apache.qpid.server.exchange.DefaultExchangeFactory;
+import org.apache.qpid.server.exchange.DefaultExchangeRegistry;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.management.AMQManagedObject;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.queue.DefaultQueueRegistry;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.AMQException;
+
+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 QueueRegistry _queueRegistry;
+
+ private ExchangeRegistry _exchangeRegistry;
+
+ private ExchangeFactory _exchangeFactory;
+
+ private MessageStore _messageStore;
+
+ protected VirtualHostMBean _virtualHostMBean;
+
+ private AMQBrokerManagerMBean _brokerMBean;
+
+ private AuthenticationManager _authenticationManager;
+
+ private ACLPlugin _accessManager;
+
+ private final Timer _houseKeepingTimer = new Timer("Queue-housekeeping", true);
+
+ private static final long DEFAULT_HOUSEKEEPING_PERIOD = 30000L;
+
+ public void setAccessableName(String name)
+ {
+ _logger.warn("Setting Accessable Name for VirualHost is not allowed. ("
+ + name + ") ignored remains :" + getAccessableName());
+ }
+
+ public String getAccessableName()
+ {
+ return _name;
+ }
+
+
+ /**
+ * 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, "VirtualHost");
+ }
+
+ public String getObjectInstanceName()
+ {
+ return _name.toString();
+ }
+
+ public String getName()
+ {
+ return _name.toString();
+ }
+
+ public VirtualHost getVirtualHost()
+ {
+ return VirtualHost.this;
+ }
+
+
+ } // End of MBean class
+
+ /**
+ * Used for testing only
+ * @param name
+ * @param store
+ * @throws Exception
+ */
+ public VirtualHost(String name, MessageStore store) throws Exception
+ {
+ this(name, new PropertiesConfiguration(), store);
+ }
+
+ /**
+ * Normal Constructor
+ * @param name
+ * @param hostConfig
+ * @throws Exception
+ */
+ public VirtualHost(String name, Configuration hostConfig) throws Exception
+ {
+ this(name, hostConfig, null);
+ }
+
+ public VirtualHost(String name, Configuration hostConfig, MessageStore store) throws Exception
+ {
+ _name = name;
+
+ _virtualHostMBean = new VirtualHostMBean();
+ // This isn't needed to be registered
+ //_virtualHostMBean.register();
+
+ _queueRegistry = new DefaultQueueRegistry(this);
+ _exchangeFactory = new DefaultExchangeFactory(this);
+ _exchangeFactory.initialise(hostConfig);
+ _exchangeRegistry = new DefaultExchangeRegistry(this);
+
+ if (store != null)
+ {
+ _messageStore = store;
+ }
+ else
+ {
+ if (hostConfig == null)
+ {
+ throw new IllegalAccessException("HostConfig and MessageStore cannot be null");
+ }
+ initialiseMessageStore(hostConfig);
+ }
+
+ _exchangeRegistry.initialise();
+
+ _authenticationManager = new PrincipalDatabaseAuthenticationManager(name, hostConfig);
+
+ _accessManager = ACLManager.loadACLManager(name, hostConfig);
+
+ _brokerMBean = new AMQBrokerManagerMBean(_virtualHostMBean);
+ _brokerMBean.register();
+ initialiseHouseKeeping(hostConfig);
+ }
+
+ private void initialiseHouseKeeping(final Configuration hostConfig)
+ {
+
+ long period = hostConfig.getLong("housekeeping.expiredMessageCheckPeriod", DEFAULT_HOUSEKEEPING_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.removeExpiredIfNoSubscribers();
+ }
+ 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(Configuration config) throws Exception
+ {
+ String messageStoreClass = config.getString("store.class");
+
+ 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) o;
+ _messageStore.configure(this, "store", config);
+ }
+
+
+ public <T> T getConfiguredObject(Class<T> instanceType, Configuration config)
+ {
+ T instance;
+ try
+ {
+ instance = instanceType.newInstance();
+ }
+ catch (Exception e)
+ {
+ _logger.error("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor");
+ throw new IllegalArgumentException("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor", e);
+ }
+ Configurator.configure(instance);
+
+ return instance;
+ }
+
+
+ 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 ACLPlugin getAccessManager()
+ {
+ return _accessManager;
+ }
+
+ public void close() throws Exception
+ {
+ if (_messageStore != null)
+ {
+ _messageStore.close();
+ }
+ }
+
+ public ManagedObject getBrokerMBean()
+ {
+ return _brokerMBean;
+ }
+
+ public ManagedObject getManagedObject()
+ {
+ return _virtualHostMBean;
+ }
+}
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..edc900f401
--- /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.queue.AMQQueue;
+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.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..a5b3a87616
--- /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.server.queue.AMQQueue;
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+
+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..218d5f04ed
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Dump.java
@@ -0,0 +1,301 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.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 QueueEntry(null,msg));
+
+ 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..7e21253fab
--- /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.AMQMessage;
+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..f187e26593
--- /dev/null
+++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Purge.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.tools.messagestore.commands;
+
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.tools.messagestore.MessageStoreTool;
+
+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..fd7d4c3f13
--- /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.queue.AMQQueue;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+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..a6dccf0f36
--- /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.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;
+import java.util.StringTokenizer;
+
+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/RunBrokerWithCommand.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/RunBrokerWithCommand.java
new file mode 100644
index 0000000000..d8b5f5f7e6
--- /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 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/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/exchange/DestWildExchangeTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.java
new file mode 100644
index 0000000000..7e2d56b460
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.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.exchange;
+
+import junit.framework.TestCase;
+import junit.framework.Assert;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.MessageHandleFactory;
+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.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.LinkedList;
+
+public class DestWildExchangeTest extends TestCase
+{
+
+ DestWildExchange _exchange;
+
+ VirtualHost _vhost;
+ MessageStore _store;
+ StoreContext _context;
+
+
+ public void setUp() throws AMQException
+ {
+ _exchange = new DestWildExchange();
+ _vhost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHosts().iterator().next();
+ _store = new MemoryMessageStore();
+ _context = new StoreContext();
+ }
+
+
+ public void testNoRoute() throws AMQException
+ {
+ AMQQueue queue = new AMQQueue(new AMQShortString("a*#b"), false, null, false, _vhost);
+ _exchange.registerQueue(new AMQShortString("a.*.#.b"), queue, null);
+
+
+ MessagePublishInfo info = new PublishInfo(new AMQShortString("a.b"));
+
+ AMQMessage message = new AMQMessage(0L, info, null);
+
+ try
+ {
+ _exchange.route(message);
+ fail("Message has no route and shouldn't be routed");
+ }
+ catch (NoRouteException nre)
+ {
+ //normal
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+ }
+
+ public void testDirectMatch() throws AMQException
+ {
+ AMQQueue queue = new AMQQueue(new AMQShortString("ab"), false, null, false, _vhost);
+ _exchange.registerQueue(new AMQShortString("a.b"), queue, null);
+
+
+ AMQMessage message = createMessage("a.b");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a.c");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ 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 = new AMQQueue(new AMQShortString("a*"), false, null, false, _vhost);
+ _exchange.registerQueue(new AMQShortString("a.*"), queue, null);
+
+
+ AMQMessage message = createMessage("a.b");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a.c");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ 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 = new AMQQueue(new AMQShortString("a#"), false, null, false, _vhost);
+ _exchange.registerQueue(new AMQShortString("a.#"), queue, null);
+
+
+ AMQMessage message = createMessage("a.b.c");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a.b");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a.c");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ message = createMessage("a");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("b");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ 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 = new AMQQueue(new AMQShortString("a"), false, null, false, _vhost);
+ _exchange.registerQueue(new AMQShortString("a.*.#.b"), queue, null);
+
+
+ AMQMessage message = createMessage("a.c.d.b");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has no route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ message = createMessage("a.c.b");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has no route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+ public void testMatchafterHash() throws AMQException
+ {
+ AMQQueue queue = new AMQQueue(new AMQShortString("a#"), false, null, false, _vhost);
+ _exchange.registerQueue(new AMQShortString("a.*.#.b.c"), queue, null);
+
+
+ AMQMessage message = createMessage("a.c.b.b");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ fail("Message has route and should not be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+
+
+ message = createMessage("a.a.b.c");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has no route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ message = createMessage("a.b.c.b");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ 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
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has no route and should be routed");
+
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+
+ public void testHashAfterHash() throws AMQException
+ {
+ AMQQueue queue = new AMQQueue(new AMQShortString("a#"), false, null, false, _vhost);
+ _exchange.registerQueue(new AMQShortString("a.*.#.b.c.#.d"), queue, null);
+
+
+ AMQMessage message = createMessage("a.c.b.b.c");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ 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
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has no route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+ public void testHashHash() throws AMQException
+ {
+ AMQQueue queue = new AMQQueue(new AMQShortString("a#"), false, null, false, _vhost);
+ _exchange.registerQueue(new AMQShortString("a.#.*.#.d"), queue, null);
+
+
+ AMQMessage message = createMessage("a.c.b.b.c");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ 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
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ }
+ catch (AMQException nre)
+ {
+ fail("Message has no route and should be routed");
+ }
+
+ Assert.assertEquals(1, queue.getMessageCount());
+
+ Assert.assertEquals("Wrong message recevied", message, queue.getMessagesOnTheQueue().get(0).getMessage());
+
+ queue.deleteMessageFromTop(_context);
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+ public void testSubMatchFails() throws AMQException
+ {
+ AMQQueue queue = new AMQQueue(new AMQShortString("a"), false, null, false, _vhost);
+ _exchange.registerQueue(new AMQShortString("a.b.c.d"), queue, null);
+
+
+ AMQMessage message = createMessage("a.b.c");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ fail("Message has route and should not be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+ public void testMoreRouting() throws AMQException
+ {
+ AMQQueue queue = new AMQQueue(new AMQShortString("a"), false, null, false, _vhost);
+ _exchange.registerQueue(new AMQShortString("a.b"), queue, null);
+
+
+ AMQMessage message = createMessage("a.b.c");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ fail("Message has route and should not be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+ public void testMoreQueue() throws AMQException
+ {
+ AMQQueue queue = new AMQQueue(new AMQShortString("a"), false, null, false, _vhost);
+ _exchange.registerQueue(new AMQShortString("a.b"), queue, null);
+
+
+ AMQMessage message = createMessage("a");
+
+ try
+ {
+ _exchange.route(message);
+ message.routingComplete(_store, _context, new MessageHandleFactory());
+ fail("Message has route and should not be routed");
+ }
+ catch (AMQException nre)
+ {
+ }
+
+ Assert.assertEquals(0, queue.getMessageCount());
+
+ }
+
+ private AMQMessage createMessage(String s) throws AMQException
+ {
+ MessagePublishInfo info = new PublishInfo(new AMQShortString(s));
+
+ TransactionalContext trancontext = new NonTransactionalContext(_store, _context, null,
+ new LinkedList<RequiredDeliveryException>(),
+ new HashSet<Long>());
+
+ AMQMessage message = new AMQMessage(0L, info, trancontext);
+ 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..18d8592817
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/ExchangeMBeanTest.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.exchange;
+
+import junit.framework.TestCase;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.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.CompositeData;
+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
+ {
+ DestNameExchange exchange = new DestNameExchange();
+ 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
+ {
+ DestWildExchange exchange = new DestWildExchange();
+ 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();
+ _virtualHost = applicationRegistry.getVirtualHostRegistry().getVirtualHost("test");
+ _queueRegistry = _virtualHost.getQueueRegistry();
+ _queue = new AMQQueue(new AMQShortString("testQueue"), false, new AMQShortString("ExchangeMBeanTest"), false, _virtualHost);
+ _queueRegistry.registerQueue(_queue);
+ }
+}
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/protocol/TestIoSession.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/TestIoSession.java
new file mode 100644
index 0000000000..ff4d3ed9fb
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/TestIoSession.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.protocol;
+
+import org.apache.mina.common.*;
+import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
+import org.apache.qpid.pool.ReadWriteThreadModel;
+
+import java.net.SocketAddress;
+import java.net.InetSocketAddress;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 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();
+
+ 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("127.0.0.1", 1234);
+ }
+
+ 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;
+ }
+
+ /**
+ * 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/protocol/TestMinaProtocolSession.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/TestMinaProtocolSession.java
new file mode 100644
index 0000000000..0c0d8f471e
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/TestMinaProtocolSession.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.AMQException;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.server.output.ProtocolOutputConverterRegistry;
+
+public class TestMinaProtocolSession extends AMQMinaProtocolSession
+{
+ public TestMinaProtocolSession() throws AMQException
+ {
+ super(new TestIoSession(),
+ ApplicationRegistry.getInstance().getVirtualHostRegistry(),
+ new AMQCodecFactory(true));
+ }
+
+ public ProtocolOutputConverter getProtocolOutputConverter()
+ {
+ return ProtocolOutputConverterRegistry.getConverter(this);
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return (byte)8;
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return (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..ed79384d42
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.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.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.protocol.TestMinaProtocolSession;
+import org.apache.qpid.server.protocol.AMQMinaProtocolSession;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+
+import javax.management.Notification;
+import java.util.LinkedList;
+import java.util.HashSet;
+
+/** 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 = null;
+ private MessageStore _messageStore = new MemoryMessageStore();
+ private StoreContext _storeContext = new StoreContext();
+ private TransactionalContext _transactionalContext = new NonTransactionalContext(_messageStore, _storeContext,
+ null,
+ new LinkedList<RequiredDeliveryException>(),
+ new HashSet<Long>());
+
+ /**
+ * Tests if the alert gets thrown when message count increases the threshold limit
+ *
+ * @throws Exception
+ */
+ public void testMessageCountAlert() throws Exception
+ {
+ _queue = new AMQQueue(new AMQShortString("testQueue1"), false, new AMQShortString("AMQueueAlertTest"),
+ false, _virtualHost);
+ _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 = new AMQQueue(new AMQShortString("testQueue2"), false, new AMQShortString("AMQueueAlertTest"),
+ false, _virtualHost);
+ _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 = new AMQQueue(new AMQShortString("testQueue3"), false, new AMQShortString("AMQueueAlertTest"),
+ false, _virtualHost);
+ _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 = new AMQQueue(new AMQShortString("testQueue4"), false, new AMQShortString("AMQueueAlertTest"),
+ false, _virtualHost);
+ _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);
+
+ sendMessages(1, MAX_MESSAGE_SIZE);
+ assertTrue(_queueMBean.getMessageCount() == 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.
+ The QueueDepth should decrease when messages are delivered from the queue (QPID-408)
+ */
+ public void testQueueDepthAlertWithSubscribers() throws Exception
+ {
+ protocolSession = new TestMinaProtocolSession();
+ AMQChannel channel = new AMQChannel(protocolSession, 2, _messageStore);
+ protocolSession.addChannel(channel);
+
+ // Create queue
+ _queue = getNewQueue();
+ _queue.registerProtocolSession(protocolSession, channel.getChannelId(),
+ new AMQShortString("consumer_tag"), true, null, false, 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) >> 10;
+ 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(0), 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.unregisterProtocolSession(protocolSession, channel.getChannelId(), new AMQShortString("consumer_tag"));
+ 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.
+ _queue.registerProtocolSession(protocolSession, channel.getChannelId(),
+ new AMQShortString("consumer_tag"), true, null, false, false);
+ _queue.deliverAsync();
+ while (_queue.getMessageCount() != 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.unregisterProtocolSession(protocolSession, channel.getChannelId(), new AMQShortString("consumer_tag"));
+ 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 AMQMessage 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
+ AMQMessage message = new AMQMessage(_messageStore.getNewMessageId(), publish, _transactionalContext);
+ message.setContentHeaderBody(contentHeaderBody);
+ message.setPublisher(protocolSession);
+ return message;
+ }
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ IApplicationRegistry applicationRegistry = ApplicationRegistry.getInstance();
+ _virtualHost = applicationRegistry.getVirtualHostRegistry().getVirtualHost("test");
+ }
+
+ private void sendMessages(long messageCount, long size) throws AMQException
+ {
+ AMQMessage[] messages = new AMQMessage[(int) messageCount];
+ for (int i = 0; i < messages.length; i++)
+ {
+ messages[i] = message(false, size);
+ messages[i].enqueue(_queue);
+ messages[i].routingComplete(_messageStore, _storeContext, new MessageHandleFactory());
+ }
+
+ for (int i = 0; i < messageCount; i++)
+ {
+ _queue.process(_storeContext, new QueueEntry(_queue,messages[i]), false);
+ }
+ }
+
+ private AMQQueue getNewQueue() throws AMQException
+ {
+ return new AMQQueue(new AMQShortString("testQueue" + Math.random()),
+ false,
+ new AMQShortString("AMQueueAlertTest"),
+ false,
+ _virtualHost);
+ }
+}
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..c02b47e9fd
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java
@@ -0,0 +1,305 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.server.AMQChannel;
+import org.apache.qpid.server.RequiredDeliveryException;
+import org.apache.qpid.server.protocol.TestMinaProtocolSession;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+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.LinkedList;
+import java.util.HashSet;
+
+/**
+ * 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;
+
+ public void testMessageCountTransient() throws Exception
+ {
+ int messageCount = 10;
+ sendMessages(messageCount, false);
+ assertTrue(_queueMBean.getMessageCount() == messageCount);
+ assertTrue(_queueMBean.getReceivedMessageCount() == messageCount);
+ long queueDepth = (messageCount * MESSAGE_SIZE) >> 10;
+ 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();
+ }
+
+ 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) >> 10;
+ 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
+ {
+ SubscriptionManager mgr = _queue.getSubscribers();
+ assertFalse(mgr.hasActiveSubscribers());
+ assertTrue(_queueMBean.getActiveConsumerCount() == 0);
+
+
+ TestMinaProtocolSession protocolSession = new TestMinaProtocolSession();
+ AMQChannel channel = new AMQChannel(protocolSession, 1, _messageStore);
+ protocolSession.addChannel(channel);
+
+ _queue.registerProtocolSession(protocolSession, 1, new AMQShortString("test"), false, null, false, false);
+ assertTrue(_queueMBean.getActiveConsumerCount() == 1);
+
+ SubscriptionSet _subscribers = (SubscriptionSet) mgr;
+ SubscriptionFactory subscriptionFactory = new SubscriptionImpl.Factory();
+ Subscription s1 = subscriptionFactory.createSubscription(channel.getChannelId(),
+ protocolSession,
+ new AMQShortString("S1"),
+ false,
+ null,
+ true,
+ _queue);
+
+ Subscription s2 = subscriptionFactory.createSubscription(channel.getChannelId(),
+ protocolSession,
+ new AMQShortString("S2"),
+ false,
+ null,
+ true,
+ _queue);
+ _subscribers.addSubscriber(s1);
+ _subscribers.addSubscriber(s2);
+ assertTrue(_queueMBean.getActiveConsumerCount() == 3);
+ assertTrue(_queueMBean.getConsumerCount() == 3);
+
+ s1.close();
+ assertTrue(_queueMBean.getActiveConsumerCount() == 2);
+ 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 >> 10));
+
+ 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)
+ {
+
+ }
+
+ AMQMessage msg = message(false, false);
+ long id = msg.getMessageId();
+ _queue.clearQueue(_storeContext);
+
+ msg.enqueue(_queue);
+ msg.routingComplete(_messageStore, _storeContext, new MessageHandleFactory());
+ _queue.process(_storeContext, new QueueEntry(_queue, msg), false);
+ _queueMBean.viewMessageContent(id);
+ try
+ {
+ _queueMBean.viewMessageContent(id + 1);
+ fail();
+ }
+ catch (JMException ex)
+ {
+
+ }
+ }
+
+ private AMQMessage 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));
+ return new AMQMessage(_messageStore.getNewMessageId(), publish, _transactionalContext, contentHeaderBody);
+ }
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ IApplicationRegistry applicationRegistry = ApplicationRegistry.getInstance();
+ _virtualHost = applicationRegistry.getVirtualHostRegistry().getVirtualHost("test");
+ _messageStore = _virtualHost.getMessageStore();
+
+ _transactionalContext = new NonTransactionalContext(_messageStore, _storeContext,
+ null,
+ new LinkedList<RequiredDeliveryException>(),
+ new HashSet<Long>());
+
+ _queue = new AMQQueue(new AMQShortString("testQueue"), false, new AMQShortString("AMQueueMBeanTest"), false, _virtualHost);
+ _queueMBean = new AMQQueueMBean(_queue);
+
+ _protocolSession = new TestMinaProtocolSession();
+ }
+
+ private void sendMessages(int messageCount, boolean persistent) throws AMQException
+ {
+ for (int i = 0; i < messageCount; i++)
+ {
+ AMQMessage currentMessage = message(false, persistent);
+ currentMessage.enqueue(_queue);
+
+ // route header
+ currentMessage.routingComplete(_messageStore, _storeContext, new MessageHandleFactory());
+
+ // Add the body so we have somthing to test later
+ currentMessage.addContentBodyFrame(_storeContext,
+ _protocolSession.getMethodRegistry()
+ .getProtocolVersionMethodConverter()
+ .convertToContentChunk(
+ new ContentBody(ByteBuffer.allocate((int) MESSAGE_SIZE),
+ MESSAGE_SIZE)));
+
+
+ }
+ }
+}
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..48d808142c
--- /dev/null
+++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.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.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 TestableMemoryMessageStore extends MemoryMessageStore
+{
+
+ MemoryMessageStore _mms = null;
+
+ 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;
+ }
+ }
+}
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);
+ }
+}